画素キャッシュ • 画像プロパティとプロファイル • マルチスペクトル画像 • 大きな画像のサポート • 画素のストリーミング • 実行スレッド • 最適化された性能のための動的スロットリングの有効化 • 異種分散処理 • カスタム画像コーダー • カスタム画像フィルタ
オズの住人は、全能の魔法使いという恩人にすっかり満足していました。彼らは、その力が誰の・なぜの・どこのものかを問うことなく、その知恵と慈悲を受け入れました。オズの住人のように、幕の裏で何が起きているかを知らなくても ImageMagick が画像の変換・編集・合成を助けてくれることに満足しているなら、このセクションは飛ばして構いません。しかし、ImageMagick の背後にあるソフトウェアとアルゴリズムをもっと知りたいなら、読み進めてください。この議論を十分に活用するには、画像の用語に慣れ、コンピュータプログラミングに親しんでいる必要があります。
アーキテクチャの概要
画像は通常、矩形の画素領域とメタデータから成ります。画像を効率的に変換・編集・合成するには、領域内(時には領域外)のどこの画素にも便利にアクセスできる必要があります。また画像シーケンスの場合、シーケンス内の任意の画像の任意の領域の任意の画素にアクセスできる必要があります。しかし、JPEG・TIFF・PNG・GIF など数百もの画像形式があり、オンデマンドでの画素アクセスを難しくしています。これらの形式には次のような違いがあります。
- カラースペース(例: sRGB・線形 RGB・線形 GRAY・CMYK・YUV・Lab など)
- ビット深度(例: 1・4・8・12・16 など)
- 格納形式(例: 符号なし・符号付き・float・double など)
- 圧縮(例: 非圧縮・RLE・Zip・BZip など)
- 向き(すなわち上から下へ・右から左へ など)
- レイアウト(例: raw・オペコードが散在 など)
加えて、一部の画像画素は減衰(attenuation)を必要とし、一部の形式は複数フレームを許し、一部の形式はまずラスタライズ(ベクターから画素へ変換)する必要のあるベクターグラフィックを含みます。
画像処理アルゴリズムの効率的な実装では、次のような取得・設定が必要になることがあります。
- 一度に 1 画素(例: 位置 10,3 の画素)
- 単一のスキャンライン(例: 行 4 のすべての画素)
- 一度に数本のスキャンライン(例: 行 4-7 の画素)
- 単一または複数の画素列(例: 列 11 のすべての画素)
- 画像中の任意の画素領域(例: 10,7 から 10,19 で定義される画素)
- ランダムな順序の画素(例: 14,15 と 640,480 の画素)
- 2 つの異なる画像の画素(例: 画像 1 の 5,1 と画像 2 の 5,1)
- 画像の境界外の画素(例: -1,-3 の画素)
- 符号なし(65311)または浮動小数点表現(例: 0.17836)の画素成分
- 負の値(例: -0.0072973525628)や量子深度を超える値(例: 65931)を含み得るハイダイナミックレンジ画素
- 異なる実行スレッドで同時に 1 つ以上の画素
- CPU・GPU・その他のプロセッサから成る異種プラットフォームで協調実行する高速化を活用するため、メモリ内のすべての画素
- 画素チャンネルをコピー・更新・ブレンドのいずれにするかを指定する、各チャンネルに関連付けられたトレイト
- どの画素が更新対象となるかを定義するマスク
- ユーザーに役立つが ImageMagick の画像処理アルゴリズムには触れられない追加チャンネル
多様な画像形式と画像処理要件を踏まえ、私たちは ImageMagick 画素キャッシュを実装し、画像領域内(すなわち本物の画素)やシーケンス内の任意の画像のどこの画素にも、オンデマンドで便利な逐次・並列アクセスを提供します。加えて、画素キャッシュは画像が定義する境界外の画素(すなわち仮想画素)へのアクセスも許します。
画素に加えて、画像には多数の画像プロパティとプロファイルがあります。プロパティには、幅・高さ・深度・カラースペースなどのよく知られた属性が含まれます。画像には、作者・コメント・作成日などの任意のプロパティもあり得ます。一部の画像にはカラーマネジメント用のプロファイルや、EXIF・IPTC・8BIM・XMP の情報プロファイルも含まれます。ImageMagick は、画像プロパティやプロファイルの取得・設定・表示、またはプロファイルの適用のためのコマンドラインオプションとプログラミングメソッドを提供します。
ImageMagick は約 50 万行の C コードから成り、任意で依存ライブラリ(例: JPEG・PNG・TIFF ライブラリ)の数百万行のコードに依存します。それを考えると、巨大なアーキテクチャ文書を予想するかもしれません。しかし、画像処理の大部分は単に画素とそのメタデータへのアクセスであり、私たちの単純・優雅・効率的な実装はこれを ImageMagick 開発者にとって容易にします。次のいくつかのセクションでは、画素キャッシュの実装と、画像プロパティ・プロファイルの取得・設定を議論します。次に、実行スレッド内での ImageMagick の使用を議論します。最後のセクションでは、特定の画像形式を読み書きする画像コーダーを議論し、続いてカスタム要件に基づいて画素へアクセス・更新するフィルタの作成について少し触れます。
画素キャッシュ
ImageMagick 画素キャッシュは、最大 64 チャンネルの画像画素のリポジトリです。チャンネルは、ImageMagick のビルド時に指定された深度で連続して格納されます。チャンネル深度は、ImageMagick の Q8 版では画素成分あたり 8 ビット、Q16 版では 16 ビット、Q32 版では 32 ビットです。デフォルトでは画素成分は 32 ビット浮動小数点の ハイダイナミックレンジ 量です。チャンネルは任意の値を保持できますが、通常は赤・緑・青・アルファの強度、またはシアン・マゼンタ・イエロー・黒・アルファの強度を含みます。チャンネルは、カラーマップ画像のカラーマップインデックスや CMYK 画像の黒チャンネルを含むこともあります。画素キャッシュのストレージは、ヒープメモリ・ディスクバックのメモリマップ・ディスク上のいずれかです。画素キャッシュは参照カウントされます。キャッシュが複製されるとき、コピーされるのはキャッシュのプロパティのみです。キャッシュ画素はその後、いずれかの画素を更新する意図を示したときにのみコピーされます。
画素キャッシュの作成
画素キャッシュは画像の作成時にその画像に関連付けられ、画素を取得または配置しようとしたときに初期化されます。画素キャッシュを画像に関連付ける一般的な 3 つの方法を示します。
背景色で初期化された画像キャンバスを作成します。
image=AllocateImage(image_info);
if (SetImageExtent(image,640,480) == MagickFalse)
{ /* an exception was thrown */ }
(void) QueryMagickColor("red",&image->background_color,&image->exception);
SetImageBackgroundColor(image);
ディスク上の JPEG 画像から画像を作成します。
(void) strcpy(image_info->filename,"image.jpg"):
image=ReadImage(image_info,exception);
if (image == (Image *) NULL)
{ /* an exception was thrown */ }
メモリ上の画像から画像を作成します。
image=BlobToImage(blob_info,blob,extent,exception);
if (image == (Image *) NULL)
{ /* an exception was thrown */ }
画素キャッシュの議論では、要点を示すために MagickCore API を使いますが、原理は ImageMagick への他のプログラムインターフェイスでも同じです。
画素キャッシュが初期化されると、画素は元のビット深度から画素キャッシュが必要とする深度へスケーリングされます。たとえば、1 チャンネル 1 ビットのモノクロ PBM 画像は、Q8 版を使っていれば 8 ビットグレー画像へ、Q16 版なら 16 ビット RGBA へスケーリングされます。どのバージョンを持っているかは -version オプションで判別できます。
$ identify -version
Version: ImageMagick 7.1.2-25 2026-06-04 Q16-HDRI https://imagemagick.org
ご覧のとおり、画素キャッシュの利便性は、ストレージ(例: 1 ビットモノクロ画像を 16 ビットで格納するのは無駄)と速度(すなわち画像全体をメモリに格納するのは、一度に 1 スキャンラインの画素にアクセスするより一般に遅い)のトレードオフを伴うことがあります。ほとんどの場合、画素キャッシュの利点は欠点を上回ります。
画素キャッシュへのアクセス
画素キャッシュが画像に関連付けられると、通常はそこへ画素を取得・更新・配置したくなります。画像領域内の画素を本物の画素、領域外の画素を仮想画素と呼びます。キャッシュ内の画素にアクセスするには以下のメソッドを使います。
- GetVirtualPixels(): 変更する意図のない画素、または画像領域外にある画素(例: -1,-3 の画素)を取得する
- GetAuthenticPixels(): 変更する意図のある画素を取得する
- QueueAuthenticPixels(): 設定する意図のある画素をキューに入れる
- SyncAuthenticPixels(): 変更された画素で画素キャッシュを更新する
画素キャッシュ内の画素を操作する典型的な MagickCore コードスニペットを示します。この例では、入力画像から出力画像へ画素をコピーし、強度を 10% 下げます。
const Quantum
*p;
Quantum
*q;
ssize_t
x,
y;
destination=CloneImage(source,source->columns,source->rows,MagickTrue,exception);
if (destination == (Image *) NULL)
{ /* an exception was thrown */ }
for (y=0; y < (ssize_t) source->rows; y++)
{
p=GetVirtualPixels(source,0,y,source->columns,1,exception);
q=GetAuthenticPixels(destination,0,y,destination->columns,1,exception);
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)
break;
for (x=0; x < (ssize_t) source->columns; x++)
{
SetPixelRed(image,90*p->red/100,q);
SetPixelGreen(image,90*p->green/100,q);
SetPixelBlue(image,90*p->blue/100,q);
SetPixelAlpha(image,90*p->opacity/100,q);
p+=GetPixelChannels(source);
q+=GetPixelChannels(destination);
}
if (SyncAuthenticPixels(destination,exception) == MagickFalse)
break;
}
if (y < (ssize_t) source->rows)
{ /* an exception was thrown */ }
ソース画像を複製して最初に出力画像を作成するとき、画素キャッシュの画素はコピーされません。GetAuthenticPixels() または QueueAuthenticPixels() を呼び出して画素キャッシュを変更・設定する意図を示したときにのみコピーされます。既存の値を更新するのではなく新しい画素値を設定したい場合は QueueAuthenticPixels() を使います。GetAuthenticPixels() で画素値を設定することもできますが、代わりに QueueAuthenticPixels() を使う方がわずかに効率的です。最後に、更新された画素が画素キャッシュへ確実に反映されるよう SyncAuthenticPixels() を使います。
各画素に任意のコンテンツ(メタ コンテンツと呼ぶ)を関連付けられます。このコンテンツにアクセスするには GetVirtualMetacontent()(コンテンツを読む)または GetAuthenticMetacontent()(コンテンツを更新する)を使います。たとえばメタコンテンツを出力するには次を使います。
const void
*metacontent;
for (y=0; y < (ssize_t) source->rows; y++)
{
p=GetVirtualPixels(source,0,y,source->columns,1);
if (p == (const Quantum *) NULL)
break;
metacontent=GetVirtualMetacontent(source);
/* print meta content here */
}
if (y < (ssize_t) source->rows)
/* an exception was thrown */
画素キャッシュマネージャは、画像画素への直接アクセスと間接アクセスのどちらを与えるかを決めます。場合によっては画素は中間バッファへステージングされます。だからこそ、このバッファを画素キャッシュへ確実に押し出し、キャッシュ内の対応する画素が更新されることを保証するために SyncAuthenticPixels() を呼ぶ必要があります。この理由から、一度に 1 本または数本のスキャンラインの画素のみを読み書きすることを推奨します。ただし、欲しい任意の矩形領域の画素を取得できます。GetAuthenticPixels() は、要求する領域が画像領域の範囲内であることを要求します。640x480 の画像では、行 479 で 640 画素のスキャンラインを取得できますが、行 480 のスキャンラインを要求すると例外が返ります(行は 0 から番号付けされます)。GetVirtualPixels() にはこの制約がありません。たとえば、
p=GetVirtualPixels(source,-3,-3,source->columns+3,6,exception);
は、一部が画像領域の範囲内になくても、要求した画素を問題なく返します。
仮想画素
注目画素の周りの近傍画素を必要とする画像処理アルゴリズムは数多くあります。そのアルゴリズムには通常、エッジ画素と呼ばれる画像境界周辺の画素をどう扱うかについての注意書きが含まれます。仮想画素を使えば、アルゴリズムにとってどの仮想画素メソッドが最も適切かを選ぶこと以外に、特別なエッジ処理を気にする必要はありません。
仮想画素へのアクセスは、MagickCore API の SetImageVirtualPixelMethod() メソッド、またはコマンドラインの -virtual-pixel オプションで制御されます。メソッドには次があります。
| background | 画像を囲む領域は背景色 |
|---|---|
| black | 画像を囲む領域は黒 |
| checker-tile | 画像と背景色の正方形を交互に並べる |
| dither | 非ランダムな 32x32 のディザパターン |
| edge | エッジ画素を無限遠へ拡張する(デフォルト) |
| gray | 画像を囲む領域はグレー |
| horizontal-tile | 画像を水平にタイル化し、上下は背景色 |
| horizontal-tile-edge | 画像を水平にタイル化し、側端の画素を複製する |
| mirror | 画像をミラータイル化する |
| random | 画像からランダムな画素を選ぶ |
| tile | 画像をタイル化する |
| transparent | 画像を囲む領域は透明な黒 |
| vertical-tile | 画像を垂直にタイル化し、両側は背景色 |
| vertical-tile-edge | 画像を垂直にタイル化し、側端の画素を複製する |
| white | 画像を囲む領域は白 |
キャッシュのストレージとリソース要件
ImageMagick 画素キャッシュのこの単純で優雅な設計は、ストレージと処理速度の面でコストを伴うことを思い出してください。画素キャッシュのストレージ要件は、画像の面積と画素成分のビット深度に比例します。たとえば 640x480 の画像で非 HDRI の Q16 版を使っている場合、画素キャッシュは 画像幅 * 高さ * ビット深度 / 8 * チャンネル数 バイト、すなわち約 2.3 メビバイト(640 * 480 * 2 * 4)を消費します。悪くありませんが、画像が 25000x25000 画素なら? 画素キャッシュは約 4.7 ギビバイトのストレージを必要とします。痛い。ImageMagick は、大きな画像をメモリではなくディスクにキャッシュすることで、巨大なストレージ要件に対応します。通常、画素キャッシュはヒープメモリを使ってメモリに格納されます。ヒープメモリが枯渇すると、ディスク上に画素キャッシュを作成してメモリマップを試みます。メモリマップのメモリも枯渇すると、単に標準のディスク I/O を使います。ディスクストレージは豊富で安価ですが、非常に遅く、メモリ内の画素アクセスより 1000 倍以上遅いこともあります。ディスクベースのキャッシュをメモリマップすれば、最大 5 倍の速度改善が得られます。ストレージに関するこれらの決定は、画素キャッシュマネージャがオペレーティングシステムと交渉して自動的に行います。ただし、キャッシュのリソース制限で画素キャッシュマネージャによる画素キャッシュの割り当て方に影響を与えられます。制限には次があります。
| width | 画像の最大幅。これを超えると例外がスローされ操作が中止される。 |
|---|---|
| height | 画像の最大高さ。これを超えると例外がスローされ操作が中止される。 |
| area | 画素キャッシュメモリに常駐できる 1 画像の最大面積(バイト)。これを超えると画像は自動的にディスクへキャッシュされ、任意でメモリマップされる。 |
| memory | ヒープから画素キャッシュに割り当てる最大メモリ量(バイト)。 |
| map | 画素キャッシュに割り当てる最大メモリマップ量(バイト)。 |
| disk | 画素キャッシュが使用を許される最大ディスク容量(バイト)。これを超えると致命的例外がスローされ、すべての処理が停止する。 |
| files | 開いている画素キャッシュファイルの最大数。これを超えると、以降にディスクへキャッシュされる画素はオンデマンドで閉じられ再オープンされる。この動作により、画素キャッシュの open/close システムコール数を減らし、速度低下なしに多数の画像をディスク上で同時にアクセスできる。 |
| thread | 並列実行を許されるスレッドの最大数。システムはこの値より少ないスレッド数を選ぶことがある。ImageMagick はデフォルトで最適なスレッド数を選び、通常はホストのコア数。この値を 1 にするとすべての並列領域が 1 スレッドで実行される。 |
| time | プロセスの実行を許される最大秒数。これを超えると例外がスローされ処理が停止する。 |
なお、これらの制限は ImageMagick 画素キャッシュに関するものです。ImageMagick 内の特定のアルゴリズムや外部デリゲートライブラリ(例: JPEG・TIFF など)はこれらの制限を尊重しません。
これらの制限の現在の設定を確認するには、次のコマンドを使います。
-> identify -list resource
Resource limits:
Width: 100MP
Height: 100MP
Area: 25.181GB
Memory: 11.726GiB
Map: 23.452GiB
Disk: unlimited
File: 768
Thread: 12
Throttle: 0
Time: unlimited
これらの制限は、セキュリティポリシー(policy.xml を参照)として、環境変数 で、-limit コマンドラインオプションで、または SetMagickResourceLimit() MagickCore API メソッドで設定できます。例として、ImageMagick へのオンライン Web インターフェイス MagickStudio は、サービス拒否を防ぐためこれらのポリシー制限を含めています。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policymap [
<!ELEMENT policymap (policy)*>
<!ATTLIST policymap xmlns CDATA #FIXED "">
<!ELEMENT policy EMPTY>
<!ATTLIST policy xmlns CDATA #FIXED "">
<!ATTLIST policy domain NMTOKEN #REQUIRED>
<!ATTLIST policy name NMTOKEN #IMPLIED>
<!ATTLIST policy pattern CDATA #IMPLIED>
<!ATTLIST policy rights NMTOKEN #IMPLIED>
<!ATTLIST policy stealth NMTOKEN #IMPLIED>
<!ATTLIST policy value CDATA #IMPLIED>
]>
<!--
Creating a security policy that fits your specific local environment
before making use of ImageMagick is highly advised. You can find guidance on
setting up this policy at https://imagemagick.org/security-policy/,
and it's important to verify your policy using the validation tool located
at https://imagemagick-secevaluator.doyensec.com/.
Secure ImageMagick security policy:
This stringent security policy prioritizes the implementation of
rigorous controls and restricted resource utilization to establish a
profoundly secure setting while employing ImageMagick. It deactivates
conceivably hazardous functionalities, including specific coders like
SVG or HTTP. The policy promotes the tailoring of security measures to
harmonize with the requirements of the local environment and the guidelines
of the organization. This protocol encompasses explicit particulars like
limitations on memory consumption, sanctioned pathways for reading and
writing, confines on image sequences, the utmost permissible duration of
workflows, allocation of disk space intended for image data, and even an
undisclosed passphrase for remote connections. By adopting this robust
policy, entities can elevate their overall security stance and alleviate
potential vulnerabilities.
-->
<policymap>
<!-- Set maximum parallel threads. -->
<policy domain="resource" name="thread" value="2"/>
<!-- Set maximum time in seconds. When this limit is exceeded, an exception
is thrown and processing stops. -->
<policy domain="resource" name="time" value="120"/>
<!-- Set maximum number of open pixel cache files. When this limit is
exceeded, any subsequent pixels cached to disk are closed and reopened
on demand. -->
<policy domain="resource" name="file" value="768"/>
<!-- Set maximum amount of memory in bytes to allocate for the pixel cache
from the heap. When this limit is exceeded, the image pixels are cached
to memory-mapped disk. -->
<policy domain="resource" name="memory" value="256MiB"/>
<!-- Set maximum amount of memory map in bytes to allocate for the pixel
cache. When this limit is exceeded, the image pixels are cached to
disk. -->
<policy domain="resource" name="map" value="512MiB"/>
<!-- Set the maximum width * height of an image that can reside in the pixel
cache memory. Images that exceed the area limit are cached to disk. -->
<policy domain="resource" name="area" value="16KP"/>
<!-- Set maximum amount of disk space in bytes permitted for use by the pixel
cache. When this limit is exceeded, the pixel cache is not be created
and an exception is thrown. -->
<policy domain="resource" name="disk" value="1GiB"/>
<!-- Set the maximum length of an image sequence. When this limit is
exceeded, an exception is thrown. -->
<policy domain="resource" name="list-length" value="32"/>
<!-- Set the maximum width of an image. When this limit is exceeded, an
exception is thrown. -->
<policy domain="resource" name="width" value="8KP"/>
<!-- Set the maximum height of an image. When this limit is exceeded, an
exception is thrown. -->
<policy domain="resource" name="height" value="8KP"/>
<!-- Periodically yield the CPU for at least the time specified in
milliseconds. -->
<!-- -->
<!-- Do not create temporary files in the default shared directories, instead
specify a private area to store only ImageMagick temporary files. -->
<!-- -->
<!-- Force memory initialization by memory mapping select memory
allocations. -->
<policy domain="cache" name="memory-map" value="anonymous"/>
<!-- Ensure all image data is fully flushed and synchronized to disk. -->
<policy domain="cache" name="synchronize" value="true"/>
<!-- Replace passphrase for secure distributed processing -->
<!-- -->
<!-- Do not permit any delegates to execute. -->
<policy domain="delegate" rights="none" pattern="*"/>
<!-- Do not permit any image filters to load. -->
<policy domain="filter" rights="none" pattern="*"/>
<!-- Don't read/write from/to stdin/stdout. -->
<policy domain="path" rights="none" pattern="-"/>
<!-- don't read sensitive paths. -->
<policy domain="path" rights="none" pattern="/etc/*"/>
<!-- Indirect reads are not permitted. -->
<policy domain="path" rights="none" pattern="@*"/>
<!-- These image types are security risks on read, but write is fine -->
<policy domain="module" rights="write" pattern="{MSL,MVG,PS,SVG,URL,XPS}"/>
<!-- This policy sets the number of times to replace content of certain
memory buffers and temporary files before they are freed or deleted. -->
<policy domain="system" name="shred" value="1"/>
<!-- Enable the initialization of buffers with zeros, resulting in a minor
performance penalty but with improved security. -->
<policy domain="system" name="memory-map" value="anonymous"/>
<!-- Set the maximum amount of memory in bytes that is permitted for
allocation requests. -->
<policy domain="system" name="max-memory-request" value="256MiB"/>
</policymap>
私たちは複数の同時セッションを処理するため、どの 1 セッションも利用可能なメモリすべてを消費してほしくありません。このポリシーにより、大きな画像はディスクにキャッシュされます。画像が大きすぎて画素キャッシュのディスク制限を超えると、プログラムは終了します。加えて、暴走する処理タスクを防ぐため時間制限を設けています。いずれか 1 つの画像の幅または高さが 8192 画素を超えると、例外がスローされ処理が停止します。ImageMagick 7.0.1-8 以降、任意のデリゲートまたはすべてのデリゲートの使用を防げます(pattern を "*" に設定)。なお、このリリースより前では、デリゲートの使用を防ぐには domain を "coder" にします(例: domain="coder" rights="none" pattern="HTTPS")。このポリシーは間接読み込みも防ぎます。たとえばファイルからテキストを読みたい場合(例: caption:@myCaption.txt)、このポリシーを削除する必要があります。
なお、キャッシュ制限は ImageMagick の各起動に対してグローバルです。つまり複数の画像を作成すると、合計のリソース要件が制限と比較され、画素キャッシュのストレージ配置が決まります。
画素キャッシュがどの種類のリソースをどれだけ消費するかを確認するには、コマンドラインに -debug cache オプションを追加します。
$ magick -debug cache logo: -sharpen 3x2 null:
2016-12-17T13:33:42-05:00 0:00.000 0.000u 7.0.0 Cache magick: cache.c/DestroyPixelCache/1275/Cache
destroy
2016-12-17T13:33:42-05:00 0:00.000 0.000u 7.0.0 Cache magick: cache.c/OpenPixelCache/3834/Cache
open LOGO[0] (Heap Memory, 640x480x4 4.688MiB)
2016-12-17T13:33:42-05:00 0:00.010 0.000u 7.0.0 Cache magick: cache.c/OpenPixelCache/3834/Cache
open LOGO[0] (Heap Memory, 640x480x3 3.516MiB)
2016-12-17T13:33:42-05:00 0:00.010 0.000u 7.0.0 Cache magick: cache.c/ClonePixelCachePixels/1044/Cache
Memory => Memory
2016-12-17T13:33:42-05:00 0:00.020 0.010u 7.0.0 Cache magick: cache.c/ClonePixelCachePixels/1044/Cache
Memory => Memory
2016-12-17T13:33:42-05:00 0:00.020 0.010u 7.0.0 Cache magick: cache.c/OpenPixelCache/3834/Cache
open LOGO[0] (Heap Memory, 640x480x3 3.516MiB)
2016-12-17T13:33:42-05:00 0:00.050 0.100u 7.0.0 Cache magick: cache.c/DestroyPixelCache/1275/Cache
destroy LOGO[0]
2016-12-17T13:33:42-05:00 0:00.050 0.100u 7.0.0 Cache magick: cache.c/DestroyPixelCache/1275/Cache
destroy LOGO[0]
このコマンドはメモリ内の画素キャッシュを利用します。ロゴは 4.688MiB を消費し、シャープ化後は 3.516MiB でした。
分散画素キャッシュ
分散画素キャッシュは、単一ホストで利用できる従来の画素キャッシュの拡張です。分散画素キャッシュは複数のサーバーにまたがれるため、非常に大きな画像をサポートするようサイズとトランザクション容量を拡大できます。1 台以上のマシンで画素キャッシュサーバーを起動します。画像を読み込んだり操作したりしてローカルの画素キャッシュリソースが枯渇すると、ImageMagick はこれらのリモート画素サーバーの 1 つ以上に接続して画素を格納・取得します。分散画素キャッシュは、リモートサーバーとの画素のやり取りにネットワーク帯域に依存します。そのため、ローカルストレージ(例: メモリ・ディスクなど)を利用する画素キャッシュより大幅に遅くなると考えられます。
magick -distribute-cache 6668 & // start on 192.168.100.50
magick -define registry:cache:hosts=192.168.100.50:6668 myimage.jpg -sharpen 5x2 mimage.png
キャッシュビュー
MagickCore API の GetVirtualPixels()・GetAuthenticPixels()・QueueAuthenticPixels()・SyncAuthenticPixels() は、一度に 1 画像あたり 1 つの画素キャッシュ領域しか扱えません。同じ画像の最初と最後のスキャンラインに同時にアクセスしたいとしたら? 解決策はキャッシュビューを使うことです。キャッシュビューは、必要なだけ多くの領域に画素キャッシュ内で同時にアクセスできるようにします。キャッシュビューの メソッド は、まずビューを開き、使い終えたら閉じる必要がある点を除いて、前述のメソッドと類似しています。画像の最初と最後の画素行に同時にアクセスできる MagickCore コードのスニペットを示します。
CacheView
*first_row,
*last_row;
first_row=AcquireVirtualCacheView(source,exception);
last_row=AcquireVirtualCacheView(source,exception);
for (y=0; y < (ssize_t) source->rows; y++)
{
const Quantum
*p,
*q;
p=GetCacheViewVirtualPixels(first_row,0,y,source->columns,1,exception);
q=GetCacheViewVirtualPixels(last_row,0,source->rows-y-1,source->columns,1,exception);
if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
break;
for (x=0; x < (ssize_t) source->columns; x++)
{
/* do something with p & q here */
}
}
last_row=DestroyCacheView(last_row);
first_row=DestroyCacheView(first_row);
if (y < (ssize_t) source->rows)
{ /* an exception was thrown */ }
Magick 画素キャッシュ形式
各画像形式は ImageMagick によってデコードされ、画素は画素キャッシュへ格納されることを思い出してください。画像を書き出すと、画素は画素キャッシュから読み出され、書き出す形式(例: GIF・PNG など)が必要とする形でエンコードされます。Magick 画素キャッシュ(MPC)形式は、画像形式との間で画素をデコード・エンコードするオーバーヘッドをなくすよう設計されています。MPC は 2 つのファイルを書き出します。1 つは拡張子 .mpc で、画像または画像シーケンスに関連付けられたすべてのプロパティ(例: 幅・高さ・カラースペースなど)を保持し、2 つ目は拡張子 .cache で、ネイティブの raw 形式の画素キャッシュです。MPC 画像ファイルを読み込むとき、ImageMagick は画像プロパティを読み、ディスク上の画素キャッシュをメモリマップするので、画像画素のデコードが不要になります。トレードオフはディスク容量です。MPC は一般に他のほとんどの画像形式よりファイルサイズが大きくなります。
MPC 画像ファイルの最も効率的な使い方は、一度書いて何度も読むパターンです。たとえば、ワークフローでソース画像からランダムな画素ブロックを抽出する必要があるとします。毎回ソース画像を再読み込みして場合によっては展開するのではなく、MPC を使って画像を直接メモリへマップします。
画素キャッシュの推奨プラクティス
GetVirtualPixels()・GetAuthenticPixels()・QueueAuthenticPixels・GetCacheViewVirtualPixels()・GetCacheViewAuthenticPixels()・QueueCacheViewAuthenticPixels() メソッドで、画素キャッシュから任意の画素・任意の画素ブロック・任意のスキャンライン・複数スキャンライン・任意の行・複数行を要求できますが、ImageMagick は一度に数画素または数画素行を返すよう最適化されています。一度に 1 本または数本のスキャンラインを要求すると追加の最適化があります。これらのメソッドは画素キャッシュへのランダムアクセスも許しますが、ImageMagick は逐次アクセスに最適化されています。画像の最後の行から最初へ逐次的に画素のスキャンラインへアクセスできますが、最初の行から最後へ逐次順にアクセスすると性能が向上することがあります。
画素を行順または列順で取得・変更・設定できます。ただし、列ごとよりも行ごとに画素へアクセスする方が効率的です。
GetAuthenticPixels() または GetCacheViewAuthenticPixels() が返した画素を更新する場合、変更が画素キャッシュと確実に同期されるよう、それぞれ SyncAuthenticPixels() または SyncCacheViewAuthenticPixels() を呼ぶのを忘れないでください。
初期画素値を設定する場合は QueueAuthenticPixels() または QueueCacheViewAuthenticPixels() を使います。GetAuthenticPixels() や GetCacheViewAuthenticPixels() メソッドはキャッシュから画素を読みますが、初期画素値を設定する場合この読み込みは不要です。画素の変更を画素キャッシュへ反映するため、それぞれ SyncAuthenticPixels() または SyncCacheViewAuthenticPixels() を呼ぶのを忘れないでください。
GetVirtualPixels()・GetAuthenticPixels()・QueueAuthenticPixels()・SyncAuthenticPixels() は、キャッシュビュー版よりわずかに効率的です。ただし、画像の複数領域に同時にアクセスする必要がある場合や、複数の実行スレッドが画像にアクセスしている場合は、キャッシュビューが必要です。
GetVirtualPixels() または GetCacheViewVirtualPixels() で画像の境界外の画素を要求できますが、画像領域の範囲内の画素を要求する方が効率的です。
適切なリソース制限を使って画素キャッシュをディスクへ強制できますが、ディスクアクセスはメモリアクセスより 1000 倍以上遅いことがあります。画素キャッシュへの高速で効率的なアクセスのため、画素キャッシュをヒープメモリに保つようにしてください。
ImageMagick の Q16 版はスケーリングなしで 16 ビット画像を読み書きできますが、画素キャッシュは Q8 版の 2 倍のリソースを消費します。システムのメモリまたはディスクリソースが限られている場合は、Q8 版を検討してください。加えて、Q8 版は通常 Q16 版より高速に動作します。
画像形式とアルゴリズムの大部分は、0 からある最大値までの固定範囲の画素値に自らを制限します。たとえば Q16 版は 0 から 65535 の強度を許します。しかしハイダイナミックレンジイメージング(HDRI)は、標準的なデジタル画像技術よりはるかに広い露出のダイナミックレンジ(すなわち明暗領域の大きな差)を許します。HDRI は、最も明るい直射日光から最も深く暗い影まで、実際のシーンに見られる広い範囲の強度レベルを正確に表現します。ハイダイナミックレンジ画像を扱うには ImageMagick のビルド時に HDRI を有効にしますが、各画素成分が 32 ビット浮動小数点値であることに留意してください。加えて、画素値はデフォルトでクランプされないため、一部のアルゴリズムは非 HDRI 版とは異なり、帯域外の画素値により予期しない結果になることがあります。
大きな画像を扱う場合、画素キャッシュが十分な空き容量のあるディスク領域に書き込まれることを確認してください。Linux では通常 /tmp、Windows では c:/temp です。これらのオプションで、ImageMagick に画素キャッシュを別の場所へ書き込ませ、メモリを節約できます。
magick -limit memory 2GB -limit map 4GB -define registry:temporary-path=/data/tmp ...
環境のグローバルリソース制限は policy.xml 設定ファイルで設定します。
同じ画像を何度も処理する予定なら、MPC 形式を検討してください。MPC 画像の読み込みは、ネイティブの画素キャッシュ形式であるため画像画素のデコードが不要で、オーバーヘッドがほぼゼロです。例を示します。
magick image.tif image.mpc
magick image.mpc -crop 100x100+0+0 +repage 1.png
magick image.mpc -crop 100x100+100+0 +repage 2.png
magick image.mpc -crop 100x100+200+0 +repage 3.png
MPC は Web サイトに理想的です。画像の読み書きのオーバーヘッドを減らします。私たちは オンライン画像スタジオ でこれを専ら使っています。
画像プロパティとプロファイル
画像には、プロパティ(例: 幅・高さ・説明など)とプロファイル(例: EXIF・IPTC・カラーマネジメント)の形でメタデータが関連付けられています。ImageMagick は、画像プロパティの取得・設定・更新、およびプロファイルの取得・設定・更新・適用のための便利なメソッドを提供します。よく使われる画像プロパティのいくつかは MagickCore API の Image 構造に関連付けられています。たとえば、
(void) printf("image width: %lu, height: %lu\n",image->columns,image->rows);
画像のコメントや説明などの大部分の画像プロパティには、GetImageProperty() と SetImageProperty() メソッドを使います。ここではプロパティを設定してすぐに取得し直します。
const char
*comment;
(void) SetImageProperty(image,"comment","This space for rent");
comment=GetImageProperty(image,"comment");
if (comment == (const char *) NULL)
(void) printf("Image comment: %s\n",comment);
ImageMagick は GetImageArtifact() と SetImageArtifact() メソッドでアーティファクトをサポートします。アーティファクトは画像形式(例: PNG)へエクスポートされないステルスなプロパティです。
画像プロファイルは GetImageProfile()・SetImageProfile()・ProfileImage() メソッドで扱います。ここではプロファイルを設定してすぐに取得し直します。
StringInfo
*profile;
profile=AcquireStringInfo(length);
SetStringInfoDatum(profile,my_exif_profile);
(void) SetImageProfile(image,"EXIF",profile);
DestroyStringInfo(profile);
profile=GetImageProfile(image,"EXIF");
if (profile != (StringInfo *) NULL)
(void) PrintStringInfo(stdout,"EXIF",profile);
マルチスペクトル画像
ImageMagick は、すべてのチャンネルが元画像と同じ寸法・画素数を持つ マルチスペクトル画像 をサポートします。ただし、すべての画像形式がマルチスペクトル画像をサポートするわけではありません。PSD・TIFF・MIFF・MPC・FTXT は最大 31 バンド(うち 21 はメタチャンネル)のマルチスペクトル画像を完全にサポートします。なお、configure スクリプトの --enable-64bit-channel-masks オプションで ImageMagick をビルドすると、最大 52 メタチャンネルの 62 バンドマルチスペクトル画像を処理できます。
画像形式が現在サポートしていないユースケースがあれば、ディスカッションフォーラム に投稿してください。将来の ImageMagick リリースでそのユースケースをサポートできる可能性は十分にあります。
画素のストリーミング
ImageMagick は、画像から読み込まれる、または画像へ書き出される際の画素のストリーミングを提供します。これには画素キャッシュに対するいくつかの利点があります。画素キャッシュが消費する時間とリソースは画像の面積に比例しますが、画素ストリームのリソースは画像の幅に比例します。欠点は、画素がストリーミングされる際に消費されなければならず、永続性がないことです。
MagickCore プログラムで適切なコールバックメソッドとともに ReadStream() または WriteStream() を使い、ストリーミングされる画素を消費します。ReadStream を使う省略例を示します。
static size_t StreamPixels(const Image *image,const void *pixels,const size_t columns)
{
register const Quantum
*p;
MyData
*my_data;
my_data=(MyData *) image->client_data;
p=(Quantum *) pixels;
if (p != (const Quantum *) NULL)
{
/* process pixels here */
}
return(columns);
}
...
/* invoke the pixel stream here */
image_info->client_data=(void *) MyData;
image=ReadStream(image_info,&StreamPixels,exception);
画像または画像の一部の 1 つ以上の画素成分を、選んだストレージ形式へストリームする軽量ツール stream も提供します。入力画像から 1 行ずつ読み込まれる画素成分を書き出すため、stream は大きな画像を扱うときや raw の画素成分が必要なときに望ましいです。画像形式の大部分は画素(赤・緑・青)を左から右、上から下へストリームします。ただし、一部の形式はこの一般的な順序をサポートしません(例: PSD 形式)。
大きな画像のサポート
ImageMagick は、読み込み・処理・書き出しの各操作を含め、メガからテラ画素にわたる画像サイズを扱う能力を持ちます。理論上、画像の寸法は 32 ビットオペレーティングシステムで最大 3100 万行/列、64 ビット OS では実に最大 31 兆まで拡張できます。ただし、実際に達成できる寸法は、ホストコンピュータで利用可能なリソースに応じて大幅に小さくなります。一部の画像形式が画像サイズに制限を課すことを認識しておくことが重要です。たとえば Photoshop 画像は幅または高さが最大 300,000 画素に制限されます。ここでは画像を 25 万画素四方へリサイズします。
magick logo: -resize 250000x250000 logo.miff
大きな画像では、メモリリソースが枯渇する可能性が高く、ImageMagick は代わりにディスク上に画素キャッシュを作成します。十分な一時ディスク容量があることを確認してください。デフォルトの一時ディスクパーティションが小さすぎる場合、ImageMagick に十分な空き容量のある別のパーティションを使わせます。たとえば、
magick -define registry:temporary-path=/data/tmp logo: \
-resize 250000x250000 logo.miff
大きな画像がシステムのメモリすべてを消費しないよう、リソース制限で画像画素をメモリマップディスクへ強制します。
magick -define registry:temporary-path=/data/tmp -limit memory 16mb \
logo: -resize 250000x250000 logo.miff
ここではすべての画像画素をディスクへ強制します。
magick -define registry:temporary-path=/data/tmp -limit area 0 \
logo: -resize 250000x250000 logo.miff
画素をディスクへキャッシュするのはメモリより約 1000 倍遅いです。ImageMagick でディスク上の大きな画像を処理すると長い実行時間が予想されます。次のコマンドで進捗を監視できます。
magick -monitor -limit memory 2GiB -limit map 4GiB -define registry:temporary-path=/data/tmp \
logo: -resize 250000x250000 logo.miff
本当に大きな画像の場合、またはホストのリソースが限られている場合、1 つ以上のリモートホストで分散画素キャッシュを利用できます。
magick -distribute-cache 6668 & // start on 192.168.100.50
magick -distribute-cache 6668 & // start on 192.168.100.51
magick -limit memory 2mb -limit map 2mb -limit disk 2gb \
-define registry:cache:hosts=192.168.100.50:6668,192.168.100.51:6668 \
myhugeimage.jpg -sharpen 5x2 myhugeimage.png
ネットワークレイテンシのため、ワークフローの処理が大幅に遅くなると予想されます。
実行スレッド
ImageMagick の内部アルゴリズムの多くは、マルチコアプロセッサチップが提供する高速化を活用するためスレッド化されています。ただし、MagickCore の GetVirtualPixels()・GetAuthenticPixels()・QueueAuthenticPixels()・SyncAuthenticPixels() 画素キャッシュメソッドを除き、自分の実行スレッドで ImageMagick アルゴリズムを使って構いません。これらのメソッドは、OpenMP の並列セクションを除き、1 つの実行スレッドのみを想定しています。複数の実行スレッドで画素キャッシュへアクセスするには、キャッシュビューを使います。たとえば CompositeImage() メソッドでこれを行います。各実行スレッドで単一のソース画像を異なる出力画像へ合成したいとします。GetVirtualPixels() を使うと、複数スレッドが画素キャッシュの異なる領域を同時に要求する可能性が高いため、結果は予測不能です。代わりに、各実行スレッドに固有のビューを作成する GetCacheViewVirtualPixels() を使い、いくつのスレッドが起動されてもプログラムが正しく動作することを保証します。MagickWand API などの他のプログラムインターフェイスは完全にスレッドセーフなので、実行スレッドに対する特別な注意は不要です。
OpenMP プログラミングパラダイムで実行スレッドを活用する MagickCore コードスニペットを示します。
CacheView
*image_view;
MagickBooleanType
status;
ssize_t
y;
/*
Acquire a cache view to enable parallelism.
*/
status=MagickTrue;
image_view=AcquireVirtualCacheView(image,exception);
#pragma omp parallel for schedule(static,4) shared(status)
for (y=0; y < (ssize_t) image->rows; y++)
{
register Quantum
*q;
register ssize_t
x;
register void
*metacontent;
if (status == MagickFalse)
continue;
/*
Get a row of pixels.
*/
q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
if (q == (Quantum *) NULL)
{
status=MagickFalse;
continue;
}
metacontent=GetCacheViewAuthenticMetacontent(image_view);
for (x=0; x < (ssize_t) image->columns; x++)
{
/*
Set the pixel color.
*/
SetPixelRed(image,...,q);
SetPixelGreen(image,...,q);
SetPixelBlue(image,...,q);
SetPixelAlpha(image,...,q);
if (metacontent != NULL)
metacontent[indexes+x]=...;
q+=GetPixelChannels(image);
}
/*
Sync the updated pixels to the pixel cache.
*/
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
status=MagickFalse;
}
/*
Destroy the cache view.
*/
image_view=DestroyCacheView(image_view);
if (status == MagickFalse)
perror("something went wrong");
このコードスニペットは非圧縮の Windows ビットマップを Magick++ 画像へ変換します。
#include "Magick++.h"
#include <assert.h>
#include "omp.h"
void ConvertBMPToImage(const BITMAPINFOHEADER *bmp_info,
const unsigned char *restrict pixels,Magick::Image *image)
{
/*
Prepare the image so that we can modify the pixels directly.
*/
assert(bmp_info->biCompression == BI_RGB);
assert(bmp_info->biWidth == image->columns());
assert(abs(bmp_info->biHeight) == image->rows());
image->modifyImage();
if (bmp_info->biBitCount == 24)
image->type(MagickCore::TrueColorType);
else
image->type(MagickCore::TrueColorMatteType);
register unsigned int bytes_per_row=bmp_info->biWidth*bmp_info->biBitCount/8;
if (bytes_per_row % 4 != 0) {
bytes_per_row=bytes_per_row+(4-bytes_per_row % 4); // divisible by 4.
}
/*
Copy all pixel data, row by row.
*/
#pragma omp parallel for
for (int y=0; y < int(image->rows()); y++)
{
int
row;
register const unsigned char
*restrict p;
register MagickCore::Quantum
*restrict q;
row=(bmp_info->biHeight > 0) ? (image->rows()-y-1) : y;
p=pixels+row*bytes_per_row;
q=image->setPixels(0,y,image->columns(),1);
for (int x=0; x < int(image->columns()); x++)
{
SetPixelBlue(image,p[0],q);
SetPixelGreen(image,p[1],q);
SetPixelRed(image,p[2],q);
if (bmp_info->biBitCount == 32) {
SetPixelAlpha(image,p[3],q);
}
q+=GetPixelChannels(image);
p+=bmp_info->biBitCount/8;
}
image->syncPixels(); // sync pixels to pixel cache.
}
return;
}
OpenMP 対応のアプリケーションから ImageMagick API を呼び出し、後続の並列領域で利用可能なスレッド数を動的に増やす予定がある場合、API を呼び出す前に増加を行うようにしてください。さもないと ImageMagick が異常終了することがあります。
MagickWand は wand ビューをサポートします。ビューは画像全体または一部を並列に反復し、画素の各行についてあなたが提供するコールバックメソッドを呼び出します。これにより、並列プログラミングの作業の大部分がその 1 モジュールに限定されます。MagickCore にも同様のメソッドがあります。例として、MagickWand と MagickCore の両方で実装された同じシグモイドコントラストアルゴリズムを参照してください。
ほとんどの状況では、最適な性能のためデフォルトのスレッド数はシステムのプロセッサコア数に設定されます。ただし、システムがハイパースレッディングされている場合や、仮想ホストで動作しプロセッサの一部のみがサーバーインスタンスで利用可能な場合、スレッド ポリシー または MAGICK_THREAD_LIMIT 環境変数を設定すると性能が向上することがあります。たとえば、仮想ホストに 8 プロセッサがあるがサーバーインスタンスには 2 つしか割り当てられていないとします。デフォルトの 8 スレッドは深刻な性能問題を引き起こすことがあります。1 つの解決策は、policy.xml 設定ファイルでスレッド数を利用可能なプロセッサに制限することです。
<policy domain="resource" name="thread" value="2"/>
あるいは、12 コアのハイパースレッディングコンピュータがデフォルトで 24 スレッドになるとします。MAGICK_THREAD_LIMIT 環境変数を設定すれば、性能が向上する可能性が高いです。
export MAGICK_THREAD_LIMIT=12
OpenMP 委員会は、OpenMP を Posix スレッドなどの他のスレッディングモデルと混在させたときの動作を定義していません。ただし、現代的な Linux リリースでは、OpenMP と Posix スレッドは問題なく相互運用するようです。Mac OS X や古い Linux リリースで ImageMagick アプリケーションプログラミングインターフェイス(例: MagickCore・MagickWand・Magick++ など)の 1 つを呼び出すプログラムモジュールから Posix スレッドを使いたい場合、ImageMagick 内の OpenMP サポートを無効にする必要があるかもしれません。configure スクリプトのコマンドラインに --disable-openmp オプションを追加し、ImageMagick を再ビルド・再インストールします。
tcmalloc メモリ割り当てライブラリでロック競合を減らし、性能をさらに向上できます。有効にするには、ImageMagick のビルド時に configure コマンドラインへ --with-tcmalloc を追加します。
スレッディングの性能
並列環境での動作を予測するのは難しいことがあります。性能は、コンパイラ・OpenMP ライブラリのバージョン・プロセッサタイプ・コア数・メモリ量・ハイパースレッディングの有無・ImageMagick と同時実行されるアプリケーションの組み合わせ・利用する特定の画像処理アルゴリズムなど、多くの要因に依存します。スレッド数の面で最適な性能を確実にする唯一の方法はベンチマークです。ImageMagick はコマンドのベンチマーク時にプログレッシブスレッディングを含み、1 つ以上のスレッドの経過時間と効率を返します。これにより、あなたの環境で何スレッドが最も効率的かを特定できます。このベンチマークでは、モデルの 1920x1080 画像を 1〜12 スレッドで 10 回シャープにします。
$ magick -bench 10 model.png -sharpen 5x2 null:
Performance[1]: 10i 1.135ips 1.000e 8.760u 0:08.810
Performance[2]: 10i 2.020ips 0.640e 9.190u 0:04.950
Performance[3]: 10i 2.786ips 0.710e 9.400u 0:03.590
Performance[4]: 10i 3.378ips 0.749e 9.580u 0:02.960
Performance[5]: 10i 4.032ips 0.780e 9.580u 0:02.480
Performance[6]: 10i 4.566ips 0.801e 9.640u 0:02.190
Performance[7]: 10i 3.788ips 0.769e 10.980u 0:02.640
Performance[8]: 10i 4.115ips 0.784e 12.030u 0:02.430
Performance[9]: 10i 4.484ips 0.798e 12.860u 0:02.230
Performance[10]: 10i 4.274ips 0.790e 14.830u 0:02.340
Performance[11]: 10i 4.348ips 0.793e 16.500u 0:02.300
Performance[12]: 10i 4.525ips 0.799e 18.320u 0:02.210
この例のスイートスポットは 6 スレッドです。物理コアが 6 つあるので理にかなっています。他の 6 つはハイパースレッドです。シャープ化はハイパースレッディングの恩恵を受けないようです。
場合によっては、スレッド数を 1 に設定するか、MAGICK_THREAD_LIMIT 環境変数・-limit コマンドラインオプション・policy.xml 設定ファイルで OpenMP を完全に無効にするのが最適なこともあります。
最適化された性能のための動的スロットリングの有効化
もう 1 つの効果的な選択肢は、ImageMagick で動的スロットリングを有効にすることです。この機能を有効にすると、ImageMagick はシステムの現在のロードアベレージに基づき、画像スキャンラインの処理間にわずかな遅延を自動的に挿入します。これはリソースの枯渇を防ぎ、特に高負荷時に滑らかな性能を保つのに役立ちます。
動的スロットリングを有効にするには、次のポリシー設定を追加します。
<policy domain="resource" name="dynamic-throttle" value="true"/>
異種分散処理
ImageMagick は OpenCL フレームワークによる異種分散処理のサポートを含みます。ImageMagick 内の OpenCL カーネルは、画像処理アルゴリズムを CPU・GPU・その他のプロセッサから成る異種プラットフォームで実行できるようにします。プラットフォームによっては、従来の単一 CPU より 1 桁高速化できることがあります。
まず、あなたの ImageMagick のバージョンが OpenCL 機能のサポートを含むことを確認します。
magick identify -version
Features: DPC Cipher Modules OpenCL OpenMP(4.5)
含む場合、画像の畳み込みを大幅に高速化するため次のコマンドを実行します。
magick image.png -convolve '-1, -1, -1, -1, 9, -1, -1, -1, -1' convolve.png
アクセラレータが利用できない、または応答に失敗した場合、ImageMagick は非アクセラレーションの畳み込みアルゴリズムに戻ります。
画像を畳み込む OpenCL カーネルの例を示します。
static inline long ClampToCanvas(const long offset,const ulong range)
{
if (offset < 0L)
return(0L);
if (offset >= range)
return((long) (range-1L));
return(offset);
}
static inline CLQuantum ClampToQuantum(const float value)
{
if (value < 0.0)
return((CLQuantum) 0);
if (value >= (float) QuantumRange)
return((CLQuantum) QuantumRange);
return((CLQuantum) (value+0.5));
}
__kernel void Convolve(const __global CLPixelType *source,__constant float *filter,
const ulong width,const ulong height,__global CLPixelType *destination)
{
const ulong columns = get_global_size(0);
const ulong rows = get_global_size(1);
const long x = get_global_id(0);
const long y = get_global_id(1);
const float scale = (1.0/QuantumRange);
const long mid_width = (width-1)/2;
const long mid_height = (height-1)/2;
float4 sum = { 0.0, 0.0, 0.0, 0.0 };
float gamma = 0.0;
register ulong i = 0;
for (long v=(-mid_height); v <= mid_height; v++)
{
for (long u=(-mid_width); u <= mid_width; u++)
{
register const ulong index=ClampToCanvas(y+v,rows)*columns+ClampToCanvas(x+u,
columns);
const float alpha=scale*(QuantumRange-source[index].w);
sum.x+=alpha*filter[i]*source[index].x;
sum.y+=alpha*filter[i]*source[index].y;
sum.z+=alpha*filter[i]*source[index].z;
sum.w+=filter[i]*source[index].w;
gamma+=alpha*filter[i];
i++;
}
}
gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
const ulong index=y*columns+x;
destination[index].x=ClampToQuantum(gamma*sum.x);
destination[index].y=ClampToQuantum(gamma*sum.y);
destination[index].z=ClampToQuantum(gamma*sum.z);
destination[index].w=ClampToQuantum(sum.w);
};
OpenCL カーネルによる画像畳み込みの完全な実装は MagickCore/accelerate.c を参照してください。
なお、Windows では TDR(GPU のタイムアウト検出と回復)の問題があるかもしれません。その目的は、実行時間のしきい値を使って GPU をハングさせる暴走タスクを検出することです。ImageMagick で OpenCL フィルタを実行する一部の古いローエンド GPU では、長い実行時間が TDR 機構を起動し、GPU 画像フィルタをプリエンプトすることがあります。これが起きると、ImageMagick は自動的に CPU コードパスにフォールバックし、期待される結果を返します。プリエンプションを避けるには、TdrDelay レジストリキーを増やします。
カスタム画像コーダー
画像コーダー(すなわちエンコーダー/デコーダー)は、1 つの画像形式(例: PNG・GIF・JPEG など)の登録・(任意で)分類・(任意で)読み込み・(任意で)書き出し・登録解除を担います。画像コーダーを登録すると、特定の形式が読み書き可能であることを ImageMagick に知らせます。登録解除はその形式がもう利用できないことを ImageMagick に伝えます。分類メソッドは画像の最初の数バイトを見て、画像が期待される形式かを判定します。読み込み側は画像サイズ・カラースペースなどのプロパティを設定し、画素キャッシュへ画素をロードします。読み込み側は単一の画像、または(形式がファイルあたり複数画像をサポートする場合は)画像シーケンスを返し、エラーが起きた場合は例外と null 画像を返します。書き出し側はその逆を行います。画像プロパティを取り、画素キャッシュをアンロードし、画像形式が必要とする形で書き出します。
サンプルの カスタムコーダー のリストを示します。これは MGK 画像形式で画像を読み書きします。MGK は単に ID、続いて画像の幅と高さ、続いて RGB 画素値という形式です。
#include <MagickCore/studio.h>
#include <MagickCore/blob.h>
#include <MagickCore/cache.h>
#include <MagickCore/colorspace.h>
#include <MagickCore/exception.h>
#include <MagickCore/image.h>
#include <MagickCore/list.h>
#include <MagickCore/magick.h>
#include <MagickCore/memory_.h>
#include <MagickCore/monitor.h>
#include <MagickCore/pixel-accessor.h>
#include <MagickCore/string_.h>
#include <MagickCore/module.h>
#include "filter/blob-private.h"
#include "filter/exception-private.h"
#include "filter/image-private.h"
#include "filter/monitor-private.h"
#include "filter/quantum-private.h"
/*
Forward declarations.
*/
static MagickBooleanType
WriteMGKImage(const ImageInfo *,Image *,ExceptionInfo *);
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% I s M G K %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% IsMGK() returns MagickTrue if the image format type, identified by the
% magick string, is MGK.
%
% The format of the IsMGK method is:
%
% MagickBooleanType IsMGK(const unsigned char *magick,const size_t length)
%
% A description of each parameter follows:
%
% o magick: This string is generally the first few bytes of an image file
% or blob.
%
% o length: Specifies the length of the magick string.
%
*/
static MagickBooleanType IsMGK(const unsigned char *magick,const size_t length)
{
if (length < 7)
return(MagickFalse);
if (LocaleNCompare((char *) magick,"id=mgk",7) == 0)
return(MagickTrue);
return(MagickFalse);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e a d M G K I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% ReadMGKImage() reads a MGK image file and returns it. It allocates the
% memory necessary for the new Image structure and returns a pointer to the
% new image.
%
% The format of the ReadMGKImage method is:
%
% Image *ReadMGKImage(const ImageInfo *image_info,
% ExceptionInfo *exception)
%
% A description of each parameter follows:
%
% o image_info: the image info.
%
% o exception: return any errors or warnings in this structure.
%
*/
static Image *ReadMGKImage(const ImageInfo *image_info,ExceptionInfo *exception)
{
char
buffer[MaxTextExtent];
Image
*image;
long
y;
MagickBooleanType
status;
register long
x;
register Quantum
*q;
register unsigned char
*p;
ssize_t
count;
unsigned char
*pixels;
unsigned long
columns,
rows;
/*
Open image file.
*/
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickCoreSignature);
if (image_info->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
image_info->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickCoreSignature);
image=AcquireImage(image_info,exception);
status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
if (status == MagickFalse)
{
image=DestroyImageList(image);
return((Image *) NULL);
}
/*
Read MGK image.
*/
(void) ReadBlobString(image,buffer); /* read magic number */
if (IsMGK(buffer,7) == MagickFalse)
ThrowReaderException(CorruptImageError,"ImproperImageHeader");
(void) ReadBlobString(image,buffer);
count=(ssize_t) sscanf(buffer,"%lu %lu\n",&columns,&rows);
if (count <= 0)
ThrowReaderException(CorruptImageError,"ImproperImageHeader");
do
{
/*
Initialize image structure.
*/
image->columns=columns;
image->rows=rows;
image->depth=8;
if ((image_info->ping != MagickFalse) && (image_info->number_scenes != 0))
if (image->scene >= (image_info->scene+image_info->number_scenes-1))
break;
/*
Convert MGK raster image to pixel packets.
*/
if (SetImageExtent(image,image->columns,image->rows,exception) == MagickFalse)
return(DestroyImageList(image));
pixels=(unsigned char *) AcquireQuantumMemory((size_t) image->columns,
3UL*sizeof(*pixels));
if (pixels == (unsigned char *) NULL)
ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
for (y=0; y < (long) image->rows; y++)
{
count=(ssize_t) ReadBlob(image,(size_t) (3*image->columns),pixels);
if (count != (ssize_t) (3*image->columns))
ThrowReaderException(CorruptImageError,"UnableToReadImageData");
p=pixels;
q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
if (q == (Quantum *) NULL)
break;
for (x=0; x < (long) image->columns; x++)
{
SetPixelRed(image,ScaleCharToQuantum(*p++),q);
SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
q+=GetPixelChannels(image);
}
if (SyncAuthenticPixels(image,exception) == MagickFalse)
break;
if (image->previous == (Image *) NULL)
if ((image->progress_monitor != (MagickProgressMonitor) NULL) &&
(QuantumTick(y,image->rows) != MagickFalse))
{
status=image->progress_monitor(LoadImageTag,y,image->rows,
image->client_data);
if (status == MagickFalse)
break;
}
}
pixels=(unsigned char *) RelinquishMagickMemory(pixels);
if (EOFBlob(image) != MagickFalse)
{
ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
image->filename);
break;
}
/*
Proceed to next image.
*/
if (image_info->number_scenes != 0)
if (image->scene >= (image_info->scene+image_info->number_scenes-1))
break;
*buffer='\0';
(void) ReadBlobString(image,buffer);
count=(ssize_t) sscanf(buffer,"%lu %lu\n",&columns,&rows);
if (count > 0)
{
/*
Allocate next image structure.
*/
AcquireNextImage(image_info,image,exception);
if (GetNextImageInList(image) == (Image *) NULL)
{
image=DestroyImageList(image);
return((Image *) NULL);
}
image=SyncNextImageInList(image);
if (image->progress_monitor != (MagickProgressMonitor) NULL)
{
status=SetImageProgress(image,LoadImageTag,TellBlob(image),
GetBlobSize(image));
if (status == MagickFalse)
break;
}
}
} while (count > 0);
(void) CloseBlob(image);
return(GetFirstImageInList(image));
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e g i s t e r M G K I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% RegisterMGKImage() adds attributes for the MGK image format to
% the list of supported formats. The attributes include the image format
% tag, a method to read and/or write the format, whether the format
% supports the saving of more than one frame to the same file or blob,
% whether the format supports native in-memory I/O, and a brief
% description of the format.
%
% The format of the RegisterMGKImage method is:
%
% unsigned long RegisterMGKImage(void)
%
*/
ModuleExport unsigned long RegisterMGKImage(void)
{
MagickInfo
*entry;
entry=AcquireMagickInfo("MGK","MGK","MGK image");
entry->decoder=(DecodeImageHandler *) ReadMGKImage;
entry->encoder=(EncodeImageHandler *) WriteMGKImage;
entry->magick=(IsImageFormatHandler *) IsMGK;
(void) RegisterMagickInfo(entry);
return(MagickImageCoderSignature);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% U n r e g i s t e r M G K I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% UnregisterMGKImage() removes format registrations made by the
% MGK module from the list of supported formats.
%
% The format of the UnregisterMGKImage method is:
%
% UnregisterMGKImage(void)
%
*/
ModuleExport void UnregisterMGKImage(void)
{
(void) UnregisterMagickInfo("MGK");
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% W r i t e M G K I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% WriteMGKImage() writes an image to a file in red, green, and blue MGK
% rasterfile format.
%
% The format of the WriteMGKImage method is:
%
% MagickBooleanType WriteMGKImage(const ImageInfo *image_info,
% Image *image)
%
% A description of each parameter follows.
%
% o image_info: the image info.
%
% o image: The image.
%
% o exception: return any errors or warnings in this structure.
%
*/
static MagickBooleanType WriteMGKImage(const ImageInfo *image_info,Image *image,
ExceptionInfo *exception)
{
char
buffer[MaxTextExtent];
long
y;
MagickBooleanType
status;
MagickOffsetType
scene;
register const Quantum
*p;
register long
x;
register unsigned char
*q;
unsigned char
*pixels;
/*
Open output image file.
*/
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickCoreSignature);
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
if (status == MagickFalse)
return(status);
scene=0;
do
{
/*
Allocate memory for pixels.
*/
if (image->colorspace != RGBColorspace)
(void) SetImageColorspace(image,RGBColorspace,exception);
pixels=(unsigned char *) AcquireQuantumMemory((size_t) image->columns,
3UL*sizeof(*pixels));
if (pixels == (unsigned char *) NULL)
ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
/*
Initialize raster file header.
*/
(void) WriteBlobString(image,"id=mgk\n");
(void) FormatLocaleString(buffer,MaxTextExtent,"%lu %lu\n",image->columns,
image->rows);
(void) WriteBlobString(image,buffer);
for (y=0; y < (long) image->rows; y++)
{
p=GetVirtualPixels(image,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
q=pixels;
for (x=0; x < (long) image->columns; x++)
{
*q++=ScaleQuantumToChar(GetPixelRed(image,p));
*q++=ScaleQuantumToChar(GetPixelGreen(image,p));
*q++=ScaleQuantumToChar(GetPixelBlue(image,p));
p+=GetPixelChannels(image);
}
(void) WriteBlob(image,(size_t) (q-pixels),pixels);
if (image->previous == (Image *) NULL)
if ((image->progress_monitor != (MagickProgressMonitor) NULL) &&
(QuantumTick(y,image->rows) != MagickFalse))
{
status=image->progress_monitor(SaveImageTag,y,image->rows,
image->client_data);
if (status == MagickFalse)
break;
}
}
pixels=(unsigned char *) RelinquishMagickMemory(pixels);
if (GetNextImageInList(image) == (Image *) NULL)
break;
image=SyncNextImageInList(image);
status=SetImageProgress(image,SaveImagesTag,scene,
GetImageListLength(image));
if (status == MagickFalse)
break;
scene++;
} while (image_info->adjoin != MagickFalse);
(void) CloseBlob(image);
return(MagickTrue);
}
コマンドラインからカスタムコーダーを呼び出すには、次のコマンドを使います。
magick logo: logo.mgk
display logo.mgk
独自のカスタムコーダーの作成を始める助けとして Magick Coder Kit を提供しています。
ビルドの前に、ImageMagick がデフォルトのシステムパスにない場合は PKG_CONFIG_PATH 環境変数を設定します。
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
カスタム画像フィルタ
ImageMagick は、独自のカスタム画像処理アルゴリズムを追加する便利な仕組みを提供します。これを画像フィルタと呼び、コマンドラインの -process オプション、または MagickCore API メソッド ExecuteModuleProcess() から呼び出します。
サンプルの カスタム画像フィルタ のリストを示します。これは画素の輝度・彩度の平均と標準偏差などのいくつかの統計を計算します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <math.h>
#include "MagickCore/studio.h"
#include "MagickCore/MagickCore.h"
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% a n a l y z e I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% analyzeImage() computes the brightness and saturation mean, standard
% deviation, kurtosis and skewness and stores these values as attributes
% of the image.
%
% The format of the analyzeImage method is:
%
% size_t analyzeImage(Image *images,const int argc,char **argv,
% ExceptionInfo *exception)
%
% A description of each parameter follows:
%
% o image: the address of a structure of type Image.
%
% o argc: Specifies a pointer to an integer describing the number of
% elements in the argument vector.
%
% o argv: Specifies a pointer to a text array containing the command line
% arguments.
%
% o exception: return any errors or warnings in this structure.
%
*/
typedef struct _StatisticsInfo
{
double
area,
brightness,
mean,
standard_deviation,
sum[5],
kurtosis,
skewness;
} StatisticsInfo;
static inline int GetMagickNumberThreads(const Image *source,
const Image *destination,const size_t chunk,int multithreaded)
{
#define MagickMax(x,y) (((x) > (y)) ? (x) : (y))
#define MagickMin(x,y) (((x) < (y)) ? (x) : (y))
/*
Number of threads bounded by the amount of work and any thread resource
limit. The limit is 2 if the pixel cache type is not memory or
memory-mapped.
*/
if (multithreaded == 0)
return(1);
if (((GetImagePixelCacheType(source) != MemoryCache) &&
(GetImagePixelCacheType(source) != MapCache)) ||
((GetImagePixelCacheType(destination) != MemoryCache) &&
(GetImagePixelCacheType(destination) != MapCache)))
return(MagickMax(MagickMin(GetMagickResourceLimit(ThreadResource),2),1));
return(MagickMax(MagickMin((ssize_t) GetMagickResourceLimit(ThreadResource),
(ssize_t) (chunk)/64),1));
}
ModuleExport size_t analyzeImage(Image **images,const int argc,
const char **argv,ExceptionInfo *exception)
{
#define AnalyzeImageFilterTag "Filter/Analyze"
#define magick_number_threads(source,destination,chunk,multithreaded) \
num_threads(GetMagickNumberThreads(source,destination,chunk,multithreaded))
char
text[MagickPathExtent];
Image
*image;
MagickBooleanType
status;
MagickOffsetType
progress;
assert(images != (Image **) NULL);
assert(*images != (Image *) NULL);
assert((*images)->signature == MagickCoreSignature);
(void) argc;
(void) argv;
status=MagickTrue;
progress=0;
for (image=(*images); image != (Image *) NULL; image=GetNextImageInList(image))
{
CacheView
*image_view;
double
area;
ssize_t
y;
StatisticsInfo
brightness,
saturation;
if (status == MagickFalse)
continue;
(void) memset(&brightness,0,sizeof(brightness));
(void) memset(&saturation,0,sizeof(saturation));
status=MagickTrue;
image_view=AcquireVirtualCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static) \
shared(progress,status,brightness,saturation) \
magick_number_threads(image,image,image->rows,1)
#endif
for (y=0; y < (ssize_t) image->rows; y++)
{
const Quantum
*p;
ssize_t
i,
x;
StatisticsInfo
local_brightness,
local_saturation;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
{
status=MagickFalse;
continue;
}
(void) memset(&local_brightness,0,sizeof(local_brightness));
(void) memset(&local_saturation,0,sizeof(local_saturation));
for (x=0; x < (ssize_t) image->columns; x++)
{
double
b,
h,
s;
ConvertRGBToHSL(GetPixelRed(image,p),GetPixelGreen(image,p),
GetPixelBlue(image,p),&h,&s,&b);
b*=QuantumRange;
for (i=1; i <= 4; i++)
local_brightness.sum[i]+=pow(b,(double) i);
s*=QuantumRange;
for (i=1; i <= 4; i++)
local_saturation.sum[i]+=pow(s,(double) i);
p+=GetPixelChannels(image);
}
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp critical (analyzeImage)
#endif
for (i=1; i <= 4; i++)
{
brightness.sum[i]+=local_brightness.sum[i];
saturation.sum[i]+=local_saturation.sum[i];
}
}
image_view=DestroyCacheView(image_view);
area=(double) image->columns*image->rows;
brightness.mean=brightness.sum[1]/area;
(void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.mean);
(void) SetImageProperty(image,"filter:brightness:mean",text,exception);
brightness.standard_deviation=sqrt(brightness.sum[2]/area-
(brightness.sum[1]/area*brightness.sum[1]/area));
(void) FormatLocaleString(text,MagickPathExtent,"%g",
brightness.standard_deviation);
(void) SetImageProperty(image,"filter:brightness:standard-deviation",text,
exception);
if (fabs(brightness.standard_deviation) >= MagickEpsilon)
brightness.kurtosis=(brightness.sum[4]/area-4.0*brightness.mean*
brightness.sum[3]/area+6.0*brightness.mean*brightness.mean*
brightness.sum[2]/area-3.0*brightness.mean*brightness.mean*
brightness.mean*brightness.mean)/(brightness.standard_deviation*
brightness.standard_deviation*brightness.standard_deviation*
brightness.standard_deviation)-3.0;
(void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.kurtosis);
(void) SetImageProperty(image,"filter:brightness:kurtosis",text,exception);
if (brightness.standard_deviation != 0)
brightness.skewness=(brightness.sum[3]/area-3.0*brightness.mean*
brightness.sum[2]/area+2.0*brightness.mean*brightness.mean*
brightness.mean)/(brightness.standard_deviation*
brightness.standard_deviation*brightness.standard_deviation);
(void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.skewness);
(void) SetImageProperty(image,"filter:brightness:skewness",text,exception);
saturation.mean=saturation.sum[1]/area;
(void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.mean);
(void) SetImageProperty(image,"filter:saturation:mean",text,exception);
saturation.standard_deviation=sqrt(saturation.sum[2]/area-
(saturation.sum[1]/area*saturation.sum[1]/area));
(void) FormatLocaleString(text,MagickPathExtent,"%g",
saturation.standard_deviation);
(void) SetImageProperty(image,"filter:saturation:standard-deviation",text,
exception);
if (fabs(saturation.standard_deviation) >= MagickEpsilon)
saturation.kurtosis=(saturation.sum[4]/area-4.0*saturation.mean*
saturation.sum[3]/area+6.0*saturation.mean*saturation.mean*
saturation.sum[2]/area-3.0*saturation.mean*saturation.mean*
saturation.mean*saturation.mean)/(saturation.standard_deviation*
saturation.standard_deviation*saturation.standard_deviation*
saturation.standard_deviation)-3.0;
(void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.kurtosis);
(void) SetImageProperty(image,"filter:saturation:kurtosis",text,exception);
if (fabs(saturation.standard_deviation) >= MagickEpsilon)
saturation.skewness=(saturation.sum[3]/area-3.0*saturation.mean*
saturation.sum[2]/area+2.0*saturation.mean*saturation.mean*
saturation.mean)/(saturation.standard_deviation*
saturation.standard_deviation*saturation.standard_deviation);
(void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.skewness);
(void) SetImageProperty(image,"filter:saturation:skewness",text,exception);
if (image->progress_monitor != (MagickProgressMonitor) NULL)
{
MagickBooleanType
proceed;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp atomic
#endif
progress++;
proceed=SetImageProgress(image,AnalyzeImageFilterTag,progress,
GetImageListLength(image));
if (proceed == MagickFalse)
status=MagickFalse;
}
}
return(MagickImageFilterSignature);
}
コマンドラインからカスタムフィルタを呼び出すには、次のコマンドを使います。
magick logo: -process \"analyze\" -verbose info:
Image: logo:
Format: LOGO (ImageMagick Logo)
Class: PseudoClass
Geometry: 640x480
...
filter:brightness:kurtosis: 3.97886
filter:brightness:mean: 58901.3
filter:brightness:skewness: -2.30827
filter:brightness:standard-deviation: 16179.8
filter:saturation:kurtosis: 6.59719
filter:saturation:mean: 5321.05
filter:saturation:skewness: 2.75679
filter:saturation:standard-deviation: 14484.7
独自のカスタム画像フィルタの作成を始める助けとして Magick Filter Kit を提供しています。
ビルドの前に、ImageMagick がデフォルトのシステムパスにない場合は PKG_CONFIG_PATH 環境変数を設定します。
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig