パフォーマンスについて話す前に、コードを確認しましょう。
var originalColor = scrBitmap.GetPixel(i, j);
if (originalColor = Color.Black)
newBitmap.SetPixel(i, j, Color.Red);
ここには 2 つのエラーがあります。
- と比較するのではなく
Color.Black
、に割り当て Color.Black
ますoriginalColor
。
- 透過性を処理しません。
透明度を確認するには、Color
オブジェクトではなく R、G、B の値を比較する必要があります。次のように変更しましょう。
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));
これで動作することがわかりますが、各画像を処理するのに非常に長い時間がかかり、GetPixel
かなりSetPixel
遅いです (主な理由は、呼び出しごとにすべてをチェックして計算するためです)。ビットマップ データを直接処理する方がはるかに優れています。画像形式が事前にわかっている場合 (そして画像ごとに固定されている場合) は、コードを少し追加するだけではるかに高速に実行できます。
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;
}
もちろん、これはさらに最適化することができ、さまざまな形式を処理する必要がある場合があります (ピクセル形式のこのリストとそのレイアウトに関するこの記事を参照してください) が、ビットマップを操作するための出発点と考えてください。
完全を期すために、これはビットマップ データに直接アクセスしない場合の同等の色です。これは非常に遅いため、めったに使用しないでください。
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;
}
また、比較の際にアルファ チャネルを考慮することに注意してください (たとえば、50% の透明な緑は 30% の透明な緑と同じ色ではありません)。アルファを無視するには、次のようなものを使用できます。
if (color.R == toReplace.R && color.G == toReplace.G && color.B == toReplace.B)
最後に、置換するピクセルが少ないことがわかっている場合は、元の画像の生のコピーを作成して (Graphics.FromImage
コンテキストを作成し、ビットマップに描画するために使用します)、置換がある場合にのみsource
呼び出すことができます。SetPixel()
IMO ここでの最適化はまったく役に立ちません。パフォーマンスが必要な場合は、最初のソリューションを使用してください...