DOBON.NET DOBON.NETプログラミング掲示板過去ログ

Bitmapオブジェクトのファイル出力(G4圧縮TIFF形式)について

環境/言語:[WindowsXP/VB.NET/.NET Framework1.1]
分類:[.NET]

2003/11/20(Thu) 17:46:02 編集(投稿者)

Bitmapオブジェクトを使用し、編集した画像をファイル出力したいと考えています。
この際、保存形式をTIFFのG4圧縮形式で指定したいと考えています。

具体的な処理例としては、

@TIFFのG4圧縮形式のファイル(C:\Image1.tif)を読み込んだBitmapオブジェクトインスタンス(bmp1)を作成
A大きさがbmp1縦2個分のBitmapオブジェクトインスタンス(bmp2)を作成
Bbmp2にbmp1を縦に2個並べて描画
Cbmp2をTIFFのG4圧縮形式のファイル(C:\Image2.tif)としてファイル出力

といった感じで、コードを以下のように書きました。

----------------------------------------------------------------------------------------------

  Private Sub SaveG4TiffFile()

    'TIFFのG4圧縮形式のファイル(C:\Image1.tif)を読み込んだBitmapオブジェクトインスタンス(bmp1)を作成
    Dim bmp1 As New Bitmap("C:\Image1.tif")
    '大きさがbmp1縦2個分のBitmapオブジェクトインスタンス(bmp2)を作成
    Dim bmp2 As New Bitmap(bmp1.Width, bmp1.Height * 2, System.Drawing.Imaging.PixelFormat.Format32bppArgb)

    'bmp2からGraphicsオブジェクトを取得
    Dim gbmp2 As Graphics = Graphics.FromImage(bmp2)

    'bmp1の位置と大きさを示すRectangleの作成
    Dim rct_bmp1 As New Rectangle(0, 0, bmp1.Width, bmp1.Height)
    'bmp2に描画する位置と大きさを示すRectangleの作成
    Dim rct_gbmp2_1 As New Rectangle(0, 0, bmp1.Width, bmp1.Height)
    Dim rct_gbmp2_2 As New Rectangle(0, bmp1.Height , bmp1.Width, bmp1.Height)

    'bmp2にbmp1を縦に2個並べて描画
    gbmp2.DrawImage(bmp1, rct_gbmp2_1, rct_bmp1, GraphicsUnit.Pixel)
    gbmp2.DrawImage(bmp1, rct_gbmp2_2, rct_bmp1, GraphicsUnit.Pixel)

    'TIFFのイメージエンコーダ用のパラメータを設定
    Dim G4TiffEncoderParameters As System.Drawing.Imaging.EncoderParameters = New System.Drawing.Imaging.EncoderParameters(2)
    G4TiffEncoderParameters.Param(0) = New System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 1)
    G4TiffEncoderParameters.Param(1) = New System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Compression, Imaging.EncoderValue.CompressionCCITT4)

    'TIFFのイメージエンコーダに関する情報を取得する
    Dim TiffImageCodecInfo As System.Drawing.Imaging.ImageCodecInfo
    TiffImageCodecInfo = GetEncoderInfo("image/tiff")

    'bmp2をTIFFのG4圧縮形式のファイル(C:\Image2.tif)としてファイル出力
    bmp2.Save("C:\Image2.tif", TiffImageCodecInfo, G4TiffEncoderParameters)

    '使用したオブジェクトの解放
    gbmp2.Dispose()
    bmp1.Dispose()
    bmp2.Dispose()

  End Sub

  Private Shared Function GetEncoderInfo(ByVal mimeType As String)

    'MimeTypeで指定されたImageCodecInfoを探して返す関数
    Dim myEncoders() As System.Drawing.Imaging.ImageCodecInfo = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders()
    Dim myEncoder As System.Drawing.Imaging.ImageCodecInfo
    For Each myEncoder In myEncoders
      If myEncoder.MimeType = mimeType Then
        Return myEncoder
      End If
    Next
    Return Nothing

  End Function

----------------------------------------------------------------------------------------------


