WriteableBitmap のデータを画像ファイルに保存する

以前に「画像ファイルを開いて WriteableBitmap にデータを書きこむ」という記事を書きましたが、本記事はそれの逆です。今度はファイル保存です。

下記のコードは、

(0) ソフトの起動時に 32bits と 8bits の WriteableBitmap を生成する。(148, 161行目)
(1) 保存時はファイルストリームを使うので using によるお決まりのコード。(218行目)
(2) BMP や PNG や JPEG などファイル形式によって違ったエンコーダを選択する。(222行目)
(3) エンコーダにデータを渡すために WriteableBitmap から BitmapFrame を生成する。
(4) エンコーダに画像データを渡して、ファイルストリームで保存する。

という流れです。

WriteableBitmap の内部には十分ネィティブなデータが格納されていると予想されるので、なぜ BitmapFrame というオブジェクトを途中で介在させないといけないのかよくわかりませんが、こうしておくと何かいいことがあるのでしょう。

下記の名前空間の追加をお忘れなく。
using Microsoft.Win32;
using System.IO;
using System.Runtime.InteropServices;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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;

// この using 追加を忘れずに. Don't forget to add this sentence.
using Microsoft.Win32;
using System.IO;
using System.Runtime.InteropServices;

namespace aaa
{

	public partial class MainWindow : Window
	{

		[StructLayout( LayoutKind.Explicit)]
		public struct GY_IMG_BPP32
		{
			[FieldOffset(0)] public byte B;
			[FieldOffset(1)] public byte G;
			[FieldOffset(2)] public byte R;
			[FieldOffset(3)] public byte A;
		}

		const int INI_W = 512;
		const int INI_H = 512;

		GY_IMG_BPP32 [] Data32;
		byte [] Data08;

		const double DPI_X = 96.0;
		const double DPI_Y = 96.0;

		WriteableBitmap Bmp32;
		WriteableBitmap Bmp08;

		/////////////////////////////////////////
		/////////////////////////////////////////
		/////////////////////////////////////////

