トップ回答者
Tiff画像ファイルのピクセル深度を複製したい

質問
-
いつも、お世話になっています。
Visual C# 2010、Windows7でTiff画像ファイルに文字情報を追記して、ファイルに書き出すプログラムを
作成し、ファイル出力は出来るのですが、入力ファイルのビットの深さが32ビットになってしまいます。
(入力は二値画像です)入力ファイルのビットの深さを複製するには、どうすれば良いのか、お教えください。
念のため、現時点のソースコードを添付します。
private void button1_Click(object sender, EventArgs e) { //ファイルから画像をビットマップに展開 Bitmap src = new Bitmap("C:\\test\\test.tif"); // 画像解像度を取得する float hRes = src.HorizontalResolution; float vRes = src.VerticalResolution; //Rect/Depthを取得する。 System.Drawing.Imaging.PixelFormat format = src.PixelFormat; Rectangle rect = new Rectangle(0, 0, src.Width, src.Height); //出力用ビットマップを作成する。 //Bitmap dest = new Bitmap(src.Width, src.Height); Bitmap dest = new Bitmap(src); dest.SetResolution(hRes, vRes); Graphics g = Graphics.FromImage(dest); //画像を描画する g.DrawImage(src, 0, 0, src.Width, src.Height); //追記の文字情報を描画する Font font = new Font("HGP明朝E", 22,FontStyle.Bold); string ctlnum = "AAAAA BBBBBB CCCCCCCCCC"; g.DrawString(ctlnum, font, Brushes.Black, 170, 0); //ファイルに書き出す。 dest.Save("C:\\test\\out.tif"); MessageBox.Show("Width=" + src.Width + ",Height=" + src.Height+",DPI="+hRes + "Depth="); }
回答
-
そのメッセージの通りでしょう。
Graphics クラスで扱うために一度、RGB24 などでビットマップを作り、その結果を読み込みながら、2 色のインデックス Bitmap に対して LockBits で書き込んでいくことになるかと思います。
(Graphics クラスのために増色し、文字列を描き込み、2 色に減色する)- 回答としてマーク MitsuoTAKEI 2013年1月13日 15:21
すべての返信
-
そのメッセージの通りでしょう。
Graphics クラスで扱うために一度、RGB24 などでビットマップを作り、その結果を読み込みながら、2 色のインデックス Bitmap に対して LockBits で書き込んでいくことになるかと思います。
(Graphics クラスのために増色し、文字列を描き込み、2 色に減色する)- 回答としてマーク MitsuoTAKEI 2013年1月13日 15:21
-
その減色処理はどんなコードでしょうか。
もし、GetPixel を使っているのなら LockBits にした方が速いですよ。あと考えられるとしたら、空の Bitmap に DrawString して、DrawString で必要だった矩形の部分で色が変わったところだけ上書きするという形にするとか。
// Clone して DrawString はできないと言ったことは先に知っているはずですよね?
// Graphics がサポートしないのですから、普通にはできません。- 編集済み AzuleanMVP, Moderator 2013年1月14日 0:37
-
Azuleanさま
ご回答ありがとうございます。
減色のコードは、以下のとおりです。
//CloneしてDrawStringというのは、
二値化した画像上で、DrawStringに
相当する機能を自前で実現したら
どうかなと、考えた次第です。
//新しい画像のピクセルデータを作成する byte[] pixels = new byte[bmpData.Stride * bmpData.Height]; for (int y = 0; y < dest.Height; y++) { for (int x = 0; x < dest.Width; x++) { //明るさが0.5以上の時は白くする if (0.5f <= dest.GetPixel(x, y).GetBrightness()) { //ピクセルデータの位置 int pos = (x >> 3) + bmpData.Stride * y; //白くする pixels[pos] |= (byte)(0x80 >> (x & 0x7)); } } } //作成したピクセルデータをコピーする IntPtr ptr = bmpData.Scan0; System.Runtime.InteropServices.Marshal.Copy(pixels, 0, ptr, pixels.Length); //ファイルに書き出す。 newImg.Save("C:\\test\\out.tif"); MessageBox.Show("Width=" + bmpData.Width + ",Height=" + bmpData.Height + ",DPI=" + hRes + ",Stride=" + bmpData.Stride);
-
二値化した画像上で、DrawStringに相当する機能を自前で実現したらどうかなと、考えた次第です。
難しそうな印象です。
何らかの方法で文字を描いた結果のピクセルごとの 0 or 1 を得ないといけませんが、自前で全部書くのは必要となる知識が多くなりそうなだと思っています。
近いことをやるのであれば、私が先に書いたように DrawString だけ別に実行してマージすることでしょうね。減色のコードは、以下のとおりです。
GetPixel は基本的に遅いので、LockBits で Scan0 から読み込むことを考えてください。
BGR24 なのか、RGB24 なのか、ARGB32 なのか、わかりませんが、そのピクセルフォーマットによる並び順で読み込むとマシになる…はず。
GetBrightness の実行効率は調べていませんが、まずは GetPixel を廃してさらに手を打たないといけないかを見るところからでしょうか。方法がわからない場合は「GetPixel LockBits」で調べるとそういったページが見つかるかと思います。
-
Azuleanさま
ご回答ありがとうございました。
遅くなりましてすみませんでした。
あれから、悪戦苦闘しまして、
なんとかGetPixel()なしで、
実現しましたが、まだ20秒近く
かかります。
やはり、1ピクセルずつの処理を
いかに効率よくするかだとわかり
ました。
良いアイデアがございましたら、
お教えください。
念のため、ソースコードを提示
させていただきます。
アドバイスありがとうございました。
//処理時間計測開始 Stopwatch sw = new Stopwatch(); sw.Start(); //画像をアンロック dest.UnlockBits(bmpData); byte[] pixels = new byte[(bmpData.Stride * bmpData.Height)/8]; int pv, dv; int SIx = 0; int DIx = 0; int RGB = 0; int threshold = 500; //32bpp画像の二値化 for (int y = 0; y < dest.Height; y++) { dv = 0; pv = 0x80; //全ピクセル繰り返し for (int x = 0; x < dest.Width; x++) { //32bppのRGB値を取得 RGB = srcBuf[SIx + 1] + srcBuf[SIx + 2] + srcBuf[SIx + 3]; if (RGB > threshold) dv += (byte)pv; if (pv == 1) { pixels[DIx++] = (byte)dv; dv = 0; pv = 0x80; } else pv >>= 1; //次のピクセルへ SIx += 4; if (pv != 0x80) pixels[DIx] = (byte)dv; } } //作成したピクセルデータをコピーする int numByte = (newImg.Width * newImg.Height) / 8; Marshal.Copy(pixels,0,bmpOut.Scan0,numByte); newImg.UnlockBits(bmpOut); //処理時間計測終了 sw.Stop(); long milsec = sw.ElapsedMilliseconds;
-
変数名が汚くて読みづらいです。
Bitmap: dest → newImg
BitmapData: bmpData → bmpOut
byte[]: srcBuf → pixelsBitmapData.Strideがほとんど考慮されていません。
pixelsのサイズ計算では使われていますが間違っています。bmpDataではなくbmpOutを参照する必要がありますし、1bppではストライドは入らないでしょう。逆にSIxはストライドを補正していません。(無駄にもpixelsはストライドを考慮しましたが)Marshal.Copy()で考慮されていないので、もしストライドがあればずれます。本題。見た感じ20秒もかからないと思うのですが。
私もunsafeを使いますが、使わない場合の別の方法として、srcBufをbyte[]ではなくint[]にします。そうすると配列アクセス・メモリアクセスが1/3になります。
その他、今回のパターンならloop unrollingでしょうか。
-
佐祐理さま
ご回答、ご指摘ありがとうございました。
pixelsのサイズ計算間違っていました。
ストライド、ポインターを使用しなくても、十分なパフォーマンスが得られましたので、
(0.061秒)これで、良しとします。
ありがとうございました。
最終のコードを添付します。
int width = dest.Width; int height = dest.Height; //32bpp画像の二値化 for (int y = 0; y < height; y++) { dv = 0; pv = 0x80; //全ピクセル繰り返し for (int x = 0; x < width; x++) { //32bppのRGB値を取得 RGB = srcBuf[SIx + 1] + srcBuf[SIx + 2] + srcBuf[SIx + 3]; if (RGB > threshold) dv += (byte)pv; if (pv == 1) { pixels[DIx++] = (byte)dv; dv = 0; pv = 0x80; } else pv >>= 1; //次のピクセルへ SIx += 4; if (pv != 0x80) pixels[DIx] = (byte)dv; } }