none
WriteableBitmapオブジェクトをスレッド内で使用する方法 RRS feed

  • 質問

  • yiwata8004と申します。
    御拝読ありがとうございます。
    内容的にC#の方とどちらに投稿するか迷いましたが、現在WPFアプリを開発
    しているという事でこちらに投稿させて頂きました。

    スレッド外で定義したWriteableBitmapオブジェクトをスレッド内(Task等)
    で使用するとBackBufferやBackBufferStrideプロパティに対して例外処理
    (System.InvalidOperation.Exception)が発生してしまいます。

    スレッドの前後にLock,UnLock,AddDirtyRectを呼んでもダメで、Back
    BufferやBackBufferStrideプロパティに対して設定をするのかと思い
    ましたがどちらも取得のみのプロパティという事で見当違い。
    海外サイトの掲示板やMSDNを読んだりしましたが、私の理解不足もあ
    ってか結局解決に至りませんでした。

    目的はスレッド内にてWriteableBitmapオブジェクトをjpg保存する事です。

    以上、アドバイスの方を宜しくお願いします。

    開発環境:Visual Studio 2013 Professional
    開発言語:WPF(C#)

    2015年5月3日 0:35

回答

  • フリーズできない場合の方法

    using System;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            private WriteableBitmap wb;
    
            public MainWindow()
            {
                InitializeComponent();
    
                wb = new WriteableBitmap(100, 100, 96, 96, PixelFormats.Bgr24, null);
    
                Int32Rect rect = new Int32Rect(0, 0, 2, 2);
                for (int i = 0; i < 100; i += 2)
                {
                    byte[] a =
                    {
                        0x00, 0x00, 0xFF,  0xFF, 0xFF, 0xFF,
                        0xFF, 0xFF, 0xFF,  0x00, 0x00, 0xFF, 
                    };
                    wb.WritePixels(rect, a, 6, i, i);
                }
    
                this.img.Source = wb;
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                wb.Lock();//必要ならロック
                IntPtr pBuffer = wb.BackBuffer;
                int stride = wb.BackBufferStride;
                int w = wb.PixelWidth;
                int h = wb.PixelHeight;
                double dpiX = wb.DpiX;
                double dpiY = wb.DpiY;
                PixelFormat format = wb.Format;
                BitmapPalette palette = wb.Palette;
    
                Task.Run(() =>
                    {
                        byte[] bs = new byte[stride * h];
                        System.Runtime.InteropServices.Marshal.Copy(pBuffer, bs, 0, bs.Length);//バイト配列として取出し
    
                        WriteableBitmap wbCopy = new WriteableBitmap(w, h, dpiX, dpiY, format, palette);
                        Int32Rect rect = new Int32Rect(0, 0, w, h);
                        wbCopy.WritePixels(rect, bs, stride, 0, 0);//バイト配列を書き込み
                        Save(wbCopy, "test1.jpg");
                    })
                    .ContinueWith(t =>
                    {
                        WriteableBitmap wbCopy = new WriteableBitmap(w, h, dpiX, dpiY, format, palette);
                        Int32Rect rect = new Int32Rect(0, 0, w, h);
                        wbCopy.WritePixels(rect, pBuffer, stride * h, stride, 0, 0);//バッファから直接コピー
                        Save(wbCopy, "test2.jpg");
                    })
                    .ContinueWith(t =>
                    {
                        Dispatcher.Invoke(() =>
                        {
                            wb.Unlock();//ロックしたならアンロック
                        });
                    });
            }
    
            private void Save(WriteableBitmap wb, string path)
            {
                JpegBitmapEncoder jpg = new JpegBitmapEncoder();
                jpg.Frames.Add(BitmapFrame.Create(wb));
                try
                {
                    using (System.IO.FileStream fs = new System.IO.FileStream(path, System.IO.FileMode.Create, System.IO.FileAccess.Write))
                    {
                        jpg.Save(fs);
                    }
                }
                catch (System.IO.IOException) { }
                catch (System.UnauthorizedAccessException) { }
            }
        }
    }
    


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答の候補に設定 星 睦美 2015年5月7日 1:06
    • 回答としてマーク yiwata8004 2015年5月13日 13:24
    2015年5月3日 3:28

