Сохранение одного канала растрового изображения в файл

Я использую этот код для сохранения растрового изображения в виде двоичных данных.

Bitmap bmp = new Bitmap(screenWidth, position);            
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);    


Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);

System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);

IntPtr ptr = bmpData.Scan0;

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

System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
bmp.UnlockBits(bmpData);            
File.WriteAllBytes(filename, bmp);    
g.Dispose();

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


person tommy_pop    schedule 10.06.2019    source источник
comment
Не уверен, как File.WriteAllBytes(filename, bmp); вообще должен работать; bmp — это растровый объект, а не массив байтов.   -  person Nyerguds    schedule 25.07.2019
comment
См. отмеченный дубликат для одного из многих существующих вопросов переполнения стека с ответами, объясняющими, как извлечь отдельные цветовые каналы (RGBA) из растрового изображения.   -  person Peter Duniho    schedule 25.07.2019
comment
@PeterDuniho Какой-то странный ответ; он сочетает в себе оптимизированные BitmapData техники со сверхмедленными SetPixel. И по причинам, упомянутым в моем ответе здесь, он вылетит и сгорит на любом входе, кроме 32-битного ARGB.   -  person Nyerguds    schedule 25.07.2019


Ответы (1)


Вы почти у цели, но не хватает нескольких ключевых деталей:

  • Вместо использования bmp.PixelFormat установите для объекта BitmapData формат пикселя в PixelFormat.Format32BppArgb, тогда вы будете на 100% уверены, какую структуру вы получите, а в 32-битном режиме шаг всегда будет точно соответствовать предсказуемому width * 4. Если вы этого не сделаете, вы можете получить неожиданные результаты, если считываемое изображение имеет палитру или какой-либо формат 16bpp, где каждый пиксель не может быть разделен на простые байты компонента цвета.
  • Прокрутите данные и извлеките канал. Порядок букв «ARGB» относится к шестнадцатеричному значению 0xAARRGGBB (как, например, 0xFF428ED0), которое представляет собой значение Uint32 с прямым порядком байтов, что означает, что фактический порядок байтов компонента цвета обратный: { BB, GG, RR, AA }.

Итак, чтобы извлечь ваш канал:

// Channels are: B=0, G=1, R=2, A=3
Int32 channel = 1 // for this example, extract the Green channel.

Int32 width;
Int32 height;
Byte[] rgbaValues;
using (Bitmap bmp = new Bitmap(screenWidth, position))
using (Graphics g = Graphics.FromImage(bmp))
{
    width = bmp.Width
    height = bmp.Height;
    g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);    
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    Int32 bytes = bmpData.Stride * bmp.Height;
    rgbaValues = new byte[bytes];
    Marshal.Copy(bmpData.Scan0, rgbValues, 0, bytes);
    bmp.UnlockBits(bmpData);
    g.Dispose();
}
Byte[] channelValues = new byte[width * height];

Int32 lineStart = 0;
Int32 lineStartChannel = 0;
for (Int32 y = 0; y < height; ++y)
{
    Int32 offset = lineStart;
    Int32 offsetChannel = lineStartChannel;
    for (Int32 x = 0; x < width; ++x)
    {
        // For reference:
        //Byte blue  = rgbaValues[offset + 0];
        //Byte green = rgbaValues[offset + 1];
        //Byte red   = rgbaValues[offset + 2];
        //Byte alpha = rgbaValues[offset + 3];
        channelValues[offsetChannel] = rgbaValues[offset + channel];
        offset += 4;
        offsetChannel++;
    }
    lineStart += stride;
    lineStartChannel += width;
}
File.WriteAllBytes(filename, channelValues);

Это просто сохраняет данные в виде массива байтов. Если вы хотите записать его как изображение, самый простой способ, вероятно, состоит в том, чтобы создать 8-битное растровое изображение, открыть на нем объект BitmapData и записать в него строки одну за другой, а затем установить его цветовую палитру в сгенерированный диапазон от от 0,0,0 до 255 255 255.

Я опубликовал функцию, которая принимает массив байтов, размеры изображения и палитру и создает из него изображение в этом ответе.

person Nyerguds    schedule 25.07.2019