Ответ 1
Немедленно применимый код
public class DirectBitmap : IDisposable
{
public Bitmap Bitmap { get; private set; }
public Int32[] Bits { get; private set; }
public bool Disposed { get; private set; }
public int Height { get; private set; }
public int Width { get; private set; }
protected GCHandle BitsHandle { get; private set; }
public DirectBitmap(int width, int height)
{
Width = width;
Height = height;
Bits = new Int32[width * height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
}
public void SetPixel(int x, int y, Color colour)
{
int index = x + (y * Width);
int col = colour.ToArgb();
Bits[index] = col;
}
public Color GetPixel(int x, int y)
{
int index = x + (y * Width);
int col = Bits[index];
Color result = Color.FromArgb(col);
return result;
}
public void Dispose()
{
if (Disposed) return;
Disposed = true;
Bitmap.Dispose();
BitsHandle.Free();
}
}
Там нет необходимости в LockBits
или SetPixel
. Используйте вышеуказанный класс для прямого доступа к растровым данным.
С помощью этого класса можно установить необработанные битовые данные как 32-битные данные. Обратите внимание, что это PARGB, который предварительно умножается на альфа. См. Alpha Compositing в Википедии для получения дополнительной информации о том, как это работает, и примеры в статье MSDN для BLENDFUNCTION, чтобы узнать, как правильно рассчитать альфа.
Если премультипликация может чрезмерно усложнить ситуацию, вместо этого используйте PixelFormat.Format32bppArgb
. PixelFormat.Format32bppPArgb
производительности происходит, когда он нарисован, потому что он внутренне преобразуется в PixelFormat.Format32bppPArgb
. Если изображение не нужно менять перед тем, как его нарисовать, работу можно выполнить до начала умножения, нарисованного в буфере PixelFormat.Format32bppArgb
и далее используемого оттуда.
Доступ к стандартным элементам Bitmap
открыт через свойство Bitmap
. Прямой доступ к данным битмапа осуществляется с помощью свойства Bits
.
Использование byte
вместо int
для необработанных пиксельных данных
Измените оба экземпляра Int32
на byte
, а затем измените эту строку:
Bits = new Int32[width * height];
К этому:
Bits = new byte[width * height * 4];
Когда используются байты, в этом порядке используется формат Alpha/Red/Green/Blue. Каждый пиксель занимает 4 байта данных, по одному для каждого канала. Функции GetPixel и SetPixel должны быть соответствующим образом переработаны или удалены.
Преимущества использования вышеуказанного класса
- Распределение памяти для простого управления данными не требуется; изменения, внесенные в необработанные данные, немедленно применяются к растровому изображению.
- Нет никаких дополнительных объектов для управления. Это реализует
IDisposable
же, какBitmap
. - Он не требует
unsafe
блока.
Соображения
- Прикрепленная память не может быть перемещена. Это необходимый побочный эффект для того, чтобы этот вид доступа к памяти работал. Это снижает эффективность сборщика мусора (статья MSDN). Делайте это только с растровыми изображениями, в которых требуется производительность, и убедитесь, что вы
Dispose
их, когда вы закончите, чтобы память могла быть отключена.
Доступ через объект Graphics
Поскольку свойство Bitmap
самом деле является объектом.NET Bitmap
, он просто выполняет операции с использованием класса Graphics
.
var dbm = new DirectBitmap(200, 200);
using (var g = Graphics.FromImage(dbm.Bitmap))
{
g.DrawRectangle(Pens.Black, new Rectangle(50, 50, 100, 100));
}
Сравнение производительности
Вопрос задает вопрос о производительности, поэтому вот таблица, которая должна показывать относительную производительность между тремя различными методами, предложенными в ответах. Это было сделано с использованием приложения на основе стандарта.NET Standard 2 и NUnit.
* Time to fill the entire bitmap with red pixels *
- Not including the time to create and dispose the bitmap
- Best out of 100 runs taken
- Lower is better
- Time is measured in Stopwatch ticks to emphasize magnitude rather than actual time elapsed
- Tests were performed on an Intel Core i7-4790 based workstation
Bitmap size
Method 4x4 16x16 64x64 256x256 1024x1024 4096x4096
DirectBitmap <1 2 28 668 8219 178639
LockBits 2 3 33 670 9612 197115
SetPixel 45 371 5920 97477 1563171 25811013
* Test details *
- LockBits test: Bitmap.LockBits is only called once and the benchmark
includes Bitmap.UnlockBits. It is expected that this
is the absolute best case, adding more lock/unlock calls
will increase the time required to complete the operation.