		public MainWindow()
		{

			InitializeComponent();

			int w = INI_W;
			int h = INI_H;
			int numpix = w * h;

			// 32ビットデータの領域を確保する.
			Data32 = new GY_IMG_BPP32[ numpix ];

			// 2bitシフトで、四分のイチ.
			int qtr_w = w >> 2;

			int xs0 = qtr_w * 0;
			int xs1 = qtr_w * 1;
			int xs2 = qtr_w * 2;
			int xs3 = qtr_w * 3;

			int xe0 = (( xs0 + qtr_w ) - 1 );
			int xe1 = (( xs1 + qtr_w ) - 1 );
			int xe2 = (( xs2 + qtr_w ) - 1 );
			int xe3 = (( xs3 + qtr_w ) - 1 );

			byte LEVEL_ALPHA = 0xff;

			// カラーグラデーション.
			for ( int j = 0; j < h; j++ )
			{

				double rate = (double)( j )/(double)( h - 1 );
				byte level = (byte)( Math.Round( rate * 255.0 ));

				int adrs = w * j;

				for ( int i = xs0; i <= xe0; i++ )
				{
					Data32[ adrs ].B = level;
					Data32[ adrs ].G = level;
					Data32[ adrs ].R = level;
					Data32[ adrs ].A = LEVEL_ALPHA;
					adrs++;
				}

				for ( int i = xs1; i <= xe1; i++ )
				{
					Data32[ adrs ].B = level;
					Data32[ adrs ].G = 0x00;
					Data32[ adrs ].R = 0x00;
					Data32[ adrs ].A = LEVEL_ALPHA;
					adrs++;
				}

				for ( int i = xs2; i <= xe2; i++ )
				{
					Data32[ adrs ].B = 0x00;
					Data32[ adrs ].G = level;
					Data32[ adrs ].R = 0x00;
					Data32[ adrs ].A = LEVEL_ALPHA;
					adrs++;
				}

				for ( int i = xs3; i <= xe3; i++ )
				{
					Data32[ adrs ].B = 0x00;
					Data32[ adrs ].G = 0x00;
					Data32[ adrs ].R = level;
					Data32[ adrs ].A = LEVEL_ALPHA;
					adrs++;
				}

			}

			// 8bitデータの領域を確保する.
			Data08 = new byte[ numpix ];

			// 白黒グラデーション.
			for ( int j = 0; j < h; j++ )
			{

				double rate = (double)( j )/(double)( h - 1 );
				byte level = (byte)( Math.Round( rate * 255.0 ));

				int adrs = w * j;

				for ( int i = 0; i < w; i++ )
				{
					Data08[ adrs ] = level;
					adrs++;
				}

			}

			// 書き込み可能ビットマップ(32bits)を生成する.
			PixelFormat pixfmt32 = PixelFormats.Bgra32;
			Bmp32 = new WriteableBitmap( w, h, DPI_X, DPI_Y, pixfmt32, null ); 

			// 書き込み可能ビットマップ(8bits)のためのパレットを定義する.
			const int NUM_PALETTE = 256;
			List<Color> list_color = new List<Color>();
			for ( int k = 0; k < NUM_PALETTE; k++ )
			{
				byte v = (byte)( k );
				Color c = Color.FromArgb( LEVEL_ALPHA, v, v, v );
				list_color.Add( c );
			}

			// 書き込み可能ビットマップ(8bits)を生成する.
			PixelFormat pixfmt08 = PixelFormats.Indexed8;
			BitmapPalette palette = new BitmapPalette( list_color );
			Bmp08 = new WriteableBitmap( w, h, DPI_X, DPI_Y, pixfmt08, palette ); 

			// WritePixels() のための引数を用意する.
			Int32Rect rct = new Int32Rect( 0, 0, w, h );
			int stride_bytes_32 = w * Marshal.SizeOf( typeof( GY_IMG_BPP32 ));
			int stride_bytes_08 = w * sizeof( byte );
			int offset = 0;

			// 引数にしたがって書き込み可能ビットマップにデータを書き込む.
			Bmp32.WritePixels( rct, Data32, stride_bytes_32, offset );
			Bmp08.WritePixels( rct, Data08, stride_bytes_08, offset );

			// ウインドウ出力先イメージのサイズを決定しビットマップと関連づける.
			image000.Width = w;
			image000.Height = h;
			image000.Source = Bmp32;

			// ウインドウのタイトル.
			this.Title = "GazoYaro";

		}

		private void menuFileSave32_Click( object sender, RoutedEventArgs e )
		{

			// 32bitsカラーグラデーションのビットマップを画面に出力する.
			image000.Width = Bmp32.PixelWidth;
			image000.Height = Bmp32.PixelHeight;
			image000.Source = Bmp32;

			// ファイルを保存ダイアログ.
			SaveFileDialog sfd = new SaveFileDialog();

			sfd.Title = "PNG(32bits)を保存する";
			sfd.Filter = "PNG|*png|ALL|*.*";
			sfd.FileName = "this_is_32";
			sfd.DefaultExt = ".png";

			// ダイアログを表示する.
			bool? ret = sfd.ShowDialog();
			if ( ret == false )
			{
				// キャンセルが押されたら脱出する.
				return; // warning.
			}

			// ここから保存にトライする.
			try
			{

				// ファイルストリームの引数を決める.
				String filepath = sfd.FileName;
				FileMode fmd = FileMode.Create;
				FileAccess fas = FileAccess.Write;

				using ( FileStream fs = new FileStream( filepath, fmd, fas ) )
				{

					// ここで保存形式を決定する.
					PngBitmapEncoder enc = new PngBitmapEncoder();
					//BmpBitmapEncoder enc = new BmpBitmapEncoder(); // BMP の場合.
					//JpegBitmapEncoder enc = new JpegBitmapEncoder(); // JPG の場合.

					// WriteableBitmap からエンコーダに渡すためのイメージデータを作成する.
					BitmapFrame bmf = BitmapFrame.Create( Bmp32 );

					// ファイル形式ごとのエンコーダに渡す.
					enc.Frames.Add( bmf );

					// ファイルストリームでファイル保存する.
					enc.Save( fs );

					// using 内の場合クローズは省略してもよい.
					fs.Close();

				}

			}
			catch ( Exception excp )
			{
				MessageBox.Show( excp.Message );
				return;
			}

		}

