System.Drawing.Bitmapを操作する

System.Drawing.Bitmapクラスは、.NETフレームワークで非常に便利です。 さまざまなグラフィック形式のファイルを読み込んで保存できます。 唯一の問題は、ビットマップをb / wに変換する必要がある場合など、ピクセル処理にはあまり役に立たないことです。 カットの下-このテーマの小さなスケッチ。

2つのビットマップがあり、1つはファイルから読み取られ、もう1つはb / w変換を含む必要があるとします。
//
sourceBitmap = (Bitmap) Image.FromFile( "Zap.png" );<br/>
//
targetBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height, sourceBitmap.PixelFormat);<br/>
targetBitmapように、白黒のみにtargetBitmap必要があります。 実際、C#ではこれは単純に行われます。

void NaïveBlackAndWhite()<br/>
{<br/>
for ( int y = 0; y < sourceBitmap.Height; ++y)<br/>
for ( int x = 0; x < sourceBitmap.Width; ++x)<br/>
{<br/>
Color c = sourceBitmap.GetPixel(x, y);<br/>
byte rgb = ( byte )(0.3 * cR + 0.59 * cG + 0.11 * cB);<br/>
targetBitmap.SetPixel(x, y, Color.FromArgb(cA, rgb, rgb, rgb));<br/>
}<br/>
}<br/>
この解決策は明確でシンプルですが、残念ながら効果的ではない恐怖です。 より高速なコードを取得するには、このすべてをC ++で記述してみてください。 まず、ピクセルの色の値を保存するための構造を作成します

// 32bpp RGBA
struct Pixel {<br/>
BYTE Blue;<br/>
BYTE Green;<br/>
BYTE Red;<br/>
BYTE Alpha;<br/>
};<br/>
これで、ピクセルを白黒にする関数を作成できます。

Pixel MakeGrayscale(Pixel& pixel)<br/>
{<br/>
const BYTE scale = static_cast < BYTE >(0.3 * pixel.Red + 0.59 * pixel.Green + 0.11 * pixel.Blue);<br/>
Pixel p;<br/>
p.Red = p.Green = p.Blue = scale;<br/>
p.Alpha = pixel.Alpha;<br/>
return p;<br/>
}<br/>
次に、バイパス関数自体を実際に作成します。

CPPSIMDLIBRARY_API void AlterBitmap( BYTE * src, BYTE * dst, int width, int height, int stride)<br/>
{<br/>
for ( int y = 0; y < height; ++y) {<br/>
for ( int x = 0; x < width; ++x)<br/>
{<br/>
int offset = x * sizeof (Pixel) + y * stride;<br/>
Pixel& s = * reinterpret_cast <Pixel*>(src + offset);<br/>
Pixel& d = * reinterpret_cast <Pixel*>(dst + offset);<br/>
// d
d = MakeGrayscale(s);<br/>
}<br/>
}<br/>
}<br/>
そして、C#から使用するだけです。

void UnmanagedBlackAndWhite()<br/>
{<br/>
// ""
Rectangle rect = new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height);<br/>
BitmapData srcData = sourceBitmap.LockBits(rect, ImageLockMode.ReadWrite, sourceBitmap.PixelFormat);<br/>
BitmapData dstData = targetBitmap.LockBits(rect, ImageLockMode.ReadWrite, sourceBitmap.PixelFormat);<br/>
// unmanaged
AlterBitmap(srcData.Scan0, dstData.Scan0, srcData.Width, srcData.Height, srcData.Stride);<br/>
//
sourceBitmap.UnlockBits(srcData);<br/>
targetBitmap.UnlockBits(dstData);<br/>
}<br/>
これによりパフォーマンスは向上しましたが、さらに必要でした。 yループの前にOpenMPディレクティブを追加すると、予測可能な2倍の加速が得られました。 それから、SIMDを試してみてください。 これを行うために、非常に読みにくい次のコードを書きました。

CPPSIMDLIBRARY_API void AlterBitmap( BYTE * src, BYTE * dst, int width, int height, int stride)<br/>
{<br/>
// /
static __m128 factor = _mm_set_ps(1.0f, 0.3f, 0.59f, 0.11f);<br/>
#pragma omp parallel for <br/>
for ( int y = 0; y < height; ++y)<br/>
{<br/>
const int offset = y * stride;<br/>
__m128i * s = ( __m128i *)(src + offset);<br/>
__m128i * d = ( __m128i *)(dst + offset);<br/>
for ( int x = 0; x < (width >> 2); ++x) {<br/>
// 4
for ( int p = 0; p < 4; ++p)<br/>
{<br/>
//
__m128 pixel;<br/>
pixel.m128_f32[0] = s->m128i_u8[(p<<2)];<br/>
pixel.m128_f32[1] = s->m128i_u8[(p<<2)+1];<br/>
pixel.m128_f32[2] = s->m128i_u8[(p<<2)+2];<br/>
pixel.m128_f32[3] = s->m128i_u8[(p<<2)+3];<br/>
// - !
pixel = _mm_mul_ps(pixel, factor);<br/>
//
const BYTE sum = ( BYTE )(pixel.m128_f32[0] + pixel.m128_f32[1] + pixel.m128_f32[2]);<br/>
//
d->m128i_u8[p<<2] = d->m128i_u8[(p<<2)+1] = d->m128i_u8[(p<<2)+2] = sum;<br/>
d->m128i_u8[(p<<2)+3] = ( BYTE )pixel.m128_f32[3];<br/>
}<br/>
s++;<br/>
d++;<br/>
}<br/>
}<br/>
}<br/>
このコードは一度に4つの乗算演算(命令_mm_mul_ps )を行うという事実にもかかわらず、これらの変換はすべて通常の演算と比較して利得を与えませんでした-むしろ、逆にアルゴリズムの動作が遅くなりました。 以下は、画像360×480の関数の結果です。 4GB RAMの2コアMacBookを使用しましたが、結果は平均です。




最終結果は次のとおりです。




結論:


読者のいずれかがさらに効率的なアルゴリズムを作成する準備ができている場合-ようこそ! ここでテストして公開します。

Source: https://habr.com/ru/post/J60085/


All Articles