Quantcast
Channel: 午後わてんのブログ
Viewing all articles
Browse latest Browse all 420

画像処理中のプログレスバー更新とキャンセルボタンで中止

$
0
0

時間のかかりがちな画像処理のときに
  • プログレスバーの更新
  • 処理のキャンセル

イメージ 5
できた!




最初に作る時には前回の
処理中に進捗率表示とキャンセルボタンで中止はasync、await、Task.Run、Progress、CancellationTokenSource ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15489172.html
これを何も考えずに画像処理に使ってみようとしたら

イメージ 1
スレッドが違うっていうエラーが出る
どうやら別スレッドでBitmapSourceを直接変更するような処理はできないみたい?
ってことでいくつか試してみた



イメージ 4

別スレッドで実行するメソッドへ渡す引数を
A:int
B:string
C:BitmapSource.Width
D:BitmapSource.Widthを入れたint
で試したら
CのBitmapSourceだけがエラーになる

A:int i = await Task.Run(() => 100 * 2);//OK
B:int i = await Task.Run(() => ("test文字数").Count());//OK
C:int i = await Task.Run(() => BitmapSource.PixelWidth * 2);//エラー
D:int w = BitmapSource.PixelWidth;
int i = await Task.Run(() => w * 2);//OK

Cみたいに直接BitmapSourceのWidthを渡して処理しようとするとエラーになるけど
Dのようにint型の変数に入れたのを引数にすればエラーにならない


デザイン画面
イメージ 2

C#コード
イメージ 3


using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;

namespace _20180507_BitmapSourceは別スレッドで処理できない
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyButtonInteger.Click += MyButtonInteger_Click;
MyButtonString.Click += MyButtonString_Click;
MyButtonBitmap.Click += MyButtonBitmap_Click;
MyButtonBitmap2.Click += MyButtonBitmap2_Click;
}

//int
private async void MyButtonInteger_Click(object sender, RoutedEventArgs e)
{
int i = await Task.Run(() => 100 * 2);
MyTextBlock.Text = i.ToString();
}

//string
private async void MyButtonString_Click(object sender, RoutedEventArgs e)
{
int i = await Task.Run(() => ("test文字数").Count());
MyTextBlock.Text = i.ToString();
}

//BitmapSource
private async void MyButtonBitmap_Click(object sender, RoutedEventArgs e)
{
var source = new BitmapImage(new Uri(@"D:\ブログ用\チェック用2\NEC_2820_2018_05_06_午後わてん.jpg"));
//エラーになる、別スレッドでBitmapSourceを使った処理はできない
int i = await Task.Run(() => source.PixelWidth * 2);
MyTextBlock.Text = i.ToString();
}

//BitmapSourceのPixelWidthを入れたint型変数
private async void MyButtonBitmap2_Click(object sender, RoutedEventArgs e)
{
var source = new BitmapImage(new Uri(@"D:\ブログ用\チェック用2\NEC_2820_2018_05_06_午後わてん.jpg"));
int w = source.PixelWidth;
//これならOK
int i = await Task.Run(() => w * 2);
MyTextBlock.Text = i.ToString();
}
}
}


これでBitmapSourceやBitmapSourceのプロパティを直接渡すとエラーになるけど
変数に入れて渡せばエラーにならず別スレッドで処理できそうなので
できたのが最初の画像処理アプリ

デザイン画面
イメージ 6


C#コード

using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Threading;

