Подтвердить что ты не робот

Зачем "шагать" в конструкторе System.Drawing.Bitmap быть кратным 4?

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

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

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

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

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

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

Есть ли способ, которым я могу подкрасться к этому ограничению?

4b9b3361

Ответ 1

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

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

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

        int bitsPerPixel = ((int)format & 0xff00) >> 8;
        int bytesPerPixel = (bitsPerPixel + 7) / 8;
        int stride = 4 * ((width * bytesPerPixel + 3) / 4);

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

Sizeof(int32) = 4

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

Ответ 5

Гораздо проще всего сделать изображение с помощью конструктора (width, height, pixelformat). Затем он заботится о самом шаге.

Затем вы можете просто использовать LockBits для копирования ваших данных изображения в него, 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;
}

Ответ 6

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

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);
}