Быстрая работа с растровыми изображениями в C # - PullRequest
38 голосов
/ 14 октября 2009

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

Используя Bitmap.GetPixel() и Bitmap.SetPixel(), моя программа работает медленно.

Как быстро конвертировать Bitmap в byte[] и обратно?

Мне нужен byte[] с length = (4 * width * height), содержащий данные RGBA каждого пикселя.

Ответы [ 6 ]

76 голосов
/ 14 октября 2009

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

Вот полный пример с использованием lockbits:

/*Note unsafe keyword*/
public unsafe Image ThresholdUA(float thresh)
{
    Bitmap b = new Bitmap(_image);//note this has several overloads, including a path to an image

    BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);

    byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);

    /*This time we convert the IntPtr to a ptr*/
    byte* scan0 = (byte*)bData.Scan0.ToPointer();

    for (int i = 0; i < bData.Height; ++i)
    {
        for (int j = 0; j < bData.Width; ++j)
        {
            byte* data = scan0 + i * bData.Stride + j * bitsPerPixel / 8;

            //data is a pointer to the first byte of the 3-byte color data
            //data[0] = blueComponent;
            //data[1] = greenComponent;
            //data[2] = redComponent;
        }
    }

    b.UnlockBits(bData);

    return b;
}

Вот то же самое, но с маршалингом:

/*No unsafe keyword!*/
public Image ThresholdMA(float thresh)
{
    Bitmap b = new Bitmap(_image);

    BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);

    /* GetBitsPerPixel just does a switch on the PixelFormat and returns the number */
    byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);

    /*the size of the image in bytes */
    int size = bData.Stride * bData.Height;

    /*Allocate buffer for image*/
    byte[] data = new byte[size];

    /*This overload copies data of /size/ into /data/ from location specified (/Scan0/)*/
    System.Runtime.InteropServices.Marshal.Copy(bData.Scan0, data, 0, size);

    for (int i = 0; i < size; i += bitsPerPixel / 8 )
    {
        double magnitude = 1/3d*(data[i] +data[i + 1] +data[i + 2]);

        //data[i] is the first of 3 bytes of color

    }

    /* This override copies the data back into the location specified */
    System.Runtime.InteropServices.Marshal.Copy(data, 0, bData.Scan0, data.Length);

    b.UnlockBits(bData);

    return b;
}
4 голосов
/ 03 сентября 2014

Вы можете использовать метод Bitmap.LockBits. Также, если вы хотите использовать параллельное выполнение задач, вы можете использовать класс Parallel в пространстве имен System.Threading.Tasks. Следующие ссылки имеют несколько примеров и объяснений.

3 голосов
/ 14 октября 2009

Вы хотите LockBits . Затем вы можете извлечь нужные байты из объекта BitmapData, который он вам дает.

2 голосов
/ 13 мая 2014

Есть еще один способ, который намного быстрее и намного удобнее. Если вы посмотрите на конструкторы Bitmap, вы найдете тот, который принимает и IntPtr в качестве последнего параметра. Это IntPtr для хранения данных пикселей. Так как ты это используешь?

Dim imageWidth As Integer = 1920
Dim imageHeight As Integer = 1080

Dim fmt As PixelFormat = PixelFormat.Format32bppRgb
Dim pixelFormatSize As Integer = Image.GetPixelFormatSize(fmt)

Dim stride As Integer = imageWidth * pixelFormatSize
Dim padding = 32 - (stride Mod 32)
If padding < 32 Then stride += padding

Dim pixels((stride \ 32) * imageHeight) As Integer
Dim handle As GCHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned)
Dim addr As IntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(pixels, 0)

Dim bitmap As New Bitmap(imageWidth, imageHeight, stride \ 8, fmt, addr)

Теперь у вас есть простой массив Integer и растровое изображение, ссылающиеся на одну и ту же память. Любые изменения, внесенные вами в массив Integer, будут напрямую влиять на растровое изображение. Давайте попробуем это с помощью простого преобразования яркости.

