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

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

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

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

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

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

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

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

ОБНОВЛЕНИЕ 1: некоторые последующие

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

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

  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 укажет на недопустимую область, и вы получите исключение при попытке чтения/записи в ее ячейку памяти.

4b9b3361

Ответ 1

  • Это безопасно, если вы используете данные marshal.copy, а не устанавливаете scan0 (напрямую или через эту перегрузку BitMap()). Вы не хотите, чтобы управляемые объекты были закреплены, это будет ограничивать сборщик мусора.
  • Если вы копируете, совершенно безопасно.
  • Входной массив управляется и может быть перемещен GC, scan0 - неуправляемый указатель, который будет устаревать, если массив перемещается. Сам объект Bitmap управляется, но устанавливает указатель scan0 в Windows с помощью дескриптора.
  • 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;

Ответ 2

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

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

Остерегайтесь, что это сложный бизнес, особенно касающийся Stride и PixelFormat.

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

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, и blit их достаточно эффективно. Обратите внимание, что закрепление огромного массива (и особенно нескольких) не будет обузой для GC и позволит вам полностью или полностью манипулировать байтом/коротким (или небезопасным, если вы ищете скорость)

Ответ 3

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

Однако традиционный способ создания Bitmap из массива Byte:

using (MemoryStream stream = new MemoryStream(byteArray))
{
     Bitmap bmp = new Bitmap(stream);
     // use bmp here....
}

Ответ 4

Вот пример кода, который я написал для преобразования байтового массива пикселей в 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;
}