が、「'TIFFのイメージエンコーダ用のパラメータを設定」しているところで例外が発生しエラーとなってしまいます。
その際、「GDI+ で一般的なエラーが発生しました。」と表示されます。

    G4TiffEncoderParameters.Param(0) = New System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 1)
    G4TiffEncoderParameters.Param(1) = New System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Compression, System.Drawing.Imaging.EncoderValue.CompressionCCITT4)

の部分を

    G4TiffEncoderParameters.Param(0) = New System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 32)
    G4TiffEncoderParameters.Param(1) = New System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Compression, System.Drawing.Imaging.EncoderValue.CompressionLZW)

とすれば、ファイル出力はできますがLZW圧縮のTIFFになってしまい、うまくいきません。

また、G4圧縮TIFFの色深度は1ビットなので

    Dim bmp2 As New Bitmap(bmp1.Width, bmp1.Height * 2, System.Drawing.Imaging.PixelFormat.Format32bppArgb)

の部分を、

    Dim bmp2 As New Bitmap(bmp1.Width, bmp1.Height * 2, System.Drawing.Imaging.PixelFormat.Format1bppIndexed)

として作ればうまくいくかと思ったんですが、
こうするとbmp2のGraphicsオブジェクトが取得できず、画像の編集ができません。
この場合、BitmapオブジェクトのSetPixelメソッドも使えないようです。

色々、試行錯誤しましたがうまくいきません。
もし解決法をご存知の方がおられましたら、ご教授頂きたく存じます。
宜しくお願い致します。

■No1509に返信(103さんの記事)

次のページが参考になりそうですが、いかがでしょうか?

・Matthew Reynolds' .NET 247 : Newsgroup - could you make it very understandable?
http://www.dotnet247.com/247reference/msgs/22/112817.aspx
管理人様、深夜にも関わらずレスありがとうございます。
私ではきっとこのサイトには辿り着けなかったと思います。大変助かります。
まだ、翻訳しきれていないのですがコードサンプルが載っていそうなので検証してみます。
少し時間がかかってしまうかもしれませんが、解決しましたらまたこちらにご報告致します。
ありがとうございました。
■No1552に返信(103さんの記事)

今気づきましたが、C#のコードで、unsafeを使っているため、VB.NETではちょっと厳しいですね。

問題となるのは、Imageをいかにモノクロにするかという点だと思いますが、この解決法に関しては、次のページが参考になりそうです。

・319591 - HOW TO: Save a .gif File with a New Color Table By Using Visual Basic .NET
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q319591
2003/11/30(Sun) 01:40:34 編集(投稿者)

・Matthew Reynolds' .NET 247 : Newsgroup - could you make it very understandable?
http://www.dotnet247.com/247reference/msgs/22/112817.aspx

・319591 - HOW TO: Save a .gif File with a New Color Table By Using Visual Basic .NET
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q319591

上記2つのサンプルが大変参考になりました。
しかし、知識不足で半分ほどしか理解できていません。

自分なりにポイントをまとめましたが、

・8bit以下のbitmapオブジェクトにはSetPixelメソッドが使用できないのでメモリに直接書き込む必要がある?

・メモリに直接書き込むにはC#のUnsafeコードが必要?

・今回のケースでVB.NET上でC#のUnsafeと同様のことを実現するには、
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q319591
のサンプルにあるWin32APIのCopyArrayToを使う必要がある?

という認識をしています。

また、8bitbitmapと1bitbitmapのStrideの値が違うので、
上記2つのサンプル間の処理の違いもそのあたりがポイントになっていると思うのですが、C#のコードがあまり読めないためよく理解できていません。

今回は、
http://www.dotnet247.com/247reference/msgs/22/112817.aspx
のサンプルをもとにC#でクラスを作成し、
VB.NETのコードからそのクラスを使用することでなんとか実装できました。

今後勉強し、処理内容を理解していきたいと思います。

いろいろご教授頂き、大変参考になりました。
この度は本当にありがとうございました。
解決済み!
■No1630に返信(103さんの記事)

103さんのおっしゃっていることは、その通りだと思います。

