Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9547

TwainDotNetが内部で32ビットBitmapを作成する部分を8ビットにも対応

$
0
0

1. はじめに

TwainDotNetでグレースケールをスキャンすると、内部で32ビットBitmapが生成され、保存した時にファイルサイズが大きくなります。このため、グレースケールの場合には8ビットで保存するようにTwainDotNetを変更してみました。

2. 変更前ソース

BitmapコンストラクタにPixelFormatの指定がなく、必ず32ビットのBitmapが作成されるようになっています。

BitmapRenderer.cs抜粋
publicBitmapRenderToBitmap(){Bitmapbitmap=newBitmap(_rectangle.Width,_rectangle.Height);using(Graphicsgraphics=Graphics.FromImage(bitmap)){IntPtrhdc=graphics.GetHdc();try{Gdi32Native.SetDIBitsToDevice(hdc,0,0,_rectangle.Width,_rectangle.Height,0,0,0,_rectangle.Height,_pixelInfoPointer,_bitmapPointer,0);}finally{graphics.ReleaseHdc(hdc);}}bitmap.SetResolution(PpmToDpi(_bitmapInfo.XPelsPerMeter),PpmToDpi(_bitmapInfo.YPelsPerMeter));returnbitmap;}

3. 変更後ソース

元データが8ビットの場合に8ビットのビットマップを構築しています。詳細は後述します。

BitmapRenderer.cs抜粋
publicBitmapRenderToBitmap(){if(_bitmapInfo.BitCount==8){intsizeBitmapFileHeader=Marshal.SizeOf(typeof(BitmapFileHeader));BitmapFileHeaderbitmapFile=newBitmapFileHeader();bitmapFile.Type='M'*256+'B';bitmapFile.Size=(_pixelInfoPointer.ToInt32()-_bitmapPointer.ToInt32())+sizeBitmapFileHeader+_bitmapInfo.SizeImage;bitmapFile.Reserved1=0;bitmapFile.Reserved2=0;bitmapFile.OffBits=(_pixelInfoPointer.ToInt32()-_bitmapPointer.ToInt32())+sizeBitmapFileHeader;IntPtr_bitmapFilePointer=Marshal.AllocHGlobal(sizeBitmapFileHeader);Marshal.StructureToPtr(bitmapFile,_bitmapFilePointer,true);byte[]buffer=newbyte[bitmapFile.Size];Marshal.Copy(_bitmapFilePointer,buffer,0,sizeBitmapFileHeader);Marshal.Copy(_bitmapPointer,buffer,sizeBitmapFileHeader,bitmapFile.Size-sizeBitmapFileHeader);MemoryStreamms=newMemoryStream(buffer);Bitmapbitmap=null;try{bitmap=newBitmap(ms);}catch(Exceptionex){throw;}bitmap.SetResolution(PpmToDpi(_bitmapInfo.XPelsPerMeter),PpmToDpi(_bitmapInfo.YPelsPerMeter));returnbitmap;}else{Bitmapbitmap=newBitmap(_rectangle.Width,_rectangle.Height);using(Graphicsgraphics=Graphics.FromImage(bitmap)){IntPtrhdc=graphics.GetHdc();try{Gdi32Native.SetDIBitsToDevice(hdc,0,0,_rectangle.Width,_rectangle.Height,0,0,0,_rectangle.Height,_pixelInfoPointer,_bitmapPointer,0);}finally{graphics.ReleaseHdc(hdc);}}bitmap.SetResolution(PpmToDpi(_bitmapInfo.XPelsPerMeter),PpmToDpi(_bitmapInfo.YPelsPerMeter));returnbitmap;}}

既存のBitmapInfoHeader.csを元に、下記のファイルを同じように作成しました。

BitmapFileHeader.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.Text;usingSystem.Runtime.InteropServices;usingSystem.Drawing;usingSystem.Diagnostics;namespaceTwainDotNet.Win32{[StructLayout(LayoutKind.Sequential,Pack=2)]publicclassBitmapFileHeader{publicshortType;publicintSize;publicshortReserved1;publicshortReserved2;publicintOffBits;publicoverridestringToString(){returnstring.Format("t:{0} s:{1} r1:{2} r2:{3} o:{4}",Type,Size,Reserved1,Reserved2,OffBits);}}}

4. 解説

変更前は下記のようにBitmapコンストラクタにPixelFormatの指定がなく、必ず32ビットのBitmapが作成されるようになっています。

Bitmapbitmap=newBitmap(_rectangle.Width,_rectangle.Height);

ここの前に、ビット数が8(グレースケール)の場合に分岐を加えます。

if(_bitmapInfo.BitCount==8){

但し、BitmapコンストラクタにPixelFormat.Format8bppIndexedを指定してGraphics.FromImage()を行うと例外が発生してGraphicsオブジェクトを作成できません。
このため、GraphicsオブジェクトからgetHdc()を行ってhDCを取得し、SetDIBitsToDevice()を行うという手が使用できません。

失敗例
if(_bitmapInfo.BitCount==8){Bitmapbitmap=newBitmap(_rectangle.Width,_rectangle.Height,PixelFormat.Format8bppIndexed);using(Graphicsgraphics=Graphics.FromImage(bitmap)){

これを回避するため、MemoryStreamからBitmapを作成しています。但し、元データにBitmapFileHeaderがないため、自前で作成しています。
BitmapFileHeader作成、byte配列の先頭にコピーします。

intsizeBitmapFileHeader=Marshal.SizeOf(typeof(BitmapFileHeader));BitmapFileHeaderbitmapFile=newBitmapFileHeader();bitmapFile.Type='M'*256+'B';bitmapFile.Size=(_pixelInfoPointer.ToInt32()-_bitmapPointer.ToInt32())+sizeBitmapFileHeader+_bitmapInfo.SizeImage;bitmapFile.Reserved1=0;bitmapFile.Reserved2=0;bitmapFile.OffBits=(_pixelInfoPointer.ToInt32()-_bitmapPointer.ToInt32())+sizeBitmapFileHeader;IntPtr_bitmapFilePointer=Marshal.AllocHGlobal(sizeBitmapFileHeader);Marshal.StructureToPtr(bitmapFile,_bitmapFilePointer,true);

BitmapInfoHeaderとカラーテーブルとビットイメージをBitmapFileHeaderの次にコピーします。

byte[]buffer=newbyte[bitmapFile.Size];Marshal.Copy(_bitmapFilePointer,buffer,0,sizeBitmapFileHeader);Marshal.Copy(_bitmapPointer,buffer,sizeBitmapFileHeader,bitmapFile.Size-sizeBitmapFileHeader);

MemoryStreamを作成し、そこからBitmapを作成します。
冗長なtry~catchはデバッグ時に例外を確認するのに使用していました。

MemoryStreamms=newMemoryStream(buffer);Bitmapbitmap=null;try{bitmap=newBitmap(ms);}catch(Exceptionex){throw;}

5. 補足

ざっと確認したばかりで、詳細なテストは未実施です。
2ビットには対応していません。


Viewing all articles
Browse latest Browse all 9547

Trending Articles