Przed mówimy o perfromance niech sprawdzić kod:
var originalColor = scrBitmap.GetPixel(i, j);
if (originalColor = Color.Black)
newBitmap.SetPixel(i, j, Color.Red);
Tutaj są dwa błędy:
- Nie porównać do
Color.Black
ale przypisaćColor.Black
do originalColor
.
- Nie obsługuje przezroczystości.
Aby sprawdzić przejrzystości należy porównać nie przedmiot Color
ale R, G, wartości B, zmieńmy do:
var originalColor = scrBitmap.GetPixel(i, j);
if (originalColor.R == 0 && originalColor.G == 0 && originalColor.B == 0)
newBitmap.SetPixel(i, j, Color.FromArgb(originalColor.A, Color.Red));
Teraz zobaczysz, że to działa, ale to trwa bardzo długi czas przetwarzania każdego obrazu: GetPixel
i SetPixel
są dość powolne (podstawowe, ponieważ sprawdzają i obliczają wszystko dla każdego połączenia). O wiele lepiej jest bezpośrednio obsługiwać dane bitmapowe. Jeśli znasz format obrazu z góry (i to ustalone dla każdego obrazu), to można to zrobić o wiele szybciej z trochę więcej kodu:
static unsafe Bitmap ReplaceColor(Bitmap source,
Color toReplace,
Color replacement)
{
const int pixelSize = 4; // 32 bits per pixel
Bitmap target = new Bitmap(
source.Width,
source.Height,
PixelFormat.Format32bppArgb);
BitmapData sourceData = null, targetData = null;
try
{
sourceData = source.LockBits(
new Rectangle(0, 0, source.Width, source.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
targetData = target.LockBits(
new Rectangle(0, 0, target.Width, target.Height),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
for (int y = 0; y < source.Height; ++y)
{
byte* sourceRow = (byte*)sourceData.Scan0 + (y * sourceData.Stride);
byte* targetRow = (byte*)targetData.Scan0 + (y * targetData.Stride);
for (int x = 0; x < source.Width; ++x)
{
byte b = sourceRow[x * pixelSize + 0];
byte g = sourceRow[x * pixelSize + 1];
byte r = sourceRow[x * pixelSize + 2];
byte a = sourceRow[x * pixelSize + 3];
if (toReplace.R == r && toReplace.G == g && toReplace.B == b)
{
r = replacement.R;
g = replacement.G;
b = replacement.B;
}
targetRow[x * pixelSize + 0] = b;
targetRow[x * pixelSize + 1] = g;
targetRow[x * pixelSize + 2] = r;
targetRow[x * pixelSize + 3] = a;
}
}
}
finally
{
if (sourceData != null)
source.UnlockBits(sourceData);
if (targetData != null)
target.UnlockBits(targetData);
}
return target;
}
Oczywiście może to być further optimized i może trzeba obsługiwać różne formaty (see this list of pixel formats i this article dotyczące ich układu), ale uważają go za punkt wyjścia do pracy z bitmapami.
Dla kompletności jest to odpowiednik koloru bez bezpośredniego dostępu do danych bitmapowych. Pamiętaj, że powinno to być rzadko używane, ponieważ jest bardzo powolne.
static Bitmap ReplaceColor(Bitmap source,
Color toReplace,
Color replacement)
{
var target = new Bitmap(source.Width, source.Height);
for (int x = 0; x < source.Width; ++x)
{
for (int y = 0; y < source.Height; ++y)
{
var color = source.GetPixel(x, y);
target.SetPixel(x, y, color == toReplace ? replacement : color);
}
}
return target;
}
Również należy pamiętać, że to rozważyć kanał alfa w porównaniu (tak 50% przezroczysty zielony, na przykład, nie jest taki sam kolor jak 30% przezroczysty zielony). Zignorować alfa można użyć mniej więcej tak:
if (color.R == toReplace.R && color.G == toReplace.G && color.B == toReplace.B)
Wreszcie, jeśli wiesz, że pikseli do zastąpienia są mało można stworzyć surową kopię oryginalnego obrazu (przy użyciu Graphics.FromImage
stworzyć kontekst i wyciągnąć do niego source
bitmapę), w ten sposób zadzwonisz pod numer SetPixel()
tylko wtedy, gdy nastąpi wymiana. IMO optymalizacja tutaj jest całkiem bezużyteczna: jeśli potrzebujesz wydajności, skorzystaj z pierwszego rozwiązania ...
To jest najlepsza odpowiedź i jestem pewien, że moje wymaganie jest spełnione, dzięki DareDevil –
To zamieni każdy kolor na nowy (nie tylko na wybrany) i sprawdzi, czy alfa wygeneruje nieoptymalne wyniki, jeśli jest gradient, ale jeśli spełnia OP ... :) –
również ten kod jest w porządku dla obrazy jednego koloru. – DareDevil