私も2つのページ
http://www.dotnet247.com/247reference/msgs/22/112817.aspx
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q319591
を参考に、VB.NET用のコード(.NET 247のConvertTo1bbpIndexedのみ)を書いてみました。正しいかどうか自信はありませんが、ご参考になれば幸いです。

Public Shared Function ConvertTo1bbpIndexed( _
ByVal src As Bitmap, _
ByVal luminanceCutOff As Integer) As Bitmap
Dim width, height As Integer
Dim dest As Bitmap
Dim rect As Rectangle
Dim data As BitmapData
Dim pixels As IntPtr
Dim row, col As Integer

width = src.Width
height = src.Height

dest = New Bitmap(width, height, PixelFormat.Format1bppIndexed)
dest.SetResolution(src.HorizontalResolution, src.VerticalResolution)

rect = New Rectangle(0, 0, width, height)
data = dest.LockBits(rect, ImageLockMode.WriteOnly, _
PixelFormat.Format1bppIndexed)
pixels = data.Scan0

Dim srcPixel As Color
Dim pBits, pDestPixel As Integer
Dim bMask As Integer
Dim luminance As Double

If data.Stride > 0 Then
pBits = pixels.ToInt32()
Else
pBits = pixels.ToInt32() + data.Stride * (height - 1)
End If

Dim stride As Integer = Math.Abs(data.Stride)

Dim bits As Byte()
ReDim bits(height * stride)

For row = 0 To height - 1
For col = 0 To width - 1
srcPixel = src.GetPixel(col, row)
pDestPixel = row * stride + Int(col / 8)

bMask = &H80 / (2 ^ (col Mod 8))

luminance = srcPixel.R * 0.299 + srcPixel.G * 0.587 + _
srcPixel.B * 0.114

If luminance >= luminanceCutOff Then
bits(pDestPixel) = bits(pDestPixel) Or bMask
Else
bits(pDestPixel) = bits(pDestPixel) And (Not bMask)
End If

Next col
Next row

Win32API.CopyArrayTo(pBits, bits, height * stride)

dest.UnlockBits(data)

Return dest
End Function

Class Win32API
<DllImport("KERNEL32.DLL", EntryPoint:="RtlMoveMemory", _
SetLastError:=True, CharSet:=CharSet.Auto, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall)> _
Public Shared Sub CopyArrayTo(<[In](), MarshalAs(UnmanagedType.I4)> _
ByVal hpvDest As Int32, <[In](), Out()> ByVal hpvSource() As Byte, _
ByVal cbCopy As Integer)
End Sub
End Class
VB.NETでのサンプルありがとうございました。
実行してみましたが、正常に動作致しました。
http://www.dotnet247.com/247reference/msgs/22/112817.aspx
のC#のコードで生成されるデータと、
管理人様作成のVB.NETのコードで生成されるデータの
バイナリ比較を行いましたが、一致致しました。

VB.NETでのコード提供していただいたおかげで
ようやく処理内容が理解できました。

処理の流れをまとめてみると

・ソースbitmapオブジェクトと同じ大きさ、解像度の1ビットbitmapオブジェクトを作成
・1ビットbitmapをメモリにロックする
・1ビットbitmapのピクセルデータ先頭のメモリアドレスを取得
・1ビットbitmapのStride(1行分のピクセルデータに使用するbyte数)を取得
・1ビットbitmapに書き込むピクセルデータの作業領域を作成
 (その容量は1ビットbitmapのheight*Strideとなる)
・ソースbitmapのピクセルデータを1ピクセルずつ取得し、
 そのピクセルのRGBの値から輝度luminanceを求め、
 閾値luminanceCutOffをもとにそのピクセルの2値化判定を行う
・ビット単位で判定結果を作業領域に保存
・ソースbitmap全ピクセル分処理したら、
 作業領域のデータを1ビットbitmapのメモリ領域に上書き
・1ビットbitmapのロックを解除
・1ビットbitmapを戻り値として返す

といった感じでしょうか?

ポイントは1ビットbitmapでは8ピクセル分のデータが1byteに格納されるので
ビット単位の編集を行う必要があるということですよね?
And、Or、Notなどを使用することでそれが実現できるということは大変勉強になりました。

今回は、管理人様には色々とお世話になりました。
本当にありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板