C # более быстрый способ установки цвета пикселей - PullRequest
0 голосов
/ 15 мая 2018

Я выбрал проект, который создает карту шума, используя setPixel(). Большая часть времени выполнения этого приложения проводится в функции setPixel(), поэтому я стремился увеличить скорость выполнения функции.

Я провел некоторое исследование этого и обнаружил, что это:

int index = x + (y * Width);
int col = color.ToArgb();

if (this.Bits == null)
{
    this.Bits = new Int32[Width * Height];
}

Bits[index] = col;

рекомендовано как более быстрый подход. Однако это создает полностью черное изображение.

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

Вот исходный код человека, ранее реализованный:

            unsafe
            {
                var scan0 = (byte*)Iptr;
                int bitmapStride = Stride;
                int bitmapPixelFormatSize = Depth / 8;

                index = (bitmapStride * y) + (x * bitmapPixelFormatSize);
                if (bitmapPixelFormatSize == 4)
                {
                    scan0[index + 3] = color.A;
                    scan0[index + 2] = color.R;
                    scan0[index + 1] = color.G;
                    scan0[index] = color.B;

                }
                else if (bitmapPixelFormatSize == 1)
                {
                    scan0[index] = color.R;

                }
                else
                {
                    scan0[index + 2] = color.R;
                    scan0[index + 1] = color.G;
                    scan0[index] = color.B;

                }
            }

Iptr это просто IntPtr

Stride - это int, единственное место, где я могу найти этот набор, это Stride = (PixelCount/Height) * (Depth / 8)

x ширина

y это высота

Смогу ли я получить объяснение того, что происходит в исходном блоке кода, и, возможно, некоторую помощь в понимании того, как преобразовать это во что-то, что выполняется быстрее, в настоящее время требуется около 500 000 мс, чтобы завершить эту функцию из-за вложенный для петли ширины * высоты.

1 Ответ

0 голосов
/ 16 мая 2018

Примечание: Следующая информация была изначально создана Бобом Пауэллом.Исходная ссылка (больше не работает): http://bobpowell.net/lockingbits.htm.

Я скопировал эту информацию из Интернет-архива на https://web.archive.org/web/20120330012542/http://bobpowell.net/lockingbits.htm. Это немного долго, но я думаю, что это стоит сохранить.

Я не уверен, что это послужит прямым ответом на ваш вопрос, но, возможно, это поможет вам найти решение.


Использование метода LockBits для доступа к данным изображения

Многие задачи обработки изображений и даже преобразования типов файлов, скажем, от 32 бит на пиксель до 8 бит на пиксель, можно ускорить, обратившись непосредственно к массиву данных пикселей, вместо того чтобы полагаться наGetPixel и SetPixel или другие методы.

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

Прежде чем я начну с предмета, я просто напомню, что методы, используемые для доступа к любым неуправляемым данным, будут различаться в зависимости от языка, на котором написана ваша программа.Разработчики C # имеют возможность с помощью небезопасного ключевого слова и использования указателей получать прямой доступ к данным в памяти.Программисты Visual Basic должны получать доступ к таким данным через методы класса Marshal, которые также могут показывать небольшую потерю производительности.

Блокировка ваших битов

Класс Bitmap предоставляет LockBits исоответствующие методы UnlockBits, которые позволяют вам фиксировать часть массива данных пикселей растрового изображения в памяти, обращаться к нему напрямую и, наконец, заменять биты в растровом изображении измененными данными.LockBits возвращает класс BitmapData, который описывает расположение и положение данных в заблокированном массиве.

Класс BitmapData содержит следующие важные свойства:

  • Scan0 Адрес в памяти фиксированного массива данных
  • Stride Ширина в байтах отдельной строки данных пикселей.Эта ширина кратна или, возможно, кратна размерам в пикселях изображения и может быть дополнена, чтобы включать в себя еще несколько байтов.Я объясню почему в ближайшее время.
  • PixelFormat Фактический формат пикселей данных.Это важно для нахождения правильных байтов
  • Ширина Ширина заблокированного изображения
  • Высота Высота заблокированного изображения

Отношение Scan0 и Stride к массиву в памяти показано на рисунке 1.

enter image description here

