Почему «шаг» в конструкторе System.Drawing.Bitmap должен быть кратным 4? - PullRequest
21 голосов
/ 02 февраля 2010

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

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

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

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

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

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

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

Ответы [ 6 ]

60 голосов
/ 02 февраля 2010

Это восходит к ранним разработкам ЦП.Самый быстрый способ прорезать биты растрового изображения - это читать их по 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);
2 голосов
/ 14 мая 2017

Гораздо проще сделать изображение с помощью конструктора (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;
}
2 голосов
/ 02 февраля 2010

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

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

2 голосов
/ 02 февраля 2010

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

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

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

1 голос
/ 02 февраля 2010

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

Sizeof(int32) = 4

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

0 голосов
/ 14 июля 2011

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

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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...