すべての返信

  • WriteableBitmapのnewを、その諸々処理するスレッドで行うほかはありません。

    一通り書き込みが終わった後であれば、Freeze()を呼び出すことで他のスレッドで読み取りやBitmapEncoder::Saveすることが可能になります。

    • 回答の候補に設定 星 睦美 2015年5月7日 1:06
    2015年5月3日 1:42
  • フリーズできない場合の方法

    using System;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    
    namespace WpfApplication1
    {
        public partial class MainWindow : Window
        {
            private WriteableBitmap wb;
    
            public MainWindow()
            {
                InitializeComponent();
    
                wb = new WriteableBitmap(100, 100, 96, 96, PixelFormats.Bgr24, null);
    
                Int32Rect rect = new Int32Rect(0, 0, 2, 2);
                for (int i = 0; i < 100; i += 2)
                {
                    byte[] a =
                    {
                        0x00, 0x00, 0xFF,  0xFF, 0xFF, 0xFF,
                        0xFF, 0xFF, 0xFF,  0x00, 0x00, 0xFF, 
                    };
                    wb.WritePixels(rect, a, 6, i, i);
                }
    
                this.img.Source = wb;
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                wb.Lock();//必要ならロック
                IntPtr pBuffer = wb.BackBuffer;
                int stride = wb.BackBufferStride;
                int w = wb.PixelWidth;
                int h = wb.PixelHeight;
                double dpiX = wb.DpiX;
                double dpiY = wb.DpiY;
                PixelFormat format = wb.Format;
                BitmapPalette palette = wb.Palette;
    
                Task.Run(() =>
                    {
                        byte[] bs = new byte[stride * h];
                        System.Runtime.InteropServices.Marshal.Copy(pBuffer, bs, 0, bs.Length);//バイト配列として取出し
    
                        WriteableBitmap wbCopy = new WriteableBitmap(w, h, dpiX, dpiY, format, palette);
                        Int32Rect rect = new Int32Rect(0, 0, w, h);
                        wbCopy.WritePixels(rect, bs, stride, 0, 0);//バイト配列を書き込み
                        Save(wbCopy, "test1.jpg");
                    })
                    .ContinueWith(t =>
                    {
                        WriteableBitmap wbCopy = new WriteableBitmap(w, h, dpiX, dpiY, format, palette);
                        Int32Rect rect = new Int32Rect(0, 0, w, h);
                        wbCopy.WritePixels(rect, pBuffer, stride * h, stride, 0, 0);//バッファから直接コピー
                        Save(wbCopy, "test2.jpg");
                    })
                    .ContinueWith(t =>
                    {
                        Dispatcher.Invoke(() =>
                        {
                            wb.Unlock();//ロックしたならアンロック
                        });
                    });
            }
    
            private void Save(WriteableBitmap wb, string path)
            {
                JpegBitmapEncoder jpg = new JpegBitmapEncoder();
                jpg.Frames.Add(BitmapFrame.Create(wb));
                try
                {
                    using (System.IO.FileStream fs = new System.IO.FileStream(path, System.IO.FileMode.Create, System.IO.FileAccess.Write))
                    {
                        jpg.Save(fs);
                    }
                }
                catch (System.IO.IOException) { }
                catch (System.UnauthorizedAccessException) { }
            }
        }
    }
    


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答の候補に設定 星 睦美 2015年5月7日 1:06
    • 回答としてマーク yiwata8004 2015年5月13日 13:24
    2015年5月3日 3:28

  • Hongliang さん
    gekka さん

    yiwata8004です。
    お二方、早速の返信有難うございます。

    やはりスレッド内で生成するしかないようですね。

    ここで少し補足説明をさせて頂きます。

    実は保存対象のWriteableBitmapオブジェクトはKinect v2から取得したデータで、メンバ変数
    (WriteableBitmap型)に保存されています。

    この変数がKinectイベントで更新される度にスレッド内でClone() or newして別オブジェクト
    を生成->保存をしたいのですが、メンバ変数であってもスレッド内でアクセスするとスレッド
    外で定義した変数同様の例外処理が起きてしまいます。

    現在、やむを得ずWriteableBitmapの保存処理をスレッド外で行ってますが毎回30~40msecも
    処理に時間を取られるため、これを解消したいというのがあります。

    このような場合、どのようにスレッド内で保存用のWriteableBitmapオブジェクトを生成したら
    いいのか悩んでいます。

    最初にこれを書くと話が複雑になると思ったのと、そもそもスレッド外のWriteableBitmap
    オブジェクトをスレッド内で使用できるかどうかも不明だったので先ずはシンプルに後者について
    質問をさせて頂いた次第でございます。m(_ _)m

    何となく、gekkaさんが書かれたソースのButton_Click()イベントハンドラ内のやり方で上手く
    いける気がしますが…(なにぶん今、手元に開発環境がないので試せないですが)

    2015年5月4日 0:19
  • 受け取ったWriteableBitmap自体は受け取った後変更しないのなら、前述の通りFreeze()しておけばいいです。

    <追記>WriteableBitmapが随時更新されるのなら、CloneしたのをFreezeですね。</追記>

    • 編集済み Hongliang 2015年5月4日 1:56
    2015年5月4日 1:10

  • Hongliang さん

    yiwata8004です。
    レスポンスありがとうございます。

    jpg保存後にWriteableBitmapオブジェクトに描画処理→Imageに割り当てという流れになってます。

    お二方から頂いた意見を参考に先ずは試してみて、また結果報告をさせて頂こうと思います。

    色々と有難うございました。

    2015年5月5日 4:50

  • yiwata8004です。

    お陰様でWriteableBitmapの保存がスレッド内(Task)でも可能となりました。
    しかし、フルHDのせいかjpg形式で保存しても処理に50msec以上要してしまいます。

    Kinect v2のイベントでWriteableBitmapが更新される度に追記保存(※)したいので
    他のファイルの追記保存のように処理時間を10msec以下に抑えたいのですが、他のクラスと
    比べてWriteableBitmapのnewに時間がかかっているあたり、フルHDでは無理なのでしょうか?

    (※)…後から再生させるためです

    2015年5月11日 13:56
  • フォーラム オペレーターの星 睦美です。
    yiwata8004 さん、こんにちは。

    私のほうで[回答の候補に設定] させていただきましたが、解決したとのことですね。投稿者から[回答としてマーク] いただけると、回答者へのお礼とはげましになりますのでよろしくお願い致します。

    追加の質問内容は新しい質問として投稿いただければ、ユーザーからのアドバイスが集まりやすくなると思います。
    それでは、引き続きフォーラムをお役立てください。


    フォーラム オペレーター 星 睦美 - MSDN Community Support

    2015年5月13日 2:36