Создать растровое изображение из байтового массива данных пикселей - PullRequest
37 голосов
/ 22 июля 2011

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

Вот пример того, как выделить массив байтов (управляемую память) для данных пикселей и создатьРастровое изображение с его помощью:

Size size = new Size(800, 600);
PixelFormat pxFormat = PixelFormat.Format8bppIndexed;
//Get the stride, in this case it will have the same length of the width.
//Because the image Pixel format is 1 Byte/pixel.
//Usually stride = "ByterPerPixel"*Width

// Но это не всегда так. Больше информации на bobpowell .

int stride = GetStride(size.Width, pxFormat);
byte[] data = new byte[stride * size.Height];
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
Bitmap bmp = new Bitmap(size.Width, size.Height, stride,
             pxFormat, handle.AddrOfPinnedObject());

//After doing your stuff, free the Bitmap and unpin the array.
bmp.Dispose();
handle.Free();

public static int GetStride(int width, PixelFormat pxFormat)
{
    //float bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format);
    int bitsPerPixel = ((int)pxFormat >> 8) & 0xFF;
    //Number of bits used to store the image data per line (only the valid data)
    int validBitsPerLine = width * bitsPerPixel;
    //4 bytes for every int32 (32 bits)
    int stride = ((validBitsPerLine + 31) / 32) * 4;
    return stride;
}

Я думал, что Bitmap сделает копию данных массива, но на самом деле он указывает на те же данные.Если вы видите:

Color c;
c = bmp.GetPixel(0, 0);
Console.WriteLine("Color before: " + c.ToString());
//Prints: Color before: Color [A=255, R=0, G=0, B=0]
data[0] = 255;
c = bmp.GetPixel(0, 0);
Console.WriteLine("Color after: " + c.ToString());
//Prints: Color after: Color [A=255, R=255, G=255, B=255]

Вопросы:

  1. Безопасно ли создавать растровое изображение из массива byte [] (управляемогопамять) и свободна () GCHandle?Если это небезопасно, мне нужно сохранить закрепленный массив, насколько это плохо для ГХ / Производительность?

  2. Безопасно ли изменять данные (например: data [0] =255;)?

  3. Адрес Scan0 может быть изменен с помощью ГХ?Я имею в виду, я получаю Scan0 из заблокированного растрового изображения, затем разблокирую его и через некоторое время снова блокирую, Scan0 может быть другим?

  4. Какова цель ImageLockMode.UserInputBuffer вметод LockBits?Найти информацию об этом очень сложно!MSDN не объясняет это четко!

РЕДАКТИРОВАТЬ 1: Некоторые последующие действия

  1. Вы должны держать его закрепленным.Это замедлит GC? Я спросил это здесь .Это зависит от количества изображений и его размеров.Никто не дал мне количественного ответа.Это кажется, что это трудно определить.Вы также можете выделить память с помощью Marshal или использовать неуправляемую память, выделенную битовой картой.

  2. Я провел много тестов с использованием двух потоков.Пока растр заблокирован, все в порядке.Если битмап разблокирован, то это небезопасно! Мой пост о чтении / записи непосредственно в Scan0 .Ответ Боинга: «Я уже объяснил выше, почему вам повезло, что вы можете использовать scan0 вне блокировки. Потому что вы используете оригинальный bmp PixelFormat и этот GDI оптимизирован в этом случае, чтобы дать вам указатель, а не копию. Этот указатель действителенпока ОС не решит освободить ее. Единственный раз, когда есть гарантия, между LockBits и UnLockBits. Period. "

  3. Да, это может случиться, но большие области памяти обрабатываются по-разномуGC реже перемещает / освобождает этот большой объект.Таким образом, это может занять некоторое время, чтобы GC переместил этот массив. Из MSDN : «Любое выделение, большее или равное 85,000 bytes, идет на large object heap (LOH)» ... «LOH собирается только во время сбора данных поколения 2».В .NET 4.5 есть улучшения в LOH.

  4. На этот вопрос ответил @Boing.Но я собираюсь признать.Я не до конца понял это.Так что если бы Boing или кто-то еще мог please clarify it, я был бы рад.Кстати, почему я не могу просто напрямую читать / писать в Sca0 без блокировки ?=> Вы не должны писать напрямую в Scan0, потому что Scan0 указывает на копию данных точечного рисунка, созданную неуправляемой памятью (внутри GDI).После разблокировки эта память может быть перераспределена для других вещей, больше не уверен, что Scan0 будет указывать на фактические данные Bitmap.Это может быть воспроизведено с получением Scan0 в блокировке, разблокировке и выполнении некоторого поворота-переворота в разблокированном растровом изображении.Через некоторое время Scan0 укажет на недопустимую область, и вы получите исключение при попытке чтения / записи в ее ячейку памяти.

Ответы [ 4 ]

