ホームページ>開発ツール>Xojo / Real Studio Trial and Error・zlibを使って圧縮/伸長してみる
Xojo / Real Studio Trial and Error
目次
zlibを使って圧縮/伸長してみる
はじめに
以下は、Xojo Cocoaビルドについての話題です。
zlibを使った圧縮/伸長について試してみました。
なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + OS X 10.13.6 High Sierra)
方針
zlibは一般的なデータ圧縮/伸長ライブラリで、Macにも標準でインストールされています。
使い方は、以下のサイトを参考にさせて頂きました。(他にも沢山あります。)
参考サイト(1):zlib の使い方
上記サイトによると、圧縮/伸長の方法としては、分割方式(deflateとinflate)と一括方式(compressとuncompress)があるとのことです。
Xojoでの利用にあたり、そのまま利用できるものがないか、まずは例によってmacoslibを調べたところ、compress2とuncompressを使ったメソッドは見つかりました。
deflateとinflateについては、macoslibになかったため、ウェブ上で探したのですが、Objective-cでの例がいくつか見つかったものの、フルセットではなさそう(?)なので、今回は公式サイトのサンプルソースコードを元に、アレンジすることにしました。
参考サイト(2):zlib Usage Example(ソースコードのみは、zpipe.cのリンクからダウンロード可)
他に、zlib.hが手元にあると便利だが、Mac上ではどこにあるのかがよく分からなかった(/opt/local/include/zlib.hは見つかったが、バージョンが古い?)ので、例えば以下が参考になる。また、これは実装してから判明したことですが、初期化のためのメソッド(deflateInitとinflateInit)が見つからないという事態に。
参考サイト(3):GitHub - madler/zlib: A massively spiffy yet delicately unobtrusive compression library.
あれこれ調べていたら、以下のサイトがヒットしました。
参考サイト(4):dylib がいなくなった、tbd ってなんだ? - Qiita
ここの、シンボル一覧のスクリーンショットには、deflateInitとinflateInitはなく、代わりにdeflateInit_とinflateInit_があります。そこで、deflateInit_とinflateInit_を使ってみたところ、とりあえずうまくいっているようだったので、これを使うことにしました。(本来は内部関数で、外部から使うべきではないようなので、本当にこれでいいかは要検証。)
参考サイト(3)の本来の趣旨である、dylibからtbdへの変更は、Xojoの場合は不要のようで、"libz.dylib"を指定すればOK。なお、deflateInit_とinflateInit_は、バージョンとz_stream構造体のサイズを指定する必要があるのですが、バージョンの方は必ずしも使用中のもの(おそらく1.2.11と思われるが、詳細不明)と一致しなくても問題ないようなのですが、構造体のサイズの方は、uLong型(total_in, total_out, adler, reserved)を(当初64bitかと思っていたのですが、)32bitにしないと、動作しませんでした。
これらを踏まえて今回は、(1)分割方式、(2)一括方式、の2種類を試してみることにしました。
それぞれの仕様は以下の通りとしました。
共通
分割方式
- アプリケーションの機能は、(取り込んだ画像ファイル中の)画像データを圧縮して書き出し、読み込んで伸長後に表示する。
- 入力ファイル形式としては、一般的な画像フォーマットに対応する。(jpegやPNGも対応するが、実用的ではない。)
- 出力ファイル形式は、圧縮結果をそのままバイナリーストリームとして書き出す、独自形式とする。(拡張子はここではpiczとした。)
- 画像ファイルの取り込みは、イメージウェルへのドラッグ&ドロップとする。
一括方式
- 圧縮と伸長のアルゴリズムは、公式サイトのサンプルソースコードを元にアレンジする。
- 圧縮前のデータサイズはこの方式では不要だが、一括方式との互換性のため、ファイルの先頭に保存する。
- 圧縮と伸長のアルゴリズムは、macoslibのメソッドを使わせてもらってアレンジする。
- 圧縮前のデータサイズが必要なので、ファイルの先頭に保存する。
Xojoでの実装
今回のサンプルは互換性検証の利便性を考えて、二つ分をまとめて一つのプロジェクトとしています。
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、圧縮/伸長が機能することを確認しました。
- Xojoで新規プロジェクトを作成
- ファイルタイプグループをプロジェクトに追加(Name:FileTypes1)し、ファイルタイプの追加(書類に+アイコン)を押し、名前:PictZlib、DisplayName:PictureZlib、Extensions:.piczを追加。
- Window1に、ImageWell(Name:ImageWell1)、PushButton4個(Name:PushButton1, Caption:Save 分割、Name:PushButton2, Caption:Open 分割、Name:PushButton3, Caption:Save 一括、Name:PushButton4, Caption:Open 一括)を追加
- ImageWell1に、言語リファレンス>>ImageWell>Examples>drag and dropの項のコードを記述。
- 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action SaveFileDeflate() End Sub
- 以下をPushButton2にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action OpenFileInflate() End Sub
- 以下をPushButton3にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action SaveFileCompress() End Sub
- 以下をPushButton4にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action OpenFileUncompress() End Sub
- 以下をWindow1にペースト
Protected Sub OpenFileInflate() Dim f As FolderItem // ファイル保存ダイアログ Dim dlg As New OpenDialog dlg.Filter = FileTypes1.PictZlib f=dlg.ShowModalWithin(self) if f=nil then return end if // ファイルを読み込みながら伸長 Dim fx As New Xojo.IO.FolderItem(f.NativePath.ToText) Dim xmb As Xojo.Core.MemoryBlock = OpenFileInflateZlib(fx) if xmb = nil then return end if // Xojo.Core.MemoryBlockに格納後、MemoryBlockに変換 Dim temp As MemoryBlock = xmb.Data Dim mb As New MemoryBlock(xmb.Size) mb.StringValue(0, mb.Size) = temp.StringValue(0, mb.Size) // データをイメージウェルにセット ImageWell1.Image = Picture.FromData(mb) End Sub
- 以下をWindow1にペースト
Protected Function OpenFileInflateZlib(fx As Xojo.IO.FolderItem) as Xojo.Core.MemoryBlock Const CHUNK As Integer = 16384 Dim ret, offset, lngh As Integer Dim have As UInteger Dim strm As z_stream Dim out As new Xojo.Core.MemoryBlock(CHUNK) Dim dest As Xojo.Core.MutableMemoryBlock = New Xojo.Core.MutableMemoryBlock(0) strm.zalloc = nil strm.zfree = nil strm.opaque = nil Dim ver As Xojo.Core.MemoryBlock = Xojo.Core.TextEncoding.UTF8.ConvertTextToData("1.2.11") // zlibのバージョン soft declare function inflateInit_ lib "libz.dylib" (byRef strm as z_stream, ver as Ptr, size as Integer) as integer ret = inflateInit_(strm, ver.Data, strm.Size) if ret<>0 then // -4 = Z_MEM_ERROR メモリ不足でmalloc()できなかった // -5 = Z_BUF_ERROR 出力バッファのサイズ不足 // -6 = Z_VERSION_ERROR 呼び出し側の想定したzlibライブラリバージョンと互換性がない、またはz_streamのサイズが一致しない return nil end if Dim bs As Xojo.IO.BinaryStream = Xojo.IO.BinaryStream.Open(fx, Xojo.IO.BinaryStream.LockModes.Read) Dim dmy As UInt64 = bs.ReadUInt64 // 伸長後のデータサイズの読み込み(この方式では使わないが、一括方式と互換性を持たせるための処置) offset=8 // データサイズのバイト分、読み込み開始位置を移動 soft declare function inflate lib "libz.dylib" (byRef strm as z_stream, fin as Integer) as integer soft declare sub inflateEnd lib "libz.dylib" (byRef strm as z_stream) do if offset+CHUNK>bs.Length then lngh=bs.Length-offset else lngh=CHUNK end if strm.avail_in = lngh if strm.avail_in = 0 then break end if Dim inn As new Xojo.Core.MemoryBlock(lngh) bs.Position=offset inn=bs.Read(lngh) strm.next_in = inn.Data offset=offset+CHUNK do strm.avail_out = CHUNK strm.next_out = out.Data ret = inflate(strm, 0) // 0 = Z_NO_FLUSH select case ret case 2 // 2 = Z_NEED_DICT ret = -3 // Z_DATA_ERROR case -3 // -3 = Z_DATA_ERROR case -4 // -4 = Z_MEM_ERROR inflateEnd(strm) return nil end select have = CHUNK - strm.avail_out Dim out2 As new Xojo.Core.MemoryBlock(have) out2=out.Mid(0,have) dest.Append(out2) out2=nil loop until strm.avail_out <> 0 inn=nil loop until ret = 1 // 1 = Z_STREAM_END inflateEnd(strm) return dest End Function
- 以下をWindow1にペースト
Protected Sub OpenFileUncompress() Dim f As FolderItem // ファイル保存ダイアログ Dim dlg As New OpenDialog dlg.Filter = FileTypes1.PictZlib f=dlg.ShowModalWithin(self) if f=nil then return end if // ファイルの内容を読み込み Dim fx As New Xojo.IO.FolderItem(f.NativePath.ToText) Dim bs As Xojo.IO.BinaryStream = Xojo.IO.BinaryStream.Open(fx, Xojo.IO.BinaryStream.LockModes.Read) // ファイルを読み込みモードで開く Dim ll As UInt64 = bs.ReadUInt64 // 伸長後のデータサイズの読み込み Dim source As Xojo.Core.MemoryBlock = bs.Read(bs.Length-8) // 圧縮されたデータの読み込み bs.Close // ファイルクローズ // 伸長 Dim xmb As Xojo.Core.MemoryBlock = OpenFileUncompressZlib(source,ll) if xmb = nil then return end if // Xojo.Core.MemoryBlockに格納後、MemoryBlockに変換 Dim temp As MemoryBlock = xmb.Data Dim mb As New MemoryBlock(xmb.Size) mb.StringValue(0, mb.Size) = temp.StringValue(0, mb.Size) // データをイメージウェルにセット ImageWell1.Image = Picture.FromData(mb) End Sub
- 以下をWindow1にペースト
Protected Function OpenFileUncompressZlib(source as Xojo.Core.MemoryBlock, expectedMaxSize as integer) as Xojo.Core.MemoryBlock // 一括圧縮したデータではexpectedMaxSizeで問題ないが、分割圧縮したデータではバッファサイズ不足になるので余裕をみている Dim dest As Xojo.Core.MemoryBlock = New Xojo.Core.MemoryBlock( expectedMaxSize*1.2 ) Dim destSize As UInt32 = dest.Size soft declare function uncompress lib "libz.dylib" (outBuf as Ptr, byref outLen as UInt32, inBuf as Ptr, inLen as UInt32) as integer Dim err As Integer = uncompress( dest.Data, destSize, source.Data, source.Size) if err=0 then return dest else // -3 = Z_DATA_ERROR zlibに伸長できないデータが入力された // -4 = Z_MEM_ERROR メモリ不足でmalloc()できなかった // -5 = Z_BUF_ERROR 出力バッファのサイズ不足 return nil end if End Function
- 以下をWindow1にペースト
Protected Sub SaveFileCompress() Dim f As FolderItem // ファイル保存ダイアログ Dim dlg As New SaveAsDialog dlg.Filter = FileTypes1.PictZlib f=dlg.ShowModalWithin(self) if f=nil then return end if // 画像をバイト列に変換 Dim data As MemoryBlock = ImageWell1.Image.GetData(Picture.FormatBMP) // バイト列をXojo.Core.MemoryBlockに変換 Dim source As new Xojo.Core.MemoryBlock(data,data.Size) // 圧縮 Dim mb As Xojo.Core.MemoryBlock = SaveFileCompressZlib(source) // levelを指定していないので、デフォルトの6が使われる if mb=nil then return end if // 圧縮結果をファイル書き出し Dim fx As New Xojo.IO.FolderItem(f.NativePath.ToText) Dim bs As Xojo.IO.BinaryStream = Xojo.IO.BinaryStream.Open(fx, Xojo.IO.BinaryStream.LockModes.Write) bs.WriteUInt64(data.Size) // 圧縮前のデータサイズ bs.Write(mb) // 圧縮データ bs.Close End Sub
- 以下をWindow1にペースト
Protected Function SaveFileCompressZlib(source As Xojo.Core.MemoryBlock, level as integer = 6) as Xojo.Core.MemoryBlock Dim dest As Xojo.Core.MemoryBlock = New Xojo.Core.MemoryBlock( source.Size * 1.001 + 12 ) Dim destSize As UInt32 = dest.Size soft declare function compress2 lib "libz.dylib" (outBuf as Ptr, byref outBufLen as UInt32, inBuf as Ptr, inBufLen as UInt32, level as Integer) as integer Dim err As Integer = compress2( dest.Data, destSize, source.Data, source.Size, level ) if err=0 then return dest.Left(destSize) else // -4 = Z_MEM_ERROR メモリ不足でmalloc()できなかった // -5 = Z_BUF_ERROR 出力バッファのサイズ不足 return nil end if End Function
- 以下をWindow1にペースト
Protected Sub SaveFileDeflate() Dim f As FolderItem // ファイル保存ダイアログ Dim dlg As New SaveAsDialog dlg.Filter = FileTypes1.PictZlib f=dlg.ShowModalWithin(self) if f=nil then return end if // 画像をバイト列に変換 Dim data As MemoryBlock = ImageWell1.Image.GetData(Picture.FormatBMP) // バイト列をXojo.Core.MemoryBlockに変換 Dim source As new Xojo.Core.MemoryBlock(data,data.Size) // 圧縮しながらファイル書き出し Dim fx As New Xojo.IO.FolderItem(f.NativePath.ToText) SaveFileDeflateZlib(fx,source,data.Size) End Sub
- 以下をWindow1にペースト
Protected Sub SaveFileDeflateZlib(fx As Xojo.IO.FolderItem, source As Xojo.Core.MemoryBlock, dsize As Integer) Const CHUNK As Integer = 16384 Dim ret, flush, offset, lngh As Integer Dim have As UInteger Dim strm As z_stream Dim out As new Xojo.Core.MemoryBlock(CHUNK) strm.zalloc = nil strm.zfree = nil strm.opaque = nil Dim ver As Xojo.Core.MemoryBlock = Xojo.Core.TextEncoding.UTF8.ConvertTextToData("1.2.11") // zlibのバージョン soft declare function deflateInit_ lib "libz.dylib" (byRef strm as z_stream, level as Integer, ver as Ptr, size as Integer) as integer ret = deflateInit_(strm, -1, ver.Data, strm.Size) // -1 = Z_DEFAULT_COMPRESSION if ret<>0 then // -4 = Z_MEM_ERROR メモリ不足でmalloc()できなかった // -5 = Z_BUF_ERROR 出力バッファのサイズ不足 // -6 = Z_VERSION_ERROR 呼び出し側の想定したzlibライブラリバージョンと互換性がない、またはz_streamのサイズが一致しない return end if soft declare function deflate lib "libz.dylib" (byRef strm as z_stream, fin as Integer) as integer soft declare sub deflateEnd lib "libz.dylib" (byRef strm as z_stream) Dim bs As Xojo.IO.BinaryStream = Xojo.IO.BinaryStream.Open(fx, Xojo.IO.BinaryStream.LockModes.Write) bs.WriteUInt64(dsize) // 圧縮前のデータサイズ(この方式では使わないが、一括方式と互換性を持たせるための処置) do if offset+CHUNK>source.Size then lngh=source.Size-offset flush=4 // 4 = Z_FINISH else lngh=CHUNK flush=0 // 0 = Z_NO_FLUSH end if strm.avail_in = CHUNK Dim inn As new Xojo.Core.MemoryBlock(lngh) inn=source.Mid(offset,lngh) strm.next_in = inn.Data offset=offset+CHUNK do strm.avail_out = CHUNK strm.next_out = out.Data ret = deflate(strm, flush) have = CHUNK - strm.avail_out Dim out2 As new Xojo.Core.MemoryBlock(have) out2=out.Mid(0,have) bs.Write(out2) // Write to File out2=nil loop until strm.avail_out <> 0 inn=nil loop until flush = 4 // 4 = Z_FINISH deflateEnd(strm) bs.Close End Sub
- 以下をWindow1の構造体(Structures)に追加
Structure z_stream next_in As Ptr avail_in As Uinteger total_in As Uinteger next_out As Ptr avail_out As UInteger total_out As Uinteger msg As Ptr state As Ptr zalloc As Ptr zfree As Ptr opaque As Ptr data_type As Integer adler As Uinteger reserved As Uinteger End Structure
おわりに
テストには、非圧縮フォーマットであるBMP形式のデータを用いました。
同一データを圧縮しても、分割と一括でファイルサイズが異なりますが、これは(1) 圧縮率が異なる、(2) 内部フォーマットが異なる、等が考えられますが、詳細は未調査です。
また、一括伸長する場合は、元データのサイズを指定する必要がありますが、一括圧縮したファイルでは問題ないのですが、分割圧縮したファイルでは、バッファサイズを多めに取らないとエラーになる、といった状況になります。(とりあえず20%位多めに取っておけば正常処理するので、詳細の追求はしていません。)
元ファイル(BMP形式) 圧縮ファイル(上段:分割、下段:一括) 76,854 byte
( 160 x 120 pixel )60,823 byte 52,763 byte 5,706,054 byte
( 1,500 x 951 pixel )1,709,248 byte 1,704,919 byte
この問題を除けば、互換性(圧縮と伸長の全ての組み合わせ)はとれているので、圧縮/伸長処理自体は動作しているとみて良さそうです。
ちなみに、jpeg形式のデータは、圧縮すると3倍近くの大きさになってしまいました。
最後に、分割と一括を併用する必要性はないのでどちらかを選ぶことになりますが、最近はメモリーの制約をあまり考えなくてもよくなっていることと、deflateInit_とinflateInit_の妥当性検証を回避できることから、一括が簡単で実用的かと。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):zlib の使い方
参考サイト(2):zlib Usage Example(ソースコードのみは、zpipe.cのリンクからダウンロード可)
参考サイト(3):GitHub - madler/zlib: A massively spiffy yet delicately unobtrusive compression library.
参考サイト(4):dylib がいなくなった、tbd ってなんだ? - Qiita
更新履歴
2019.03.26 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]