Свойство Stride, как показано на рисунке1, содержит ширину одной строки в байтах.Размер строки, однако, может не быть точным кратным размеру пикселя, потому что для эффективности система гарантирует, что данные упакованы в строки, которые начинаются на четырехбайтовой границе и дополняются до кратности четырех байтов.Это означает, например, что изображение размером 24 бита на пиксель шириной 17 пикселей будет иметь шаг 52. Используемые данные в каждой строке будут занимать 3 * 17 = 51 байт, а заполнение 1 байтом расширит каждую строку до 52 байт или13 * 4 байта.Индексированное изображение 4Bpp шириной 17 пикселей будет иметь шаг 12. Девять байтов или, точнее, восемь с половиной, будут содержать данные, и строка будет дополнена еще 3 байтами до границы в 4 байта.

Часть переноса данных строки, как было предложено выше, размещается в соответствии с форматом пикселей.Изображение 24 бит на пиксель, содержащее данные RGB, будет иметь новый пиксель каждые 3 байта, 32 бит на пиксель RGBA каждые четыре байта.Пиксельные форматы, содержащие более одного пикселя на байт, такие как 4-битный индексированный пиксель и 1-битный индексированный пиксель, должны обрабатываться осторожно, чтобы требуемый пиксель не путался с соседними пикселями в одном байте.

Нахождение правильного байта.

Поскольку шаг - это ширина строки, для индексации любой строки или координаты Y вы можете умножить шаг на координату Y, чтобы получитьначало определенного ряда.Поиск правильного пикселя в строке, возможно, более сложен и зависит от знания расположения форматов пикселей.В следующих примерах показано, как получить доступ к определенному пикселю для данного формата пикселей.

  • Format32BppArgb При заданных координатах X и Y адрес первого элемента в пикселе - Scan0 + (у * шаг) + (х * 4).Это указывает на синий байт.Следующие три байта содержат зеленый, красный и альфа-байты.
  • Format24BppRgb Учитывая координаты X и Y, адрес первого элемента в пикселе - Scan0 + (y * Stride) + (х * 3).Это указывает на синий байт, за которым следуют зеленый и красный.
  • Format8BppIndexed Учитывая координаты X и Y, адрес байта является Scan0 + (y * Stride) + x.Этот байт является индексом в палитре изображений.
  • Format4BppIndexed При заданных координатах X и Y байт, содержащий данные пикселей, рассчитывается как Scan0 + (y * Stride) + (x / 2).Соответствующий байт содержит два пикселя, верхний полубайт - самый левый, а нижний полубайт - самый правый из двух пикселей.Четыре бита верхнего и нижнего полубайта используются для выбора цвета из цветовой палитры 16.
  • Format1BppIndexed Учитывая координаты X и Y, байт, содержащий пиксель, вычисляется с помощью Scan0 +(у * Страйд) + (х / 8).Байт содержит 8 битов, каждый бит - один пиксель с самым левым пикселем в бите 8 и самым правым пикселем в бите 0. Биты выбираются из цветовой палитры с двумя входами.

Итерация попиксели

Для форматов пикселей с одним или несколькими байтами на пиксель формула проста и может быть выполнена путем циклического перебора всех значений Y и X по порядку.Код в следующих листингах устанавливает для синего компонента 32-битного пиксельного изображения значение 255. В обоих случаях bm является ранее созданным растровым изображением.

BitmapData bmd=bm.LockBits(new Rectangle(0, 0, 10, 10), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat);

int PixelSize=4;

for(int y=0; y<bmd.Height; y++)
{
    byte* row = (byte *)bmd.Scan0+(y*bmd.Stride);
    for(int x = 0; x<bmd.Width; x++)
    {
        row[x * PixelSize] = 255;
    }
}

В VB эта операция будет обрабатываться немного по-другому, посколькуVB не знает указателей и требует использования класса маршала для доступа к неуправляемым данным.

Dim x As Integer
Dim y As Integer
Dim PixelSize As Integer = 4

Dim bmd As BitmapData = bm.LockBits(new Rectangle(0, 0, 10, 10), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat)

For y = 0 To bmd.Height - 1
    For x = 0 To bmd.Width - 1
        Marshal.WriteByte(bmd.Scan0, (bmd.Stride * y) + (4 * x) , 255)
    Next