		private void menuFileSave08_Click( object sender, RoutedEventArgs e )
		{

			// 8bits白黒グラデーションのビットマップを画面に出力する.
			image000.Width = Bmp08.PixelWidth;
			image000.Height = Bmp08.PixelHeight;
			image000.Source = Bmp08;

			// ファイルを保存ダイアログ.
			SaveFileDialog sfd = new SaveFileDialog();

			sfd.Title = "PNG(8bits)を保存する";
			sfd.Filter = "PNG|*png|ALL|*.*";
			sfd.FileName = "this_is_08";
			sfd.DefaultExt = ".png";

			// ダイアログを表示する.
			bool? ret = sfd.ShowDialog();
			if ( ret == false )
			{
				// キャンセルが押されたら脱出する.
				return; // warning.
			}

			// ここから保存にトライする.
			try
			{

				// ファイルストリームの引数を決める.
				String filepath = sfd.FileName;
				FileMode fmd = FileMode.Create;
				FileAccess fas = FileAccess.Write;

				using ( FileStream fs = new FileStream( filepath, fmd, fas ) )
				{

					// ここで保存形式を決定する.
					PngBitmapEncoder enc = new PngBitmapEncoder();
					//BmpBitmapEncoder enc = new BmpBitmapEncoder(); // BMP の場合.
					//JpegBitmapEncoder enc = new JpegBitmapEncoder(); // JPG の場合.

					// WriteableBitmap からエンコーダに渡すためのイメージデータを作成する.
					BitmapFrame bmf = BitmapFrame.Create( Bmp08 );

					// ファイル形式ごとのエンコーダに渡す.
					enc.Frames.Add( bmf );

					// ファイルストリームでファイル保存する.
					enc.Save( fs );

					// using 内の場合クローズは省略してもよい.
					fs.Close();

				}

			}
			catch ( Exception excp )
			{
				MessageBox.Show( excp.Message );
				return;
			}

		}

		private void menuKindBmp32_Click( object sender, RoutedEventArgs e )
		{
			// 32bitsカラーグラデーションのビットマップを画面に出力する.
			image000.Width = Bmp32.PixelWidth;
			image000.Height = Bmp32.PixelHeight;
			image000.Source = Bmp32;
		}

		private void menuKindBmp08_Click( object sender, RoutedEventArgs e )
		{
			// 8bits白黒グラデーションのビットマップを画面に出力する.
			image000.Width = Bmp08.PixelWidth;
			image000.Height = Bmp08.PixelHeight;
			image000.Source = Bmp08;
		}

		private void menuApplicationQuit_Click( object sender, RoutedEventArgs e )
		{
			// ウインドウを閉じてアプリケーションを終了する.
			this.Close();
		}

		private void menuFileOpen_Click( object sender, RoutedEventArgs e )
		{
			MessageBox.Show( "NOP: menuFileOpen_Click();" );
		}

	}
}

サンプルソースを用意しました。上記のコードのコピーペーストでうまく動かない場合にご利用ください。

下記の記事は、本記事の逆でファイルを開くほうです。よろしければご参照ください。

画像ファイルを開いて WriteableBitmap にデータを書きこむ

画像ファイルを開き、内部データにアクセスしやすい WriteableBitmap を経由してウインドウに表示する方法を紹介します。