Недавно я написал проект, в котором я делал манипуляции с цветом по пикселям на пиксель. Он должен был работать быстро, так как он будет обновляться, пока вы перемещаете курсор мыши.
Я начал с небезопасного кода, но мне не нравится небезопасный код, и поэтому я перешел на безопасную территорию, и когда я это сделал, у меня возникли проблемы со скоростью, но разрешение не меняло условную логику. Он разрабатывал лучшие алгоритмы для манипулирования пикселями.
Я дам вам обзор того, что я сделал, и я надеюсь, что он поможет вам стать тем, кем вы хотите быть, потому что это действительно близко.
Первое: у меня было несколько возможных форматов ввода пикселей. Из-за этого я не мог предположить, что байты RGB были в определенных смещениях или даже статической ширине. Таким образом, я читаю информацию из переданного изображения и возвращаю «цвет», который представляет размеры каждого поля:
private System.Drawing.Color GetOffsets(System.Drawing.Imaging.PixelFormat PixelFormat)
{
//Alpha contains bytes per color,
// R contains R offset in bytes
// G contains G offset in bytes
// B contains B offset in bytes
switch(PixelFormat)
{
case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
return System.Drawing.Color.FromArgb(3, 0, 1, 2);
case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
case System.Drawing.Imaging.PixelFormat.Format32bppPArgb:
return System.Drawing.Color.FromArgb(4, 1, 2, 3);
case System.Drawing.Imaging.PixelFormat.Format32bppRgb:
return System.Drawing.Color.FromArgb(4, 0, 1, 2);
case System.Drawing.Imaging.PixelFormat.Format8bppIndexed:
return System.Drawing.Color.White;
default:
return System.Drawing.Color.White;
}
}
Например, предположим, что источником является 24-битное RGB-изображение. Я не хотел менять альфа-значения, так как собираюсь смешать с ним цвет.
Таким образом, R со смещением 0, B со смещением 1 и G со смещением 2, и каждый пиксель имеет ширину в три бита. Это я создаю временный цвет с этими данными.
Далее, поскольку это пользовательский элемент управления, я не хотел мерцания, поэтому я переопределил OnPaintBackground и отключил его:
protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs pevent)
{
//base.OnPaintBackground(pevent);
}
Наконец, и вот часть, которая доходит до сути того, что вы делаете, я рисую новое изображение на каждом OnPaint (которое вызывается при перемещении мыши, потому что я «аннулирую» его в обработчике события перемещения мыши)
Полный код - прежде чем я вызову определенные разделы ...
protected override void OnPaint(System.Windows.Forms.PaintEventArgs pe)
{
base.OnPaint(pe);
pe.Graphics.FillRectangle(new System.Drawing.SolidBrush(this.BackColor), pe.ClipRectangle);
System.Drawing.Rectangle DestinationRect = GetDestinationRectangle(pe.ClipRectangle);
if(DestinationRect != System.Drawing.Rectangle.Empty)
{
System.Drawing.Image BlendedImage = (System.Drawing.Image) this.Image.Clone();
if(HighlightRegion != System.Drawing.Rectangle.Empty && this.Image != null)
{
System.Drawing.Rectangle OffsetHighlightRegion =
new System.Drawing.Rectangle(
new System.Drawing.Point(
Math.Min(Math.Max(HighlightRegion.X + OffsetX, 0), BlendedImage.Width - HighlightRegion.Width -1),
Math.Min(Math.Max(HighlightRegion.Y + OffsetY, 0), BlendedImage.Height - HighlightRegion.Height -1)
)
, HighlightRegion.Size
);
System.Drawing.Bitmap BlendedBitmap = (System.Drawing.Bitmap) BlendedImage;
System.Drawing.Color OffsetRGB = GetOffsets(BlendedImage.PixelFormat);
byte BlendR = SelectionColor.R;
byte BlendG = SelectionColor.G;
byte BlendB = SelectionColor.B;
byte BlendBorderR = SelectionBorderColor.R;
byte BlendBorderG = SelectionBorderColor.G;
byte BlendBorderB = SelectionBorderColor.B;
if(OffsetRGB != System.Drawing.Color.White) //White means not supported
{
int BitWidth = OffsetRGB.G - OffsetRGB.R;
System.Drawing.Imaging.BitmapData BlendedData = BlendedBitmap.LockBits(new System.Drawing.Rectangle(0, 0, BlendedBitmap.Width, BlendedBitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, BlendedBitmap.PixelFormat);
int StrideWidth = BlendedData.Stride;
int BytesPerColor = OffsetRGB.A;
int ROffset = BytesPerColor - (OffsetRGB.R + 1);
int GOffset = BytesPerColor - (OffsetRGB.G + 1);
int BOffset = BytesPerColor - (OffsetRGB.B + 1);
byte[] BlendedBytes = new byte[Math.Abs(StrideWidth) * BlendedData.Height];
System.Runtime.InteropServices.Marshal.Copy(BlendedData.Scan0, BlendedBytes, 0, BlendedBytes.Length);
//Create Highlighted Region
for(int Row = OffsetHighlightRegion.Top ; Row <= OffsetHighlightRegion.Bottom ; Row++)
{
for(int Column = OffsetHighlightRegion.Left ; Column <= OffsetHighlightRegion.Right ; Column++)
{
int Offset = Row * StrideWidth + Column * BytesPerColor;
if(Row == OffsetHighlightRegion.Top || Row == OffsetHighlightRegion.Bottom || Column == OffsetHighlightRegion.Left || Column == OffsetHighlightRegion.Right)
{
BlendedBytes[Offset + ROffset] = BlendBorderR;
BlendedBytes[Offset + GOffset] = BlendBorderG;
BlendedBytes[Offset + BOffset] = BlendBorderB;
}
else
{
BlendedBytes[Offset + ROffset] = (byte) ((BlendedBytes[Offset + ROffset] + BlendR) >> 1);
BlendedBytes[Offset + GOffset] = (byte) ((BlendedBytes[Offset + GOffset] + BlendG) >> 1);
BlendedBytes[Offset + BOffset] = (byte) ((BlendedBytes[Offset + BOffset] + BlendB) >> 1);
}
}
}
System.Runtime.InteropServices.Marshal.Copy(BlendedBytes, 0, BlendedData.Scan0, BlendedBytes.Length);
BlendedBitmap.UnlockBits(BlendedData);
//base.Image = (System.Drawing.Image) BlendedBitmap;
}
}
pe.Graphics.DrawImage(BlendedImage, 0, 0, DestinationRect, System.Drawing.GraphicsUnit.Pixel);
}
}
Ниже приведено несколько объяснений кода ...
System.Drawing.Image BlendedImage = (System.Drawing.Image) this.Image.Clone();
Важно рисовать закадровое изображение - это создает одно такое изображение. В противном случае рисунок будет намного медленнее.
if(HighlightRegion != System.Drawing.Rectangle.Empty && this.Image != null)
HighlightRegion - это RECT, который удерживает область, которую нужно «пометить» на исходном изображении. Я использовал это для разметки областей изображения размером 4 миллиона пикселей, и он все еще работает достаточно быстро, чтобы быть в «реальном времени»
Некоторый код ниже используется, потому что пользователь может прокручивать изображение вверх или вниз, поэтому я изменяю пункт назначения по количеству прокрутки.
Ниже я приведу ИЗОБРАЖЕНИЕ к BITMAP и получу ранее упомянутую информацию о Цвете, которую мне нужно будет начать использовать сейчас. В зависимости от того, что вы делаете, вы можете захотеть кэшировать это вместо того, чтобы получать его каждый раз.
System.Drawing.Bitmap BlendedBitmap = (System.Drawing.Bitmap) BlendedImage;
На моем элементе управления я выставил два свойства Color - SelectionColor и SelectionBorderColor - так, чтобы мои регионы все еще имели приятную границу с ними. Часть моей оптимизации скорости состояла в том, чтобы предварительно преобразовать их в байты, так как я буду делать побитовые операции через мгновение.
Вы увидите комментарий в коде «Белый не поддерживается» - в этом случае «Белый» - это «Поддельный цвет», который мы используем для хранения нашей битовой ширины. Я использовал «Белый» для обозначения «Я не могу оперировать этими данными»
Следующая строка устанавливает, что на самом деле каждый цвет является одним битом, потому что они могут не зависеть от нашего целевого формата цвета путем вычитания смещения R и G. Обратите внимание, что если вы не можете гарантировать, что ваш G следует вашему R, вам нужно будет использовать что-то еще. В моем случае это было гарантировано.
Теперь, где начинается та часть, которую вы действительно ищете. Я использую LockBits для получения битовых данных. После этого я использую данные, чтобы завершить настройку некоторых переменных перед циклом.
А потом я копирую данные в байтовый массив. Я собираюсь перебрать этот байтовый массив, изменить значения и затем скопировать его данные обратно в BITMAP. Я работал над BITMAP непосредственно перед тем, как подумать, что, поскольку он вне экрана, он будет таким же быстрым, как и работа с собственным массивом.
Я был не прав. Профилирование производительности доказало это мне. Это быстрее, чтобы скопировать все в байтовый массив и работать в этом.
Теперь цикл начинается. Это идет строка за строкой, столбец за столбцом. Смещение - это число, указывающее нам, где в байтовом массиве мы находимся с точки зрения «текущего пикселя».
Затем я смешиваю 50% или рисую границу. Обратите внимание, что для каждого пикселя у меня есть не только оператор IF, но и ИЛИ.
И все так же быстро, как пылает.
Наконец, я копирую обратно и разблокирую биты. А затем скопируйте изображение на экранную поверхность.