[C#] 画像からExif情報を削除するRemovePropertyItemメソッドの挙動がおかしい件

Bitmapクラス (Imageクラスから派生) のメソッドを眺めていたら、プロパティ項目を削除するメソッドを発見。

Image.RemovePropertyItem メソッド (System.Drawing)
http://msdn.microsoft.com/ja-jp/library/system.drawing.image.removepropertyitem(v=vs.110).aspx

Exif情報などメタデータはプロパティ項目として画像に格納されているので、画像ファイルを読み込んでこのメソッドでプロパティ項目を削除して保存しなおせば、Exif情報が削除された画像ファイルが作成できるはず。そう思って試してみたのだけどうまくいかない。画像ファイルを読み込んだBitmapインスタンスからはプロパティ項目を削除できるのだが、保存しなおした画像ファイルには削除したはずのプロパティ項目が復活しているのだ。おかしい。

テストコード

以下のコードで挙動を調べた。

結果と個人的な推察

インスタンス上で変更したプロパティ情報は、保存したファイルにも反映される。しかし、削除したプロパティ情報は保存したファイルでは復活してしまう。これは.NETのバグだろうか。いや、文書には書かれていないけど、恐らくは仕様だろう。僕の予想は以下の通り。

画像データに加えてメタ情報が格納されることで画像ファイルは以前に増して肥大化している。極端な例だけど、わずか5×5ピクセルの画像に10GBのメタデータが付加されることだってあり得る。もしImageクラスに画像ファイルの内容を全て保持するようにすると、オブジェクトが肥大化してメモリを圧迫するのは明らか。そこでImageクラスの設計者は、クラスの機能に必要なデータだけを保持することにした。その弊害として、オブジェクトを別ファイルに書き出しすと、元ファイルにあったメタ情報が継承されないという問題が起こる。これを回避するために、ファイルを書き出す際には元ファイルからメタ情報を直接転記するようにしたのだろう。

僕の予想が当たっているかどうかはわからないけど、もし仕様ならばきちんと文書に記して欲しい。挙動がはっきりわからなければ使えない。

[C#] スプラッシュスクリーンを表示する

Windowsアプリにスプラッシュスクリーンを表示したい場合がある。その場限りの使用を想定した自前ユーティリティには必要ないけど、商品としてリリースされる予定の受託アプリには必ずと言っていいほどスプラッシュスクリーンの表示が求められる。

以下のページにわかりやすい具体例がある。

@IT:.NET TIPS Windowsアプリケーションでスプラッシュ・スクリーンを表示するには?
http://www.atmarkit.co.jp/fdotnet/dotnettips/223splashscrn/splashscrn.html

スプラッシュスクリーンを表示するコードを以下に抜粋。留意点は ①スプラッシュスクリーンはモードレスフォームとして表示する ②スプラッシュスクリーン表示中のバックグラウンド処理ではDoEvents()を回す ③スプラッシュスクリーンを閉じた後はメインフォームにフォーカスを渡す、の3点。

[C#] フォルダをコピーする

あるディレクトリを別のディレクトリにサブディレクトリを含めてコピーする。

[C#] フォームに常に入力フォーカスを当てる

WindowsマシンをKIOSK端末のように使う想定で、アプリが常に入力フォーカスを持つようにしたいというニーズあり。すなわち、バックグラウンドで動いているアプリがメッセージを表示してもフォーカスを移すことなく、メインのアプリが常に前面かつ入力フォーカスを持つようにしたい。

まず思いついたのは、アプリのメインフォームにTopMost(常に最前面に表示)の属性をセットすること。試してみると、確かにフォームは常に最前面に表示されるけど、キーボードの入力フォーカスは他のアプリに移ってしまうことがあった。入力フォーカスをアプリに取り戻すためには、マウスでフォームをクリックしてやる必要がある。これでは要件が満足されない。

それじゃと思いついたのは、キーボードをグローバルフックすること。ネットを検索してみたら同じような事例が見つかる。だけどWin32 APIを呼んでやらないといけないのでちょっと面倒かな。もっと手軽にできる方法は無いだろうか。

C# フォームが非アクティブな状態でもキー判定を行いたい – Yahoo!知恵袋
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1080106570

ぬるり。: グローバルフック・ザ・キーボード
http://hongliang.seesaa.net/article/7539988.html

そこで思いついたのは以下の方法。タイマーを使って周期的に自分をアクティブ化することで、常にこのフォームがキーボードフォーカスを得る。インターバルを短くし過ぎるとタイマー処理のオーバーヘッドが大きくなるので注意が必要かも。あまりマナーの良い方法じゃないので自慢できたものじゃないが、手軽に実装したければこういう方法もありということで。

[C#] C++からC#のDLLを呼ぶ方法

以前のエントリで「C#からC++のDLLを呼ぶ方法」を書いた。今回はその逆で「C++からC#のDLL関数を呼ぶ方法」を考える。

これが必要になるのは次のようなシチュエーション。C++で書かれたアプリがあり、今回そのアプリに機能を追加したい。もちろんC++で地道にコーディングすれば機能は追加できるけど工数は多くなる。C#と.NETを使えば工数が少なくなるのは明らかなので、今更C++でコーディングするのはだるい。サクサクっとC#でコーディングして、それをC++から呼べると楽だよね?

方法としては、C++からC#のDLLを直接呼ぶことはできないので、C#で作成したCOMをC++から呼ぶことになる。以下にテストコードを示す。

テストDLL (C#) のソース

DLLを作成するために、Visual C#で「クラスライブラリ」としてプロジェクトを作成する。

プロジェクトの[プロパティ]-[アプリケーション]-[アセンブリ情報]設定で、[アセンブリをCOM参照可能にする]をチェックしておく。

VS2010-properties

そして、COMをビルドしてRegasm.exeを使ってシステムに登録する。

Regasm.exe (アセンブリ登録ツール)
http://msdn.microsoft.com/ja-jp/library/tzat5yw6(v=vs.110).aspx

これでC#(COM)側の準備は完了。

テストアプリ (C++) のソース

C++からC#で書かれたCOMの関数を呼び出す。

参考サイト

マネージドDLLとの接続、ActiveX(COM)による接続、アンマネージドDLLとの接続
http://satoshi3.sakura.ne.jp/memo/connect_dll.htm

C++からC#のdllを参照する際、引数内に構造体があった場合の処理 – C・C++ – 教えて!goo
http://oshiete.goo.ne.jp/qa/5493528.html?from=recommend

[C#] C#からC++のDLLを呼ぶ方法

最近は新規Windowsアプリ開発はC#でやるようになった。だけど、「既存のC/C++ライブラリ使い回す代わりに工数減らして」と発注元から要求されたり、サードパーティから購入したC言語DLLの商用ライブラリをリンクしないといけなかったりと、いまだにC/C++との連携は避けられない。

というわけで、C#からC/C++のDLL関数を呼び出す手順を覚え書きとして記しておく。

サンプルコード

以下のような簡単なテスト関数をC++で書いてDLLをビルドした。このDLLの関数をC#のテストアプリから呼び出してみる。

参考サイト

チュートリアル: ダイナミック リンク ライブラリの作成と使用 (C++)
http://msdn.microsoft.com/ja-jp/library/ms235636.aspx

pInvokeStackImbalance MDA
http://msdn.microsoft.com/ja-jp/library/0htdy0k3(v=vs.110).aspx

[C#] Waveファイルを再生する

アプリでWaveファイルを再生するには、System.Media.SoundPlayerクラスを使うと簡単。

ハードディスク上のWaveファイルを再生する

アプリにリソースとして組み込んだWaveファイルを再生する

[C#] リストから別のリストの内容を取り除く

例えば会員名簿と退会会員名簿がテキストファイルに保存されていて、前者から後者を除外して有効会員名簿を作成するとしよう。それぞれの名簿を文字列リストに読み込んでどう処理するか考察してみたい。ここでは処理時間に関しては議論しない。

手っ取り早いのはRemoveメソッドを使って、リスト中で最初に見つかった要素を削除する方法だろうか。

この方法だと、もし会員名簿に重複して会員が登録されている場合(それが許される場合)、最初の登録しか削除されない。もし名簿が重複を許すことが明らかな場合(そういう仕様ならば)、ぞれぞれのリストから事前に重複要素を削除してから、2つのリストをぶつければ良いだろう。重複要素を削除するにはDistinctメソッドが使える。

参考サイト

Enumerable.Distinct(TSource) メソッド (IEnumerable(TSource)) (System.Linq)
http://msdn.microsoft.com/ja-jp/library/bb348436(v=vs.110).aspx

[C#] ビットマップにピクセル単位で高速にアクセスするには (GetPixel/SetPixel vs BitmapData 速度比較)

Bitmapクラスにはピクセル単位のアクセス関数 SetPixel/GetPixel が用意されているけど、ネットを見るとこれら関数はあまり速くないらしい。これら関数を使う代わりに、ビットマップデータをアンマネージ配列にコピーした 上で処理する方法が推奨されているみたい。画像処理ソフトを書く上で処理速度は重要だ。両者でどれぐらいの速度差があるのか調べてみた。

フルカラー画像を読み込んだ後、ピクセル単位にグレイスケールに変換するのに要した時間をミリ秒単位で計測。グレイスケールへの変換には、YCrCb変換のY値の計算式を使った。

方法1: SetPixel/GetPixelを使う

方法2: ビットマップデータをアンマネージ配列にコピーしてから処理する

方法3: ビットマップをシステムメモリにロックして直アクセスする (バイト単位)

方法2のMarshal.Copyのコストが掛かっているかも?という懸念から。

方法4: ビットマップをシステムメモリにロックして直アクセスする (ピクセル単位)

方法3では1ピクセルごとにMarshal.ReadByte/WriteByteが3回ずつ呼ばれるので、これら関数のオーバーヘッドが掛かっているかも?という懸念から。

テストに使ったデータ

画像1 lena.jpg (400×400ピクセル)
画像2 ff_x_e1_004.JPG (4896×3264ピクセル) – 富士フィルムサイトより拝借

フジノンレンズ XF18-55mmF2.8-4 R LM OIS : サンプル画像 | 富士フイルム
http://fujifilm.jp/personal/digitalcamera/x/fujinon_lens_xf18_55mmf28_4_r_lm_ois/sample_images/

テスト結果

処理時間は以下のようになった。方法3と4は、アンマネージ配列をアロケートしてデータをコピーするコストを考えた代替方法だったのだけど、画像が大きくなってもそのコストは大したことはなかったので気にする必要はなさそう。

# 処理内容 画像1(400×400ピクセル) 画像2(4896×3264ピクセル)
方法1 GetPixel/SetPixel関数を使用 597ms 28,187ms
方法2 アンマネージ配列にコピーした上で処理 10ms 268ms
方法3 システムメモリにロックして直アクセス (バイト単位) 19ms 523ms
方法4 システムメモリにロックして直アクセス (ピクセル単位) 15ms 400ms

環境: Windows 7 Ultimate SP1 (64bit), Intel Core i7 3.4GHz, RAM 16GB, Visual Studio 2012

[C#] 画像ファイルからExif情報を読み出す

C#でExif情報を読み出すプログラムを書けないか調べたところ、System.Drawing.Image.PropertyItems プロパティを使って画像に格納されたメタデータを取得できることがわかった。

Image.PropertyItems プロパティ (System.Drawing)
http://msdn.microsoft.com/ja-jp/library/system.drawing.image.propertyitems(v=vs.90).aspx

Exif情報をテキストファイルにダンプするサンプルを書いてみた。

Exifデータの意味については仕様書を参照。

JEITA / JEITA規格・AV電子機器部門(デジタルカメラ<電子カメラ一般>関係)
http://www.jeita.or.jp/cgi-bin/standard/list.cgi?cateid=1&subcateid=4

Exchangeable image file format for digital still cameras: Exif Version 2.3
http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf