WPF でのビットマップ操作

前回の更新からずいぶんと間があいてしまった。その間に、Visual Studio 2008 が発売されて、さらに SP1 まで出てしまった。

WinForm ベースアプリでのビットマップ操作を以前に投稿したが、今回はその WPF 版。

1. GUIの準備
XAML で GUI を作る中で、 Image コントロールを配置する。Image コントロールの Source プロパティはプログラム中で設定するので設定しない。

2. 画像データの準備
前と同じように、画像のサイズに等しい byte の配列を用意する。

3. WriteableBitmap の生成
コンストラクタの引数のうち、DPI については、とりあえず 96 を指定。
ピクセルフォーマットは、System.Windows.Media.PixelFormats.Gray8 を指定。

4. WriteableBitmap への書き込み
WriteableBitmap.WritePixels() メソッドを使って、画像データを WriteableBitmap を書き込む。
WinForm と同様に、WriteableBitmap.BackBuffer と WriteableBitmap.BackBufferStride プロパティから画像バッファのアドレスを取得して、画像データをコピーする方法もあるが、せっかく WritePixels() メソッドが用意されているので、こちらを使うことにする。

5. Image.Source プロパティの設定
1 で無視した Image コントロールの Source プロパティを設定する。


注意しなければならないのは、画像サイズが変わったときに、WriteableBitmap オブジェクトを生成しなおすことになるのだが、その際には、Image コントロールの Source プロパティも設定しなおす必要がある。

閉じる コメント(0)

閉じる トラックバック(0)

ListBoxのDataSourceプロパティ(WinForm)

ListBoxやComboBoxで、一覧に並べるデータは、デザイン時にコレクションエディタで設定するのが基本的なやり方。GUIを簡単に作っていくだけならそのやり方でも十分なのだけど、ちょっとした数のデータを管理するためには、画面と内部データの2重管理になって、結構ややこしい。

こんなときにListBoxやComboBoxのDataSourceプロパティが使えるということを最近知った。

DataSourceという名前から、てっきりデータベースとのリンクにしか使えないと思い込んでいたのだけど、IListインターフェースを実装するクラスも指定できて、DisplayNameプロパティに設定したプロパティ名で取得できるオブジェクトが表示される文字列に対応する。

こんな感じで管理したいデータをラップしたクラスを用意する。
class MyData{...}//中身は省略

class MyDataState{
    private MyData m_MyData;
    public string DisplayString{
        get{ return m_MyData.ToString();}
    }
    public MyData MyDataValue{
        get{ return m_MyData; }
    }
}

次に BindgList で MyData のリスト構造を作る。
    // ListBox と同じスコープで。
    BindingList<MyDataState> m_datalist = new BindingList<MyDataState>();
    m_datalist.Add( new MyDataState(...) );
    .... // (必要なだけ項目を用意する)

最後に、ListBoxにm_datalistを関連付けさせる。
    ListBox1.DataSource = m_datalist;
    ListBox1.DisplayMember = "DisplayString";
    ListBox1.ValueMember = "MyDataValue";

こうすると、 m_datalist の中身が ListBox の中身に反映される。
m_datalist の中身を変更したときは、ResetItem()(BindingListのメソッド)を呼べばListBoxの表示にも反映される。

Win32の頃だと、オーナードロー/カスタムドローでがんばらないとこういうことはできなかったので、このテクニックは今後の応用範囲は広いかも。

このブログを書く際に、ヘルプをおさらいしていたら、DataSourceプロパティのほかに、DataBindingsというプロパティの存在にも気がついた。目指していることは似ている感じなので、それぞれに適した利用シーン(データ構造?)の違いなんかをもう少し調べたほうがいいかも。

閉じる コメント(0)

閉じる トラックバック(0)

C#でのビットマップ操作(8bitグレイスケール)

C#(厳密には.NET Framework と言うべきか)でのビットマップ操作

画像ファイルをロードして表示するためのAPIやクラスの解説はよく見かけるのだけど、プログラム中でピクセルを操作した結果を表示する方法についてはあまり見かけないので、メモ。

大まかに言うと、
・Bitmap オブジェクトを生成。
・Bitmap オブジェクトから BitmapDataをロック
・ロックしたBitmapDataに設定
・BitmapDataのロックを解除
・Bitmapオブジェクトを描画
で描画できる。

ここでは、8bitグレイスケールのデータ(byte配列)を画像として描画するためのサンプルコードを示す。

まず、描画の元となるデータとしての8bitデータ配列を用意。
        // BMP_WIDTH, BMP_HEIGHT は、データの幅と高さ
        private byte[] img_data_ = new byte[BMP_WIDTH * BMP_HEIGHT];
描画元としてのBitmapオブジェクトを用意。8bitグレイスケールなので、ピクセルフォーマットはパレット付8ビットを選択する。
        private Bitmap img_ = new Bitmap(BMP_WIDTH, BMP_HEIGHT, PixelFormat.Format8bppIndexed);

8bitグレイスケールなので、パレットを設定。
        // パレットの設定は、img_ に対して 1 回だけおこなえばよい。
        ColorPalette pal = img_.Palette;
        for (int i = 0; i < 256; ++i)
        {
            pal.Entries[i] = Color.FromArgb(i, i, i);
        }
        img_.Palette = pal;  // なぜかこうする。
ColorPalette オブジェクトを Bitmap から取得して、設定した後、Bitmap オブジェクトへ設定し直す事が必要。Bitmap から取得した ColorPalette オブジェクトが、取得元の Bitmap のカラーテーブルそのものを指すのではなく、複製したオブジェクトが返っているのだろうか。

ここまでが、言わば準備の段階。次からが、8bit配列を画像として描画するための本質的な部分。

