Почему шаг в конструкторе System.Drawing.Bitmap должен быть кратным 4?

Я пишу приложение, которое требует, чтобы я взял проприетарный формат растрового изображения (MVTec Halcon HImage) и преобразовал его в System.Drawing.Bitmap на C #.

Единственные частные функции, данные мне, чтобы помочь мне в этом, включают запись в файл, за исключением использования функции «получить указатель».

Эта функция великолепна, она дает мне указатель на данные пикселей, ширину, высоту и тип изображения.

Моя проблема в том, что когда я создаю свой System.Drawing.Bitmap с помощью конструктора:

new System.Drawing.Bitmap(width, height, stride, format, scan)

Мне нужно указать «шаг», кратный 4. Это может быть проблемой, поскольку я не уверен, какой размер растрового изображения будет использоваться для моей функции. Предположим, у меня получается растровое изображение размером 111x111 пикселей, у меня нет другого способа запустить эту функцию, кроме добавления фиктивного столбца к моему изображению или вычитания 3 столбцов.

Есть ли способ обойти это ограничение?


person Gorchestopher H    schedule 02.02.2010    source источник


Ответы (6)


Это восходит к ранним проектам ЦП. Самый быстрый способ обработать биты битовой карты - прочитать их по 32 бита за раз, начиная с начала строки развертки. Это лучше всего работает, когда первый байт строки развертки выровнен по границе 32-битного адреса. Другими словами, адрес, кратный 4. На ранних процессорах неправильное выравнивание первого байта потребовало бы дополнительных циклов ЦП для чтения двух 32-битных слов из ОЗУ и перетасовки байтов для создания 32-битного значения. Обеспечение того, чтобы каждая строка сканирования начиналась с выровненного адреса (автоматически, если шаг кратен 4), этого можно избежать.

Это больше не является серьезной проблемой для современных процессоров, теперь гораздо важнее выравнивание по границе строки кеша. Тем не менее, требование кратного 4 для шага осталось по причинам совместимости приложений.

Кстати, вы можете легко рассчитать шаг по формату и ширине с помощью этого:

        int bitsPerPixel = ((int)format & 0xff00) >> 8;
        int bytesPerPixel = (bitsPerPixel + 7) / 8;
        int stride = 4 * ((width * bytesPerPixel + 3) / 4);
person Hans Passant    schedule 02.02.2010
comment
Это правильный ответ; плюс один весь день. Подробнее о вычислении stride: stackoverflow.com/questions/1983781/. - person jason; 02.02.2010
comment
Так что мне, скорее всего, следует попробовать изменить формат изображения. Прямо сейчас я использую то, что хранит каждый пиксель как один байт. Сказав это, я полагаю, что не могу использовать этот конструктор Bitmap, так как не существует типа изображения, который принимает только один байт и не полагается на нестандартную цветовую карту. - person Gorchestopher H; 02.02.2010
comment
Правильно, для 8bpp нужна палитра. GDI + плохо их поддерживает, это доставит вам массу хлопот. Вы можете сортировать его, загрузив существующее изображение 8bpp для начала. Вы можете украсть его записи палитры или LockBits напрямую, если он имеет правильный размер. Или синтезируйте один в MemoryStream. - person Hans Passant; 02.02.2010
comment
Спасибо всем за ваш вклад. В итоге я создал свою собственную палитру, а пока я просто заставлю ширину моего изображения быть кратной 4. - person Gorchestopher H; 03.02.2010
comment
@HansPassant привет! Мне интересно понять, почему первый байт строки развертки должен быть выровнен по границе 32-битного адреса, и я не уверен, что смогу получить это хорошо из ответа. не могли бы вы предоставить более подробные ресурсы по этому поводу? Благодарность! - person kzidane; 25.05.2015

Более простой способ - просто создать изображение с помощью конструктора (width, height, pixelformat). Затем он позаботится о самой походке.

Затем вы можете просто использовать LockBits, чтобы скопировать в него данные вашего изображения, строка за строкой, не беспокоясь о самих Stride; вы можете буквально просто запросить это у объекта BitmapData. Для фактической операции копирования для каждой строки развертки вы просто увеличиваете целевой указатель на шаг, а исходный указатель на ширину данных строки.

