10

私は現在、OpenMPを使用して圧縮プロセスを高速化するlibpngに基づくC++でPNGエンコーダーを実装しようとしています。このツールは、さまざまな画像形式からPNGファイルを生成することができます。完全なソースコードをpastebin.comにアップロードしたので、これまでに行ったことを確認できます:http: //pastebin.com/8wiFzcgV

ここまでは順調ですね!さて、私の問題は、圧縮された画像データを含むIDATチャンクの生成を並列化する方法を見つけることです。通常、libpng関数png_write_rowは、PNGファイルに関するすべての情報を含む構造体へのポインターと、単一の画像行のピクセルデータを含む行ポインターを使用してforループで呼び出されます。

(Pastebinファイルの114-117行目)

//Loop through image
for (i = 0, rp = info_ptr->row_pointers; i < png_ptr->height; i++, rp++) {
    png_write_row(png_ptr, *rp);
}

次に、Libpngは次々に行を圧縮し、圧縮されたデータで内部バッファーを埋めます。バッファがいっぱいになるとすぐに、圧縮されたデータはIDATチャンクでイメージファイルにフラッシュされます。

私のアプローチは、画像を複数の部分に分割し、1つのスレッドで行1を10に圧縮し、別のスレッドで11から20に圧縮するという方法でした。しかし、libpngは内部バッファーを使用しているため、最初に思ったほど簡単ではありません:)どういうわけか、libpngに圧縮データをスレッドごとに個別のバッファーに書き込ませる必要があります。その後、バッファを正しい順序で連結して、出力イメージファイルにすべてまとめて書き込むことができるようにする方法が必要です。

それで、誰かがOpenMPとlibpngの微調整でこれをどのように行うことができるか考えていますか?どうもありがとうございます!

4

2 に答える 2

11

これはコメントには長すぎますが、実際には答えでもありません-

libpngを変更せずに(または独自のエンコーダーを作成せずに)これを実行できるかどうかはわかりません。いずれにせよ、PNG圧縮がどのように実装されているかを理解しておくと役に立ちます。

高レベルでは、画像はピクセルの行のセットです(通常はRGBAタプルを表す32ビット値)。

各行に個別にフィルターを適用できます。フィルターの唯一の目的は、行をより「圧縮可能」にすることです。たとえば、「サブ」フィルターは、各ピクセルの値をそのピクセルとその左側のピクセルとの差にします。このデルタエンコーディングは一見ばかげているように見えるかもしれませんが、隣接するピクセル間の色が類似している場合(これはそうなる傾向があります)、結果の値は、実際の色に関係なく非常に小さくなります。このようなデータは繰り返しが多いため、圧縮する方が簡単です。

レベルを下げると、画像データはバイトのストリームとして表示されます(行は互いに区別されなくなります)。これらのバイトは圧縮され、別のバイトストリームを生成します。圧縮されたデータは任意にセグメントに分割され(どこでも好きな場所に!)、それぞれ1つのIDATチャンクに書き込まれます(CRCチェックサムを含むチャンクごとのわずかな簿記のオーバーヘッドもあります)。

最も低いレベルは、圧縮ステップ自体である興味深い部分に私たちをもたらします。PNG形式は、zlib圧縮データ形式を使用します。zlib自体は、実際の圧縮データ形式であるdeflate(zipファイルもこれを使用します)の単なるラッパーです(Adler-32チェックサムを含むより多くの簿記があります)。deflateは、2つの圧縮手法をサポートしています。ハフマンコーディング(文字列内で異なるバイトが発生する頻度を考慮して、バイト文字列を表すために必要なビット数を最適な数に減らす)とLZ77エンコーディング(すでに出力に2回書き込まれる代わりに、参照されます)。

deflate圧縮の並列化で注意が必要なのは、一般に、入力ストリームの一部を圧縮するには、参照する必要がある場合に備えて、前の部分も使用できるようにする必要があることです。ただし、PNGが複数のIDATチャンクを持つことができるのと同様に、deflateは複数の「ブロック」に分割されます。あるブロックのデータは、以前にエンコードされた別のブロックのデータを参照できますが、参照する必要はありませんもちろん、そうでない場合は、圧縮率に影響する可能性があります)。

したがって、デフレートを並列化するための一般的な戦略は、入力を複数の大きなセクションに分割し(圧縮率が高いままになるように)、各セクションを一連のブロックに圧縮してから、ブロックを接着することです(ブロックは'常にバイト境界で終了するわけではありませんが、空の非圧縮ブロック(タイプ00)を配置できます。これにより、セクション間のバイト境界に位置合わせされます。ただし、これは簡単なことではなく、非常に低いレベルの圧縮を制御し(deflateブロックを手動で作成)、すべてのブロックにまたがる適切なzlibラッパーを作成し、これらすべてをIDATチャンクに詰め込む必要があります。

独自の実装を使用したい場合は、 PNGを圧縮するために明示的に作成した独自のzlib / deflate実装(およびその使用方法)を読むことをお勧めします(Haxe for Flashで記述されていますが、移植は比較的簡単です。 C ++)。Flashはシングルスレッドであるため、並列化は行いませんが、エンコーディングを複数のフレームにわたって実質的に独立したセクションに分割します(セクション間でバイト数の状態が保持されるため、「仮想的に」)。同じこと。

幸運を!

于 2012-05-31T05:00:11.200 に答える
5

私はついに圧縮プロセスを並列化するためにそれを手に入れました。キャメロンが彼の答えへのコメントで述べたように、私はそれらを結合するためにzstreamからzlibヘッダーを取り除く必要がありました。zlibは、バイト境界に書き込むためにすべてのチャンク(Z_FINISHで書き込む必要がある最後のチャンクを除く)に使用できるZ_SYNC_FLUSHと呼ばれるオプションを提供するため、フッターを削除する必要はありませんでした。したがって、後でストリーム出力を単純に連結できます。最終的に、adler32チェックサムはすべてのスレッドで計算され、結合されたzstreamの最後にコピーされる必要があります。

結果に興味がある場合は、https://github.com/anvio/png-parallelで完全な概念実証を見つけることができます。

于 2012-07-01T04:58:01.040 に答える