BitmapDataをロックして、設定する。
        BitmapData bmpdata = null;
        try
        {
            bmpdata = img_.LockBits(new Rectangle(0, 0, BMP_WIDTH, BMP_HEIGHT), 
                       ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
            for (int j = 0; j < BMP_HEIGHT; ++j)
            {
                IntPtr dst_line = (IntPtr)((Int64)bmpdata.Scan0 + j * bmpdata.Stride);
                Marshal.Copy(img_data_, j * BMP_WIDTH, dst_line, BMP_WIDTH);
            }
        }
        finally
        {
            if (bmpdata != null)
            {
                img_.UnlockBits(bmpdata);
            }
        }
ロックしたBitmapDataは必ずしもロック範囲で連続したデータになっているわけではないので、1行単位でStrideメンバ分のオフセットを加えてアドレスを計算する。
また、生メモリ配列に等しいメモリに配列のデータをコピーするために、System.Runtime.InteropServices.Marshal.Copy()メソッドを利用する。

最後に描画する。
        // Form上に配置した Panel に描画している。
        e.Graphics.DrawImage(img_, new Rectangle(panel1.Location, panel1.Size));

自分が調べてた中で、一番の肝は、配列からrawメモリへコピーするためのMarshal.Copy()メソッドの存在。BitmapDataのロックについては、DirectX のテクスチャメモリ(Surfaceメモリ)のロックと同様だし、1行単位のオフセットをStrideメンバで計算しなければならないことも同様。

閉じる コメント(0)

閉じる トラックバック(0)

DICOMTK 覚え書き(3-2)

リンクエラーが出るのは、プログラム本体のコンパイルオプションと、Libraryのコンパイルオプションが違うため。DCMTKのビルド時のオプションを修正して、ビルドしなおす必要がある。

CMakelists.txt で、それぞれの開発環境向け特有の設定している箇所(ここでは Visual Studio 2008)の、C ソースおよび C++ ソースのコンパイルオプションに、/MD または /MDd を加える。
/MD, /MDd は、コンパイラのコード生成で、MFC DLLを使ったコードを出力するオプションらしい。
具体的には、
    IF(CMAKE_GENERATOR STREQUAL "Visual Studio 8 2005")
      SET(CMAKE_C_FLAGS "/nologo /W3 /Gy")
      SET(CMAKE_C_FLAGS_DEBUG "/MDd /Z7 /Od")
      SET(CMAKE_C_FLAGS_RELEASE "/MD /O2")
      SET(CMAKE_C_FLAGS_MINSIZEREL "/MD /O2")
      SET(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Z7 /Od")
と、
    IF(CMAKE_GENERATOR STREQUAL "Visual Studio 8 2005")
      SET(CMAKE_CXX_FLAGS "/nologo /W3 /Gy")
      SET(CMAKE_CXX_FLAGS_DEBUG "/MDd /Z7 /Od")
      SET(CMAKE_CXX_FLAGS_RELEASE "/MD /O2")
      SET(CMAKE_CXX_FLAGS_MINSIZEREL "/MD /O2")
      SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MDd /Z7 /Od")
    ENDIF(CMAKE_GENERATOR STREQUAL "Visual Studio 8 2005")
のように修正。しかし、この修正だけでは、Libraryのビルドは問題なく終わるけど、ユーティリティーの類でリンクエラーが発生する。Libraryだけなら、このまま放っておいても問題ない気もするが、気持ち悪いので、ビルド時の define に、"_AFXDLL" を加える。そのためには、
ADD_DEFINITIONS(-D_REENTRANT -D_AFXDLL)
のようにして、全てのコンパイル時に "-D_AFXDLL"のコンパイルオプションが加わるように設定する。
Release版、Debug版それぞれでビルドして、インストールする。これまでと同様にLibraryだけ取り出して、ディレクトリ名を変更して保存しておく。

出来たLibraryを参照するようにすれば、MFC DLLを使用するアプリケーションとのリンクでも問題なくリンクできる。

ちなみに、この記事で、Library と英語で綴っているのは、なぜかカタカナで投稿しようとしたらエラーになって登録出来ないため。別のページを作ってカタカナだけの単語を投稿したところ問題なく出来るので、他のキーワードと組み合わせてYahooのNGキーワードに引っかかってしまったのかも。

閉じる コメント(0)

閉じる トラックバック(0)

DCMTK覚え書き(3-1)

次に、MFCを使ったアプリケーションでの利用を試す。

MFCと言っても、まずはコンソールアプリから。

AppWizardで、コンソールアプリのひな形を作成し、その途中でMFC DLLを利用するように指定してひな形を作成する。

プログラム本体は前回のものをコピー。

// DCMTestMFC.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include "DCMTestMFC.h"
#include <dcmtk/dcmdata/dctk.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// 唯一のアプリケーション オブジェクトです。

CWinApp theApp;

using namespace std;

const char* DCMFILE = "XXXXX.dcm";

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	int nRetCode = 0;

	// MFC を初期化して、エラーの場合は結果を印刷します。
	if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
	{
		// TODO: 必要に応じてエラー コードを変更してください。
		_tprintf(_T("致命的なエラー: MFC の初期化ができませんでした。\n"));
		nRetCode = 1;
	}
	else
	{
		DcmFileFormat dcmfile;
		DcmDataset *dataset;
		Uint16 rows, columns;
		DcmStack stack;
		if( dcmfile.loadFile( DCMFILE ).bad() )
		{
			printf( "DcmFileFormat::loadFile() failed.\n" );
			return 1;
		}
		dataset = dcmfile.getDataset();
		if( !dataset->findAndGetUint16( DCM_Columns, columns ).good() )
		{
			printf( "Get columns tag failed.\n" );
			return 1;
		}
		if( !dataset->findAndGetUint16( DCM_Rows, rows ).good() )
		{
			printf( "Get Rows tag failed.\n" );
			return 1;
		}
		printf( "cols = %d, rows = %d\n", columns, rows );
	}

	return nRetCode;
}


すると、
error C2371: 'SBYTE' : 再定義されています。異なる基本型です。
というコンパイルエラーがでる。

typedef char Sint8;
typedef Sint8 SBYTE;

という DCMTK内の定義と、

typedef signed char SBYTE;

という oledb.h 内の定義(MFC関係のヘッダをインクルードした関係で読み込まれたらしい)が矛盾しているとのこと。web を調べてみたが、解決策は見つからず、DCMTK内の Sint8 の typedef を書き換えることで回避することにする。char も signed char も実質的に同じだし、これで良しとする。

しかし、今度はリンクエラーが出る。DCMTK のライブラリのコンパイル設定と、アプリケーションのコンパイル設定(Cランタイムの選択)が異なるためらしい。

そこで DCMTK のビルドに再び戻ることになる。

閉じる コメント(0)

閉じる トラックバック(0)

[ すべて表示 ]


.

人気度

ヘルプ

Yahoo Image

  今日 全体
訪問者 0 13963
ブログリンク 0 1
コメント 0 0
トラックバック 0 0

ケータイで見る

モバイル版Yahoo!ブログにアクセス!

モバイル版Yahoo!ブログにアクセス!

URLをケータイに送信
(Yahoo! JAPAN IDでのログインが必要です)

1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31

標準グループ

登録されていません

開設日: 2007/10/8(月)


プライバシーポリシー -  利用規約 -  ガイドライン -  順守事項 -  ヘルプ・お問い合わせ

Copyright (C) 2012 Yahoo Japan Corporation. All Rights Reserved.