Next

Подбайтовые пиксели.

Пиксель Format4BppIndexed и Format1BppIndexedформаты, упомянутые ранее, оба имеют более одного пикселя, сохраненного в байте.В таких случаях вам необходимо убедиться, что изменение данных для одного пикселя не повлияет на другой пиксель или пиксели, содержащиеся в этом байте.

Метод индексации изображения размером 1 бит на пиксель основан на использовании побитовогологические операции And и Or для сброса или установки определенных битов в байте.После использования формулы, показанной выше для изображений с 1 битом на пиксель, младшие 3 бита координаты X используются для выбора необходимого бита.Перечисления ниже показывают этот процесс в C # и VB.В обоих примерах bmd - это растровые данные, извлеченные из изображения размером 1 бит на пиксель.

Код C # использует указатели и требует компиляции с небезопасным кодом

byte* p=(byte*)bmd.Scan0.ToPointer();

int index=y*bmd.Stride+(x>>3);

byte mask=(byte)(0x80>>(x&0x7));

if(pixel)
    p[index]|=mask;
else
    p[index]&=(byte)(mask^0xff);

В коде VB используется класс маршала

Dim mask As Byte = 128 >> (x And 7)

Dim offset As Integer = (y * bmd.Stride) + (x >> 3)

Dim currentPixel As Byte = Marshal.ReadByte(bmd.Scan0, offset)

If pixel = True Then
    Marshal.WriteByte(bmd.Scan0, offset, currentPixel Or mask)
Else
    Marshal.WriteByte(bmd.Scan0, offset, CByte(currentPixel And (mask Xor 255)))
End If

Обратите внимание, вполне допустимо использовать класс Marshal из кода C #.Я использовал указатели, потому что они предлагают лучшую производительность.

Доступ к отдельным пикселям в изображении размером 4 бита на пиксель обрабатывается аналогичным образом.Верхний и нижний полубайты байта должны рассматриваться отдельно, а изменение содержимого нечетных X пикселей не должно влиять на четные X пикселей.Код ниже показывает, как выполнить это в C # и VB.

C #

int offset = (y * bmd.Stride) + (x >> 1);

byte currentByte = ((byte *)bmd.Scan0)[offset];

if((x&1) == 1)
{
    currentByte &= 0xF0;
    currentByte |= (byte)(colorIndex & 0x0F);
}
else
{
    currentByte &= 0x0F;
    currentByte |= (byte)(colorIndex << 4);
}

((byte *)bmd.Scan0)[offset]=currentByte;

VB

Dim offset As Integer = (y * bmd.Stride) + (x >> 1)

Dim currentByte As Byte = Marshal.ReadByte(bmd.Scan0, offset)

If (x And 1) = 1 Then
    currentByte = currentByte And &HF0
    currentByte = currentByte Or (colorIndex And &HF)
Else
    currentByte = currentByte And &HF
    currentByte = currentByte Or (colorIndex << 4)
End If

Marshal.WriteByte(bmd.Scan0, offset, currentByte)

Использование LockBits и UnlockBits

Метод LockBits принимает прямоугольник, который может быть того же размера или меньше, чем обрабатываемое изображение, PixelFormat, который обычно такой же, как уобрабатываемое изображение и значение ImageLockMode, указывающее, являются ли данные доступными только для чтения, только для записи, для чтения и записи или выделенным пользователем буфером.Этот последний параметр нельзя использовать из C # или VB, потому что перегрузка метода для LockBits, указывающая пользовательский буфер, не включена в управляемую оболочку GDI +.

Очень важно, чтобы по завершении всех операций помещалась BitmapDataвернуться в растровое изображение с помощью метода UnlockBits.Фрагмент кода ниже иллюстрирует это.

Dim bmd As BitmapData = bm.LockBits(New Rectangle(0, 0, 10, 10), ImageLockMode.ReadWrite, bm.PixelFormat)

    ' do operations here

bm.UnlockBits(bmd)

Сводка

Это почти охватывает аспекты доступа к самым популярным и сложным пиксельным форматам напрямую.Использование этих методов вместо методов GetPixel и SetPixel, предоставляемых Bitmap, покажет заметное повышение производительности для ваших процедур обработки изображений и преобразования форматов изображений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...