24 голосов
/ 05 марта 2012
  1. Безопасно, если вы marshal.copy данных, а не устанавливаете scan0 (напрямую или через эту перегрузку BitMap ()).Вы не хотите закреплять управляемые объекты, это ограничит сборщик мусора.
  2. Если вы копируете, совершенно безопасно.
  3. Входной массив управляется и может быть перемещен GC, scan0 - неуправляемый указатель, который устарел бы, если бы массив перемещался.Сам объект Bitmap управляется, но устанавливает указатель scan0 в Windows с помощью дескриптора.
  4. ImageLockMode.UserInputBuffer?По-видимому, его можно передать в LockBits, может быть, он говорит Bitmap () скопировать данные входного массива.

Пример кода для создания растрового изображения в градациях серого из массива:

    var b = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);

    ColorPalette ncp = b.Palette;
    for (int i = 0; i < 256; i++)
        ncp.Entries[i] = Color.FromArgb(255, i, i, i);
    b.Palette = ncp;

    var BoundsRect = new Rectangle(0, 0, Width, Height);
    BitmapData bmpData = b.LockBits(BoundsRect,
                                    ImageLockMode.WriteOnly,
                                    b.PixelFormat);

    IntPtr ptr = bmpData.Scan0;

    int bytes = bmpData.Stride*b.Height;
    var rgbValues = new byte[bytes];

    // fill in rgbValues, e.g. with a for loop over an input array

    Marshal.Copy(rgbValues, 0, ptr, bytes);
    b.UnlockBits(bmpData);
    return b;
10 голосов
/ 23 мая 2013

Относительно вашего вопроса 4: ImageLockMode.UserInputBuffer может дать вам контроль над процессом выделения того огромного объема памяти, на который можно ссылаться в объекте BitmapData.

Если вы решите создать себе объект BitmapData, вы можете избежать Marshall.Copy. Затем вам придется использовать этот флаг в сочетании с другим ImageLockMode.

Остерегайтесь, что это сложное дело, особенно в отношении Страйд и PixelFormat.

Вот пример, в котором за один снимок получит содержимое буфера 24 ббит / с в BitMap, а затем за один снимок прочитает его обратно в другой буфер в 48 ббит / с.

Size size = Image.Size;
Bitmap bitmap = Image;
// myPrewrittenBuff is allocated just like myReadingBuffer below (skipped for space sake)
// But with two differences: the buff would be byte [] (not ushort[]) and the Stride == 3 * size.Width (not 6 * ...) because we build a 24bpp not 48bpp
BitmapData writerBuff= bm.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb, myPrewrittenBuff);
// note here writerBuff and myPrewrittenBuff are the same reference
bitmap.UnlockBits(writerBuff);
// done. bitmap updated , no marshal needed to copy myPrewrittenBuff 

// Now lets read back the bitmap into another format...
BitmapData myReadingBuffer = new BitmapData();
ushort[] buff = new ushort[(3 * size.Width) * size.Height]; // ;Marshal.AllocHGlobal() if you want
GCHandle handle= GCHandle.Alloc(buff, GCHandleType.Pinned);
myReadingBuffer.Scan0 = Marshal.UnsafeAddrOfPinnedArrayElement(buff, 0);
myReadingBuffer.Height = size.Height;
myReadingBuffer.Width = size.Width;
myReadingBuffer.PixelFormat = PixelFormat.Format48bppRgb;
myReadingBuffer.Stride = 6 * size.Width;
// now read into that buff
BitmapData result = bitmap.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.ReadOnly, PixelFormat.Format48bppRgb, myReadingBuffer);
if (object.ReferenceEquals(result, myReadingBuffer)) {
    // Note: we pass here
    // and buff is filled
}
bitmap.UnlockBits(result);
handle.Free();
// use buff at will...

Если вы используете ILSpy, вы увидите, что этот метод ссылается на GDI + , и эти методы помогают более полно.

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

Затем вы сможете сойти с ума, например, выделив огромную виртуальную память, отображенную на scan0, и довольно эффективно их скопировать. Обратите внимание, что закрепление огромного массива (и особенно нескольких) не будет обременительным для GC и позволит вам совершенно безопасно манипулировать байтом / шортом (или небезопасно, если вы стремитесь к скорости)

6 голосов
/ 22 июля 2011

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

Однако традиционный способ создания растрового изображения изМассив байтов:

using (MemoryStream stream = new MemoryStream(byteArray))
{
     Bitmap bmp = new Bitmap(stream);
     // use bmp here....
}
0 голосов
/ 07 февраля 2014

Вот пример кода, который я написал для преобразования байтового массива пикселей в 8-битное изображение в оттенках серого (bmp) этот метод принимает массив пикселей, ширину и высоту изображения в качестве аргументов //

public Bitmap Convert2Bitmap(byte[] DATA, int width, int height)
{
    Bitmap Bm = new Bitmap(width,height,PixelFormat.Format24bppRgb);
    var b = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
    ColorPalette ncp = b.Palette;
    for (int i = 0; i < 256; i++)
        ncp.Entries[i] = Color.FromArgb(255, i, i, i);
    b.Palette = ncp;
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            int Value = DATA[x + (y * width)];
            Color C = ncp.Entries[Value];
            Bm.SetPixel(x,y,C);
        }
    }
   return Bm;
}
...