Public Sub Brightness(ByRef pixels() As Integer, ByVal scale As Single)
    Dim r, g, b As Integer
    Dim mult As Integer = CInt(1024.0f * scale)
    Dim pixel As Integer

    For i As Integer = 0 To pixels.Length - 1
        pixel = pixels(i)
        r = pixel And 255
        g = (pixel >> 8) And 255
        b = (pixel >> 16) And 255

        'brightness calculation
        'shift right by 10 <=> divide by 1024
        r = (r * mult) >> 10
        g = (g * mult) >> 10
        b = (b * mult) >> 10

        'clamp to between 0 and 255
        If r < 0 Then r = 0
        If g < 0 Then g = 0
        If b < 0 Then b = 0
        r = (r And 255)
        g = (g And 255)
        b = (b And 255)

        pixels(i) = r Or (g << 8) Or (b << 16) Or &HFF000000
    Next
End Sub

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

addr = IntPtr.Zero
If handle.IsAllocated Then
    handle.Free()
    handle = Nothing
End If
bitmap.Dispose()
bitmap = Nothing
pixels = Nothing

Я проигнорировал альфа-компонент здесь, но вы также можете использовать его. Таким образом, я собрал много инструментов для редактирования растровых изображений. Это намного быстрее и надежнее, чем Bitmap.LockBits () и, что самое главное, для начала редактирования вашего растрового изображения требуется нулевое копирование.

2 голосов
/ 01 марта 2013

Опираясь на ответ @notJim (и с помощью http://www.bobpowell.net/lockingbits.htm),, я разработал следующее, что значительно облегчает мою жизнь, поскольку я получаю массив массивов, который позволяет мне переходить на пиксель по его значению. Координаты x и y. Конечно, координату x необходимо скорректировать на количество байтов на пиксель, но это простое расширение.

Dim bitmapData As Imaging.BitmapData = myBitmap.LockBits(New Rectangle(0, 0, myBitmap.Width, myBitmap.Height), Imaging.ImageLockMode.ReadOnly, myBitmap.PixelFormat)

Dim size As Integer = Math.Abs(bitmapData.Stride) * bitmapData.Height
Dim data(size - 1) As Byte

Marshal.Copy(bitmapData.Scan0, data, 0, size)

Dim pixelArray(myBitmap.Height)() As Byte

'we have to load all the opacity pixels into an array for later scanning by column
'the data comes in rows
For y = myBitmap.Height - 1 To 0 Step -1
    Dim rowArray(bitmapData.Stride) As Byte
    Array.Copy(data, y * bitmapData.Stride, rowArray, 0, bitmapData.Stride)
    'For x = myBitmap.Width - 1 To 0 Step -1
    '   Dim i = (y * bitmapData.Stride) + (x * 4)
    '   Dim B = data(i)
    '   Dim G = data(i + 1)
    '   Dim R = data(i + 2)
    '   Dim A = data(i + 3)
    'Next
    pixelArray(y) = rowArray
Next
0 голосов
/ 08 сентября 2016

Попробуйте это решение C #.

Создайте приложение winforms для тестирования.

Добавьте кнопку и PictureBox, а также событие click и событие закрытия формы.

Используйте следующий код для вашей формы:

public partial class Form1 : Form
{
    uint[] _Pixels { get; set; }

    Bitmap _Bitmap { get; set; }

    GCHandle _Handle { get; set; }

    IntPtr _Addr { get; set; }


    public Form1()
    {
        InitializeComponent();

        int imageWidth = 100; //1920;

        int imageHeight = 100; // 1080;

        PixelFormat fmt = PixelFormat.Format32bppRgb;

        int pixelFormatSize = Image.GetPixelFormatSize(fmt);

        int stride = imageWidth * pixelFormatSize;

        int padding = 32 - (stride % 32);

        if (padding < 32)
        {
            stride += padding;
        }

        _Pixels = new uint[(stride / 32) * imageHeight + 1];

         _Handle = GCHandle.Alloc(_Pixels, GCHandleType.Pinned);

        _Addr = Marshal.UnsafeAddrOfPinnedArrayElement(_Pixels, 0);

        _Bitmap = new Bitmap(imageWidth, imageHeight, stride / 8, fmt, _Addr);

        pictureBox1.Image = _Bitmap;

    }

    private void button1_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < _Pixels.Length; i++)
        {
            _Pixels[i] = ((uint)(255 | (255 << 8) | (255 << 16) | 0xff000000));

        }

    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        _Addr = IntPtr.Zero;

        if (_Handle.IsAllocated)
        {
            _Handle.Free();

        }

        _Bitmap.Dispose();

        _Bitmap = null;

        _Pixels = null;

    }

}

Теперь любые изменения, внесенные в массив, автоматически обновляют растровое изображение.

Чтобы увидеть эти изменения, вам нужно вызвать метод refresh на картинке.

...