Помощь с декодером растровых изображений

#c# #.net #image #image-processing

#c# #.net #изображение #обработка изображений

Вопрос:

Я работал над декодером растровых изображений, но мой алгоритм обработки пиксельных данных, похоже, не совсем правильный:

     public IntPtr ReadPixels(Stream fs, int offset, int width, int height, int bpp)
    {
        IntPtr bBits;

        int pixelCount = bpp * width * height;
        int Row = 0;
        decimal value = ((bpp*width)/32)/4;
        int RowSize = (int)Math.Ceiling(value);
        int ArraySize = RowSize * Math.Abs(height);
        int Col = 0;
        Byte[] BMPData = new Byte[ArraySize];

        BinaryReader r = new BinaryReader(fs);
        r.BaseStream.Seek(offset, SeekOrigin.Begin);

        while (Row < height)
        {
            Byte ReadByte;

            if (!(Col >= RowSize))
            {                       
                ReadByte = r.ReadByte();
                BMPData[(Row * RowSize)   Col] = ReadByte;
                Col  = 1;                    
            }

            if (Col >= RowSize)
            {
                Col = 0;
                Row  = 1;
            }
        }

        bBits = System.Runtime.InteropServices.Marshal.AllocHGlobal(BMPData.Length);
        System.Runtime.InteropServices.Marshal.Copy(BMPData, 0, bBits, BMPData.Length);

        return bBits;
}
  

Я могу обрабатывать только монохромные растровые изображения, и на некоторых частях растрового изображения обрабатывается нормально. Ни один из них не сжат, и они отображаются вверх ногами и переворачиваются. Мне действительно не помешала бы некоторая помощь в этом вопросе.

Комментарии:

1. @user646265: почему вы используете это десятичное значение = ((bpp * width) / 32) /4? Для чего это? Я имею в виду / 32 и / 4…

2. 32 и 4 являются частным случаем для изображений с плавающими подпикселями и 4 каналами. Не будет работать для любого другого формата данных изображения.

3. @Marco: видишь en.wikipedia.org/wiki/BMP_file_format#Pixel_Storage .

Ответ №1:

     decimal value = ((bpp*width)/32)/4;
    int RowSize = (int)Math.Ceiling(value);
  

Это неверно. Ваша переменная RowSize на самом деле называется «stride». Вы вычисляете это следующим образом:

     int bytes = (width * bitsPerPixel   7) / 8;
    int stride = 4 * ((bytes   3) / 4);
  

Комментарии:

1. Будет работать только для одного пиксельного формата, крайне не рекомендуется использовать этот метод.

2. Эмм, нет, BitsPerPixel не является константой.

3. И использование десятичной системы счисления для чисел с плавающей запятой действительно неэффективно.

4. Хм, это потому, что stride на самом деле кратен 4. И в моем фрагменте нет плавающей точки, это целочисленная математика.

5. Шаг НЕ всегда кратен 4, чаще он кратен 8. Но это тоже может быть нечетное число, зависит от файла изображения. Ваш код предполагает наличие 4 каналов. Также значение субпикселя является либо плавающим, либо it, либо байтовым, а не десятичным.

Ответ №2:

Вы игнорируете stride.

Строки изображений можно дополнить слева дополнительными байтами, чтобы их размер делился на число, например (1 = без заполнения, 2, 4, 8 = по умолчанию для многих изображений, 16, …).

Кроме того, изображения могут представлять собой прямоугольную область внутри большего изображения, что делает «отступ» между строками на меньшем изображении еще больше (поскольку шаг — это шаг большего изображения). — В этом случае изображение также может иметь смещение для своей начальной точки в буфере.

Лучшая практика — это:

 // Overload this method 3 time for different bit per SUB-pixel values (8, 16, or 32)
// = (byte, int, float)
// SUB-pixel != pixel (= 1 3 or 4 sub-pixels (grey or RGB or BGR or BGRA or RGBA or ARGB or ABGR)
unsafe
{
    byte[] buffer = image.Buffer;
    int stride = image.buffer.Length / image.PixelHeight;
    // or int stride = image.LineSize; (or something like that)

    fixed (float* regionStart = (float*)(void*)buffer) // or byte* or int* depending on datatype
    {
        for (int y = 0; y < height; y  ) // height in pixels
        {
            // float* and float or byte* and byte or int* and int
            float* currentPos
                = regionStart   offset / SizeOf(float)   stride / SizeOf(float) * y;

            for (int x = 0; x < width; x  ) // width in pixels
            {
                for (int chan = 0; chan < channel; chan  ) // 1, 3 or 4 channels
                {
                    // DO NOT USE DECIMAL - you want accurate image values
                    // with best performance - primative types
                    // not a .NET complex type used for nice looking values for users e.g. 12.34
                    // instead use actual sub pixel type (float/int/byte) or double instead!
                    var currentValue = value;

                    currentPos  ;
                }
            }
        }
    }
}
  

Комментарии:

1. Предположения — смещение и шаг указаны в байтах, а не в фактическом типе данных — отсюда разделение на SizeOf(x). Фактические имена свойств BitmapSource и xxxDecoder могут отличаться (не могу запомнить их наизусть).

2. @Danny Varod: Я не совсем понимаю: является ли channel битами на пиксель и как мне получить доступ к указателю в конструкции Bitmap? P.S. Что такое image.buffer и image.pixel height? Тем не менее, спасибо за пример.

3. Размер субпикселя * количество каналов = бит на пиксель. Считайте формат пикселей (например, RGBA32 или RGB24) и декодируйте в формат количество каналов подпиксель, используя enum. (Например, RGBA = 4 канала 32/4 = 8 бит на подпиксель = формат байта.)

4. Высота пикселя — это высота в пикселях (не высота отображения). Буфер — это то, что вы получаете при считывании пикселей изображения (буфер памяти пикселей), например image.readpixels или stream.

5. Спасибо, этот код великолепен, но также сбивает с толку. Мне удалось применить stride и нормально отображать растровые изображения без него, в любом случае спасибо.

Ответ №3:

Я нахожу кое-что, чего не понимаю:

 decimal value = ((bpp*width)/32)/4;
int RowSize = (int)Math.Ceiling(value);
  

Размер строки, на мой взгляд, должен быть (bpp*width) / 8 (bpp%8==0?0:1)

Комментарии:

1. Размер строки может быть больше, например, из-за различных отступов. (Смотрите мой ответ.)