Создать Bitmap из байтового массива данных пикселей

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

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

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 [] (управляемая память) и free () GCHandle? Если это небезопасно, мне нужно сохранить закрепленный массив, насколько это плохо для GC / Performance?

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

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

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

РЕДАКТИРОВАТЬ 1: Некоторое продолжение

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

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

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

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

38
задан Dour High Arch 26 August 2019 в 23:48
поделиться