C# で低レベル画像転送

C# には Bitmap クラスが用意されているのだけれども、ちょっとばかしアルファブレンドだの加算合成だの乗算合成だのを比較的高速に処理する必要が出てきたので、メモリを直接弄ってごにょごにょしてみることにしました。
が、しかしこれが意外に頭を悩ませてくれます。


まず画像のロードですが、これは簡単です。

Bitmap bitmap = new Bitmap(filename);
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
    ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

this.pixels = new byte[data.Stride * data.Height];
Marshal.Copy(data.Scan0, this.pixels, 0, this.pixels.Length);

bitmap.UnlockBits(data);

これでイメージのデータが ARGB8888 の形式で取れるので、あとは pixels を適当に弄ればイメージの操作が可能になります。


で、次は、これを描画する方法。
DirectXOpenGL を使おうとは思っていないので、Graphics.DrawImage() を使って画面に描画することになります。
Graphics.DrawImage() で使用するイメージは Image クラスなので、Bitmap から取得した pixels を、再び Bitmap(Image)クラスに変換してやる必要があります。
これは、

Bitmap(int width, int height, int stride, PixelFormat format, IntPtr scan0);

こいつを使うと、scan0 をそのまま使って転送してくれるので、コピーが発生しないため、比較的高速に Bitmap が生成できます。
ただ、この scan0 は IntPtr なので、byte[] である pixels を、IntPtr に変換してやる必要があります。
Marshal.UnsafeAddrOfPinnedArrayElement を使えばそれは可能ですが、

IntPtr scan0 = Marshal.UnsafeAddrOfPinnedArrayElement(pixels, 0)

これだけだと scan0 は、ガーベジコレクタによってコンパクションが行われてしまう可能性があるので、これが有効である保証はありません。
なので、id:yaneurao:20060424 の方法を使って、一時的にコンパクションを回避しておきます。
自分だったら外部から生成できないようにコンストラクタは internal にして、Dispose() のし忘れや複数回 Dispose() されるのも考慮に入れて書くなぁ、ってことでちょっと改変。

class UnsafePointer : IDisposable
{
    private GCHandle handle;
    private IntPtr ptr;

    internal UnsafePointer(GCHandle handle, IntPtr ptr)
    {
        this.handle = handle;
        this.ptr = ptr;
    }

    ~UnsafePointer()
    {
        Dispose();
    }

    public IntPtr IntPtr
    {
        get { return ptr; }
    }

    #region IDisposable メンバ

    public void Dispose()
    {
        lock (this)
        {
            if (this.handle.IsAllocated)
            {
                this.handle.Free();
            }
        }
    }

    #endregion
}

で、こいつを使って Bitmap を生成して画面に描画します。

public void DrawBySystemGraphics(Graphics graphics, Point dstPoint)
{
    using (UnsafePointer ptr = Converter.ArrayToUnsafePointer(pixels, 0))
    {
        Bitmap bitmap = new Bitmap(this.Width, this.Height, this.Stride, this.PixelFormat, ptr.IntPtr);
        graphics.DrawImage(bitmap, dstPoint);
    }
}

以上。


で、今自分が頭を悩ませてるのは、メモリ上の位置が変わる可能性があるから、この Bitmap を外部に出すことは危険すぎて出来ないということ。
Graphics を使って描画するときも、わざわざメソッドを作ってやらないといけない。
やるとしても、

public Bitmap ToBitmap()
{
    Bitmap bitmap = null;
    using (UnsafePointer ptr = Converter.ArrayToUnsafePointer(pixels, 0))
    {
        Bitmap tmp =  new Bitmap(this.Width, this.Height, this.Stride, this.PixelFormat, ptr.IntPtr);
        bitmap = (System.Drawing.Bitmap)tmp.Clone();
    }
    return bitmap;
}

これが限界だろう。


Bitmap が外部に出せたら、わざわざ DrawBySystemGraphics() とか作らなくてもいいし楽なんだけど、その方法がなかなか……。
何とかならないかなぁ……(;´Д`)