Вот пример, когда я получил данные изображения в байтовом массиве. Если это полностью компактные данные, ваш шаг ввода обычно равен ширине изображения, умноженной на количество байтов на пиксель. Если это 8-битные данные с палитрой, это просто ширина.

Если данные изображения были извлечены из объекта изображения, вы должны точно таким же образом сохранить исходный шаг этого процесса извлечения, извлекая его из объекта BitmapData.

/// <summary>
/// Creates a bitmap based on data, width, height, stride and pixel format.
/// </summary>
/// <param name="sourceData">Byte array of raw source data</param>
/// <param name="width">Width of the image</param>
/// <param name="height">Height of the image</param>
/// <param name="stride">Scanline length inside the data</param>
/// <param name="pixelFormat">Pixel format</param>
/// <param name="palette">Color palette</param>
/// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param>
/// <returns>The new image</returns>
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor)
{
    Bitmap newImage = new Bitmap(width, height, pixelFormat);
    BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
    Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
    // Compensate for possible negative stride on BMP format.
    Boolean isFlipped = stride < 0;
    stride = Math.Abs(stride);
    // Cache these to avoid unnecessary getter calls.
    Int32 targetStride = targetData.Stride;
    Int64 scan0 = targetData.Scan0.ToInt64();
    for (Int32 y = 0; y < height; y++)
        Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
    newImage.UnlockBits(targetData);
    // Fix negative stride on BMP format.
    if (isFlipped)
        newImage.RotateFlip(RotateFlipType.Rotate180FlipX);
    // For indexed images, set the palette.
    if ((pixelFormat & PixelFormat.Indexed) != 0 && palette != null)
    {
        ColorPalette pal = newImage.Palette;
        for (Int32 i = 0; i < pal.Entries.Length; i++)
        {
            if (i < palette.Length)
                pal.Entries[i] = palette[i];
            else if (defaultColor.HasValue)
                pal.Entries[i] = defaultColor.Value;
            else
                break;
        }
        newImage.Palette = pal;
    }
    return newImage;
}
person Nyerguds    schedule 14.05.2017

Как было сказано ранее Джейком, вы вычисляете шаг, находя количество байтов на пиксель (2 для 16 бит, 4 для 32 бит), а затем умножая его на ширину. Итак, если у вас ширина 111 и 32-битное изображение, у вас будет 444, что кратно 4.

Однако допустим на минуту, что у вас есть 24-битное изображение. 24 бита равны 3 байтам, поэтому при ширине 111 пикселей у вас будет 333 пикселя в качестве шага. Это, очевидно, не кратно 4. Таким образом, вам нужно округлить до 336 (следующее по величине кратное 4). Несмотря на то, что у вас есть немного лишнего, это неиспользуемое пространство недостаточно велико, чтобы действительно иметь большое значение для большинства приложений.

К сожалению, это ограничение невозможно обойти (если только вы не всегда используете 32- или 64-разрядные образы, которые всегда кратны 4.

person Tim C    schedule 02.02.2010

Помните, что stride отличается от width. У вас может быть изображение со 111 (8-битными) пикселями на строку, но каждая строка хранится в памяти 112 байтов.

Это сделано для эффективного использования памяти и, как сказал @Ian, данные хранятся в int32.

person Matt Warren    schedule 02.02.2010

Потому что он использует int32 для хранения каждого пикселя.

Sizeof(int32) = 4

Но не волнуйтесь, когда изображение сохраняется из памяти в файл, оно будет использовать наиболее эффективное использование памяти. Внутренне он использует 24 бита на пиксель (8 бит красного, 8 зеленых и 8 синих) и оставляет последние 8 бит избыточными.

person Dead account    schedule 02.02.2010

Правильный код:

public static void GetStride(int width, PixelFormat format, ref int stride, ref int bytesPerPixel)
{
    //int bitsPerPixel = ((int)format & 0xff00) >> 8;
    int bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format);
    bytesPerPixel = (bitsPerPixel + 7) / 8;
    stride = 4 * ((width * bytesPerPixel + 3) / 4);
}
person Pedro    schedule 14.07.2011