Newer
Older
TIASShot / TIASshot / Utils / BitmapUtil.cs
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace TIASshot {

    /// <summary>
    /// Bitmap 操作の共有ユーティリティ。
    /// プレビュー表示経路で毎フレーム発生する Bitmap の複製処理を高速化するために,
    /// GDI+ の DrawImage 経路(<see cref="Bitmap(Image)"/>)ではなく,
    /// LockBits による生画素の直接コピー(memcpy 相当)を提供する。
    /// </summary>
    internal static class BitmapUtil {

        [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
        private static extern void CopyMemory(IntPtr dest, IntPtr src, UIntPtr count);

        /// <summary>
        /// Bitmap を高速に複製する。
        /// 入力と同じ PixelFormat・サイズの新規 Bitmap を確保し,LockBits で取得した
        /// 生画素バッファをネイティブ memcpy(RtlMoveMemory)で丸ごとコピーする。
        /// 生成される Bitmap は入力とは独立した画素バッファを持つため,
        /// 呼び出し元スレッドとは別のスレッド(UI スレッド等)で安全に保持・Dispose できる
        /// (<see cref="Bitmap(Image)"/> によるフルコピーと同じ所有権モデル)。
        /// </summary>
        /// <param name="src">複製元の Bitmap(読み取りのみ・変更しない)</param>
        /// <returns>src と独立した画素バッファを持つ新規 Bitmap</returns>
        public static Bitmap FastClone(Bitmap src) {
            if (src == null) return null;

            // インデックスカラー系(8bpp 以下)はパレットを保持する必要があり,
            // LockBits の単純コピーだけではパレット情報が失われるため,
            // 安全網として従来どおりの GDI+ フルコピーにフォールバックする。
            // ※ この経路は入力と独立・色が等価なコピーを返すが,GDI+ の Bitmap(Image)
            //    により PixelFormat は 32bppArgb に変換される(8bppIndexed のままにはならない)。
            // プレビュー画像(Mat.ToBitmap() 由来の 24bppRgb)は通常この分岐に入らない。
            if ((src.PixelFormat & PixelFormat.Indexed) != 0) {
                return new Bitmap(src);
            }

            var rect = new Rectangle(0, 0, src.Width, src.Height);
            var dst = new Bitmap(src.Width, src.Height, src.PixelFormat);

            BitmapData srcData = null;
            BitmapData dstData = null;
            try {
                srcData = src.LockBits(rect, ImageLockMode.ReadOnly, src.PixelFormat);
                dstData = dst.LockBits(rect, ImageLockMode.WriteOnly, dst.PixelFormat);

                // ストライドは通常一致するが,念のため行ごとに小さい方のバイト数だけコピーする
                // (負ストライド/パディング差があっても src・dst それぞれの範囲を超えない堅牢な実装)
                int bytesPerRow = Math.Min(Math.Abs(srcData.Stride), Math.Abs(dstData.Stride));
                var count = (UIntPtr)bytesPerRow;

                for (int y = 0; y < src.Height; y++) {
                    IntPtr srcRow = IntPtr.Add(srcData.Scan0, y * srcData.Stride);
                    IntPtr dstRow = IntPtr.Add(dstData.Scan0, y * dstData.Stride);
                    CopyMemory(dstRow, srcRow, count);
                }
            } finally {
                // src は ReadOnly ロックのみなので UnlockBits は元データを変更しない(src は破損しない)
                if (srcData != null) src.UnlockBits(srcData);
                if (dstData != null) dst.UnlockBits(dstData);
            }

            return dst;
        }
    }
}