LoginSignup
4
3

More than 3 years have passed since last update.

WPFにおける画像のピクセル値への高速アクセス

Last updated at Posted at 2019-09-24

1.ポインタを用いる

C#でもunsafeの範囲ではC言語同様ポインタを使える.
コード内にunsafe修飾子をつける.
/unsafe+ オプションをつけてコンパイルする必要がある.
###2.System.IO.MemoryStreamを用いる
System.IO.MemoryStream を使って読み込むとファイルのロックが任意のタイミングで解除できる.
new BitmapImage(new Uri(f))を使うとファイルがロックされてしまい,その後上書き・削除などができなくなることがままある.//必ず発生するわけではないことが厄介

サンプルの解説

AccessPixels(...)各ピクセルにアクセスするメソッド
in string fは読み込みたい画像ファイル(jpg,png可)のパス
int offset=(bitmap.PixelWidth*y+x)*4;はポインタの位置を示している.y,xに任意の座標を指定すればいい.
SaveBitmapSourceToFile(...)//変更データをbitmap形式でファイルに上書き保存.

    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.IO;

    private static unsafe void AccessPixels(in string f){
        System.IO.MemoryStream data = new System.IO.MemoryStream(File.ReadAllBytes(f));
        WriteableBitmap wbmp = new WriteableBitmap(BitmapFrame.Create(data));
        data.Close();//MemoryStreamを閉じてロックを解除
        FormatConvertedBitmap bitmap = new FormatConvertedBitmap(wbmp, PixelFormats.Pbgra32, null, 0);//32bit で読む
        byte[] originalPixels = new byte[bitmap.PixelWidth * bitmap.PixelHeight * 4];//αの値も読みたいのでbgrαの4種 4*8bit=32bit
        int stride = (bitmap.PixelWidth * bitmap.Format.BitsPerPixel + 7) / 8;
        bitmap.CopyPixels(originalPixels, stride, 0);

        for (int y = 0; y < bitmap.PixelHeight; ++y)
            for (int x = 0; x < bitmap.PixelWidth; ++x) {
                int offset = (bitmap.PixelWidth * y + x) * 4;
                if(originalPixels[0 + offset] >=128) 
                     originalPixels[0 + offset]=255;//b
                else originalPixels[0 + offset]=0;//b

                if(originalPixels[1 + offset] >=128) 
                     originalPixels[1 + offset]=255;//g
                else originalPixels[1 + offset]=0;//g
        }
        BitmapSource originalBitmap = BitmapSource.Create(bitmap.PixelWidth,bitmap.PixelHeight, 96, 96, PixelFormats.Pbgra32, null, originalPixels, stride);//変更データ(配列)をBitmapSourceに変換.
        SaveBitmapSourceToFile(originalBitmap,f+".bmp")//変更データをファイルに保存.
        //SaveAsPng(originalBitmap,f+".png")//変更データをpngで保存.
    }

    private void SaveBitmapSourceToFile(BitmapSource bitmapSource, string filePath){
        using (var fileStream = new FileStream(filePath, FileMode.Create)){
            BitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
            encoder.Save(fileStream);
        }
    }
    public void SaveAsPng(BitmapSource bitmapSource,string pngPath){
        using (var fileStream = new FileStream(pngPath, FileMode.Create)){
            var encoder = new PngBitmapEncoder();
            encoder.Interlace = PngInterlaceOption.On;
            encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
            encoder.Save(fileStream);
        }
    }

参考サイト
//ファイルをロックしないパターン
http://neareal.net/index.php?Programming%2F.NetFramework%2FWPF%2FWriteableBitmap%2FLoadReleaseableBitmapImage
// BitmapSourceから配列にコピー
https://water2litter.net/gin/?p=990
https://imagingsolution.net/program/csharp/bitmap-data-memory-format/
//BitmapSourceクラスをbitmapとして保存する
http://ni4muraano.hatenablog.com/entry/2017/10/13/080000
//BitmapSourceクラスをpngとして保存する
http://ni4muraano.hatenablog.com/entry/2018/01/29/080000

可読性が落ちるがもっと速くしたい場合,for以下を変更する

       for (int i = 0; i < originalPixels.Length;){
            if(originalPixels[i] >=128) 
                 originalPixels[i++]=255;//b
            else originalPixels[i++]=0;//b

            if(originalPixels[i] >=128) 
                 originalPixels[i++]=255;//g
            else originalPixels[i++]=0;//g

            originalPixels[i++]=originalPixels[i] >=128 ?255:0;//r

            originalPixels[i++]=255;//α
    }
4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3