方法: 指定された同期コンテキストで作業をスケジュールする
この例は、WPF (Windows Presentation Foundation) アプリケーションで TaskScheduler.FromCurrentSynchronizationContext メソッドを使用して、ユーザー インターフェイス (UI) コントロールが作成されたスレッドでタスクをスケジュールする方法を示しています。
手順
WPF プロジェクトを作成するには
Visual Studio で、WPF アプリケーション プロジェクトを作成し、名前を付けます。
デザイン ビューで、ツールボックスからデザイン サーフェイスに Image コントロールをドラッグします。 XAML ビューで、水平方向の配置を "Left" に指定します。 コントロールは実行時に動的にサイズ変更されるため、サイズを気にする必要はありません。 既定の名前である "image1" をそのまま使用します。
ツールボックスからアプリケーション ウィンドウの左下の部分にボタンをドラッグします。 ボタンをダブルクリックして Click イベント ハンドラーを追加します。 XAML ビューで、ボタンの Content プロパティを "Make a Mosaic" と指定し、水平方向の配置を "Left" と指定します。
MainWindow.xaml.cs ファイルで、次のコードを使用してファイルの内容をすべて置き換えます。 名前空間の名前がプロジェクト名と一致することを確認してください。
F5 キーを押してアプリケーションを実行します。 ボタンをクリックするたびに、タイルの新しい配置が表示されます。
例
説明
次の例では、指定したディレクトリからランダムに選択されるイメージのモザイクを作成します。 WPF オブジェクトを使用して、イメージの読み込みとサイズ変更を行います。 次に、ParallelFor() ループを使用するタスクに生のピクセルを渡して、ピクセル データを単一の大きなバイト配列に書き込みます。 2 つのタイルが同じ配列要素を占めることはないので、同期は不要です。 また、タイルの位置は他のタイルとは独立して計算されるため、任意の順序でタイルを書き込むことができます。 次に、UI スレッドで実行されるタスクに大きな配列を渡し、そこでピクセル データを Image コントロールに読み込みます。
コード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace wpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private int fileCount;
int colCount;
int rowCount;
private int tilePixelHeight;
private int tilePixelWidth;
private int largeImagePixelHeight;
private int largeImagePixelWidth;
private int largeImageStride;
PixelFormat format;
BitmapPalette palette;
public MainWindow()
{
InitializeComponent();
// For this example, values are hard-coded to a mosaic of 8x8 tiles.
// Each tile is 50 pixels high and 66 pixels wide and 32 bits per pixel.
colCount = 12;
rowCount = 8;
tilePixelHeight = 50;
tilePixelWidth = 66;
largeImagePixelHeight = tilePixelHeight * rowCount;
largeImagePixelWidth = tilePixelWidth * colCount;
largeImageStride = largeImagePixelWidth * (32 / 8);
this.Width = largeImagePixelWidth + 40;
image1.Width = largeImagePixelWidth;
image1.Height = largeImagePixelHeight;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
// For best results use 1024 x 768 jpg files at 32bpp.
string[] files = System.IO.Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures\", "*.jpg");
fileCount = files.Length;
Task<byte[]>[] images = new Task<byte[]>[fileCount];
for (int i = 0; i < fileCount; i++)
{
int x = i;
images[x] = Task.Factory.StartNew(() => LoadImage(files[x]));
}
// When they�ve all been loaded, tile them into a single byte array.
var tiledImage = Task.Factory.ContinueWhenAll(
images, (i) => TileImages(i));
// We are currently on the UI thread. Save the sync context and pass it to
// the next task so that it can access the UI control "image1".
var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
// On the UI thread, put the bytes into a bitmap and
// and display it in the Image control.
var t3 = tiledImage.ContinueWith((antedecent) =>
{
// Get System DPI.
Matrix m = PresentationSource.FromVisual(Application.Current.MainWindow)
.CompositionTarget.TransformToDevice;
double dpiX = m.M11;
double dpiY = m.M22;
BitmapSource bms = BitmapSource.Create( largeImagePixelWidth,
largeImagePixelHeight,
dpiX,
dpiY,
format,
palette, //use default palette
antedecent.Result,
largeImageStride);
image1.Source = bms;
}, UISyncContext);
}
byte[] LoadImage(string filename)
{
// Use the WPF BitmapImage class to load and
// resize the bitmap. NOTE: Only 32bpp formats are supported correctly.
// Support for additional color formats is left as an exercise
// for the reader. For more information, see documentation for ColorConvertedBitmap.
BitmapImage myBitmapImage = new BitmapImage();
myBitmapImage.BeginInit();
myBitmapImage.UriSource = new Uri(filename);
tilePixelHeight = myBitmapImage.DecodePixelHeight = tilePixelHeight;
tilePixelWidth = myBitmapImage.DecodePixelWidth = tilePixelWidth;
myBitmapImage.EndInit();
format = myBitmapImage.Format;
int size = (int)(myBitmapImage.Height * myBitmapImage.Width);
int stride = (int)myBitmapImage.Width * 4;
byte[] dest = new byte[stride * tilePixelHeight];
myBitmapImage.CopyPixels(dest, stride, 0);
return dest;
}
int Stride(int pixelWidth, int bitsPerPixel)
{
return (((pixelWidth * bitsPerPixel + 31) / 32) * 4);
}
// Map the individual image tiles to the large image
// in parallel. Any kind of raw image manipulation can be
// done here because we are not attempting to access any
// WPF controls from multiple threads.
byte[] TileImages(Task<byte[]>[] sourceImages)
{
byte[] largeImage = new byte[largeImagePixelHeight * largeImageStride];
int tileImageStride = tilePixelWidth * 4; // hard coded to 32bpp
Random rand = new Random();
Parallel.For(0, rowCount * colCount, (i) =>
{
// Pick one of the images at random for this tile.
int cur = rand.Next(0, sourceImages.Length);
byte[] pixels = sourceImages[cur].Result;
// Get the starting index for this tile.
int row = i / colCount;
int col = (int)(i % colCount);
int idx = ((row * (largeImageStride * tilePixelHeight)) + (col * tileImageStride));
// Write the pixels for the current tile. The pixels are not contiguous
// in the array, therefore we have to advance the index by the image stride
// (minus the stride of the tile) for each scanline of the tile.
int tileImageIndex = 0;
for (int j = 0; j < tilePixelHeight; j++)
{
// Write the next scanline for this tile.
for (int k = 0; k < tileImageStride; k++)
{
largeImage[idx++] = pixels[tileImageIndex++];
}
// Advance to the beginning of the next scanline.
idx += largeImageStride - tileImageStride;
}
});
return largeImage;
}
}
}
コメント
この例は、UI スレッドからデータを移動し、そのデータを並列ループと Task オブジェクトを使用して変更し、UI スレッドで実行されるタスクに返す方法を示しています。 この方法は、WPF API によってサポートされていない操作または十分な速さで実行されない操作を、タスク並列ライブラリを使用して実行する必要がある場合に便利です。 WPF でイメージのモザイクを作成するもう 1 つの方法は、WrapPanel オブジェクトを使用し、このオブジェクトにイメージを追加することです。 WrapPanel は、タイルを配置する作業を処理します。 ただし、この作業は UI スレッドでしか実行できません。
この例には、いくつかの制限があります。 たとえば、サポートされるのは 1 ピクセルあたり 32 ビットのイメージのみです。他の形式のイメージは、サイズ変更操作中に BitmapImage オブジェクトによって壊されます。 また、すべてのソース イメージは、タイルよりも大きいサイズである必要があります。 応用として、複数のピクセル形式およびファイル サイズを処理する機能を追加することもできます。