namespace _20180507_別スレッドで画像処理
{
public partial class MainWindow : Window
{
BitmapSource OriginBitmap;//元画像用
CancellationTokenSource MyCancelSource;//キャンセル用

public MainWindow()
{
InitializeComponent();
OriginBitmap = GetBitmapSourceWithChangePixelFormat2(
@"D:\ブログ用\チェック用2\NEC_2773_2018_05_05_午後わてん_.jpg", PixelFormats.Pbgra32, 96, 96);
MyImage.Source = OriginBitmap;

MyButtonOrigin.Click += MyButtonOrigin_Click;
MyButton1.Click += MyButton1_Click;
MyButtonCancel.Click += MyButtonCancel_Click;
MyButtonCancel.IsEnabled = false;
}

private void MyButtonCancel_Click(object sender, RoutedEventArgs e)
{
MyCancelSource.Cancel();//キャンセルを発行
}

//処理実行ボタン押した時
private async void MyButton1_Click(object sender, RoutedEventArgs e)
{
MyButtonボタンの有効設定(true);
var wb = new WriteableBitmap(OriginBitmap);
int w = wb.PixelWidth;
int h = wb.PixelHeight;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
MyCancelSource = new CancellationTokenSource();
CancellationToken token = MyCancelSource.Token;
Progress<int> progress = new Progress<int>(MyProgressUpdate);
await Task.Run(() => ColorReverce(pixels, w, h, stride, token, progress));
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
MyImage.Source = wb;
MyButtonボタンの有効設定(false);
}

//元の画像に戻す
private void MyButtonOrigin_Click(object sender, RoutedEventArgs e)
{
MyImage.Source = OriginBitmap;
}

//プログレスバーの表示更新
private void MyProgressUpdate(int i)
{
MyProgressBar.Value = i;
}

private void MyButtonボタンの有効設定(bool is処理中)
{
if (is処理中 == true)
{
MyButtonCancel.IsEnabled = true;
MyButtonOrigin.IsEnabled = false;
MyButton1.IsEnabled = false;
}
else
{
MyButtonCancel.IsEnabled = false;
MyButtonOrigin.IsEnabled = true;
MyButton1.IsEnabled = true;
}
}

//色を反転
private bool ColorReverce(byte[] pixels, int w, int h, int stride,
CancellationToken token, IProgress<int> progress)
{
long p;
for (int y = 0; y < h; ++y)
{
if (token.IsCancellationRequested == true)
{
return false;
}

for (int x = 0; x < w; ++x)
{
p = y * stride + x * 4;
//RGBを反転、アルファ値はそのまま
pixels[p] = (byte)(255 - pixels[p]);
pixels[p + 1] = (byte)(255 - pixels[p + 1]);
pixels[p + 2] = (byte)(255 - pixels[p + 2]);
}
progress.Report(y * 100 / (h - 1));//プログレスバー更新
Thread.Sleep(10);//0.01秒待機
}
return true;
}


//指定PixelFormatでBitmapSource取得
private BitmapSource GetBitmapSourceWithChangePixelFormat2(
string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0)
{
BitmapSource source = null;
try
{
using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
var bf = BitmapFrame.Create(fs);
var convertedBitmap = new FormatConvertedBitmap(bf, pixelFormat, null, 0);
int w = convertedBitmap.PixelWidth;
int h = convertedBitmap.PixelHeight;
int stride = (w * pixelFormat.BitsPerPixel + 7) / 8;
byte[] pixels = new byte[h * stride];
convertedBitmap.CopyPixels(pixels, stride, 0);
//dpi指定がなければ元の画像と同じdpiにする
if (dpiX == 0) { dpiX = bf.DpiX; }
if (dpiY == 0) { dpiY = bf.DpiY; }
//dpiを指定してBitmapSource作成
source = BitmapSource.Create(
w, h, dpiX, dpiY,
convertedBitmap.Format,
convertedBitmap.Palette, pixels, stride);
};
}
catch (Exception) { }
return source;
}

}
}


イメージ 7
画像の色を反転する画像処理
今まではBitmapSourceを受け取って処理していたけど、別スレッドで処理する場合はそれだとエラーになるのでbyte型配列とint型だけ受け取るように変更(80行目)
100行目でウェイトをかけているのは、色の反転処理はあっという間に終わってキャンセルボタンを押せないから

イメージ 8
処理開始ボタンのクリックイベント
36~41行目と47,48行目は以前では画像処理で行っていたところ
46行目で画像処理を別スレッドで行っている
それ以外は前回と同じ




やっとできたけど

BitmapSourceはButtonやTextBlockと同じUIってことなのかねえ
そもそもUIってのがイマイチ理解できていない

それに別スレッドで行うメソッドに引数を渡す時に
BitmapSource.Widthそのままだとエラーになるけど
int w = BitmapSource.Width
って変数に入れて渡せばエラーにならないってのも、そうなのかあって感じ

参照すること自体がUIスレッドにアクセスするってことだから別スレッドエラーで
int型は参照型じゃなくて値型だからint型変数に入れれば参照じゃないからOK
ってことかなあ

それでも今回ので画像処理中の進捗率表示と処理キャンセルっていう目的は達成できた


コード




Viewing all articles
Browse latest Browse all 420

Trending Articles