1. はじめに
TwainDotNetでグレースケールをスキャンすると、内部で32ビットBitmapが生成され、保存した時にファイルサイズが大きくなります。このため、グレースケールの場合には8ビットで保存するようにTwainDotNetを変更してみました。
2. 変更前ソース
BitmapコンストラクタにPixelFormatの指定がなく、必ず32ビットのBitmapが作成されるようになっています。
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ビットのビットマップを構築しています。詳細は後述します。
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を元に、下記のファイルを同じように作成しました。
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ビットには対応していません。