ピクセルキャッシュ • 画像プロパティとプロファイル • マルチスペクトル画像 • 大規模画像サポート • ストリーミングピクセル • 実行スレッド • 異種分散処理 • カスタム画像コーデック • カスタム画像フィルタ
オズの住民たちは、全能の魔法使いという恩人に対して非常に満足していました。彼らは、その力のある所以や理由、場所を疑問に思うことなく、魔法使いの知恵と慈悲を受け入れていました。オズの住民たちのように、ImageMagickが画像の変換、編集、合成に役立つことを知っていれば、その裏側の仕組みを知らなくても問題ない場合は、このセクションを飛ばしても構いません。しかし、ImageMagickの背後にあるソフトウェアとアルゴリズムについてもっと知りたい場合は、読み進めてください。この説明を十分に理解するには、画像の命名法を理解し、コンピュータプログラミングに精通している必要があります。
アーキテクチャの概要
画像は通常、長方形のピクセル領域とメタデータで構成されています。画像を効率的に変換、編集、または合成するには、領域内の(そして場合によっては領域外の)任意のピクセルに簡単にアクセスできる必要があります。そして、画像シーケンスの場合、シーケンス内の任意の画像の任意の領域の任意のピクセルにアクセスできる必要があります。しかし、JPEG、TIFF、PNG、GIFなど、数百もの画像フォーマットがあり、オンデマンドでピクセルにアクセスすることが困難になっています。これらのフォーマット内では、次のような違いがあります。
- カラー空間(例:sRGB、線形RGB、線形GRAY、CMYK、YUV、Labなど)
- ビット深度(例:1、4、8、12、16など)
- ストレージフォーマット(例:符号なし、符号付き、浮動小数点数など)
- 圧縮(例:非圧縮、RLE、Zip、BZipなど)
- 方向(例:上から下、右から左など)
- レイアウト(例:raw、オペコードと混在など)
さらに、一部の画像ピクセルには減衰が必要な場合があり、一部のフォーマットでは複数のフレームが許可され、一部のフォーマットには最初にラスタライズ(ベクトルからピクセルへの変換)する必要があるベクトルグラフィックが含まれています。
画像処理アルゴリズムの効率的な実装には、次の操作が必要になる場合があります。
- 一度に1ピクセル(例:位置10,3のピクセル)
- 1本の走査線(例:行4のすべてのピクセル)
- 一度にいくつかの走査線(例:ピクセル行4〜7)
- ピクセルの1列または複数の列(例:列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画像は、ImageMagickのQ8バージョンを使用している場合は8ビットグレイ画像に、Q16バージョンでは16ビットRGBAにスケールされます。使用しているバージョンは、-versionオプションで確認できます。
$ identify -version
Version: ImageMagick 7.1.1-38 2024-05-05 Q16 https://imagemagick.dokyumento.jp
ご覧のとおり、ピクセルキャッシュの利便性には、ストレージ(例: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() は、要求する領域が画像領域の範囲内にあることを要求します。640 x 480 の画像の場合、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 ピクセルキャッシュのシンプルでエレガントな設計は、ストレージと処理速度の点でコストがかかることを思い出してください。ピクセルキャッシュのストレージ要件は、画像の面積とピクセルコンポーネントのビット深度によってスケールします。たとえば、640 x 480 の画像を使用していて、ImageMagick の非 HDRI Q16 バージョンを使用している場合、ピクセルキャッシュは画像 幅 * 高さ * ビット深度 / 8 * チャネル バイト、つまり約 2.3 メビバイト (つまり 640 * 480 * 2 * 4) を消費します。それほど悪くはありませんが、画像が 25000 x 25000 ピクセルだったらどうでしょうか?ピクセルキャッシュは約 4.7 ギビバイトのストレージが必要です。大変です。ImageMagick は、巨大なストレージ要件の可能性に対応するために、大規模な画像をメモリではなくディスクにキャッシュします。通常、ピクセルキャッシュはヒープメモリを使用してメモリに格納されます。ヒープメモリが使い果たされると、ディスク上にピクセルキャッシュを作成し、メモリマップしようとします。メモリマップメモリが使い果たされると、標準のディスクI/Oを使用します。ディスクストレージは豊富で安価ですが、非常に遅く、メモリ内のピクセルへのアクセスよりも 1000 倍以上遅くなります。ディスクベースのキャッシュをメモリマップすると、最大 5 倍の速度向上を実現できます。ストレージに関するこれらの決定は、ピクセルキャッシュマネージャがオペレーティングシステムとネゴシエートすることにより自動的に行われます。ただし、キャッシュリソース制限を使用して、ピクセルキャッシュマネージャがピクセルキャッシュを割り当てる方法に影響を与えることができます。制限には以下が含まれます。
width | 画像の最大幅。この制限を超えると例外がスローされ、操作は中断されます。 |
height | 画像の最大高さ。この制限を超えると例外がスローされ、操作は中断されます。 |
area | ピクセルキャッシュメモリに存在できる任意の1つの画像の最大面積(バイト単位)。この制限を超えると、画像は自動的にディスクにキャッシュされ、オプションでメモリマップされます。 |
memory | ヒープからピクセルキャッシュに割り当てるメモリの最大量(バイト単位)。 |
map | ピクセルキャッシュに割り当てるメモリマップの最大量(バイト単位)。 |
disk | ピクセルキャッシュで使用できるディスク容量の最大量(バイト単位)。この制限を超えると、致命的な例外がスローされ、すべての処理が停止します。 |
files | 開いているピクセルキャッシュファイルの最大数。この制限を超えると、その後ディスクにキャッシュされたピクセルは閉じられ、必要に応じて再度開かれます。この動作により、ピクセルキャッシュのオープン/クローズシステムコールの数を減らすことで、多数の画像をディスク上で同時にアクセスできます。 |
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.dokyumento.jp/script/security-policy.php,
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つのセッションですべての使用可能なメモリを消費させたくありません。このポリシーにより、大規模な画像はディスクにキャッシュされます。画像が大きすぎてピクセルキャッシュのディスク制限を超えると、プログラムが終了します。さらに、実行が制御不能になる処理タスクを防ぐために、時間制限を設定しています。幅または高さが 8192 ピクセルを超える画像があると、例外がスローされ、処理が停止します。ImageMagick 7.0.1-8 以降では、任意のデリゲートまたはすべてのデリゲートの使用を禁止できます(パターンを "*" に設定)。このリリースより前には、デリゲートの使用を禁止するために "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つのピクセルキャッシュ領域しか処理できません。同じ画像の最初と最後のスキャンラインに同時にアクセスしたいとしますか?解決策はキャッシュビューを使用することです。キャッシュビューを使用すると、必要な数の領域に同時にピクセルキャッシュでアクセスできます。キャッシュビューのメソッドは、前のメソッドと類似していますが、最初にビューを開いて、終了したら閉じる必要があります。画像の最初と最後のピクセル行に同時にアクセスできる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 の拡張子を持つファイルで、画像または画像シーケンスに関連付けられたすべてのプロパティ(幅、高さ、カラー空間など)を保持し、もう1つは .cache の拡張子を持つファイルで、ネイティブの生形式のピクセルキャッシュです。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バージョンのImageMagickを検討してください。さらに、Q8バージョンは通常、Q16バージョンよりも高速に実行されます。
ほとんどの画像形式とアルゴリズムは、0から最大値までの固定範囲のピクセル値に制限されています(例:ImageMagickの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はウェブサイトに最適です。画像の読み書きのオーバーヘッドを削減します。オンライン画像スタジオでは、MPCを独占的に使用しています。
画像のプロパティとプロファイル
画像には、プロパティ(幅、高さ、説明など)とプロファイル(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はメタチャネル)のマルチスペクトル画像を完全にサポートしています。設定スクリプトの--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);
また、streamという軽量ツールも提供しており、画像または画像の一部を1つ以上のピクセルコンポーネントを、選択したストレージ形式にストリーミングできます。これは、入力画像から読み取られたピクセルコンポーネントを1行ずつ書き込むため、大きな画像を処理する場合や、生のピクセルコンポーネントが必要な場合にstreamが好ましいです。ほとんどの画像形式は、ピクセル(赤、緑、青)を左から右、上から下にストリーミングします。ただし、一部の形式では、この一般的な順序付けをサポートしていません(例:PSD形式)。
大型画像のサポート
ImageMagickは、メガピクセルからテラピクセルに及ぶ画像サイズを処理する機能を備えており、読み取り、処理、書き込み操作を含みます。理論的には、32ビットオペレーティングシステムでは最大3100万行/列、64ビットOSでは最大31兆行/列まで画像の寸法を拡張できます。ただし、実際に達成可能な寸法は、ホストコンピューターで使用可能なリソースによって大きく異なります。一部の画像形式では、画像サイズに制限があることに注意することが重要です。たとえば、Photoshop画像は、幅または高さが最大30万ピクセルに制限されています。ここでは、画像を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はワンドビューをサポートしています。ビューは画像全体または画像の一部を並列で反復処理し、各ピクセル行に対して、ユーザーが提供するコールバックメソッドを呼び出します。これにより、並列プログラミングアクティビティの大部分は、その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++など)を呼び出すプログラムモジュールからPOSIXスレッドを使用する場合は、ImageMagick内でOpenMPサポートを無効にする必要があるかもしれません。--disable-openmpオプションをconfigureスクリプトのコマンドラインに追加し、ImageMagickを再構築して再インストールしてください。
tcmallocメモリ割り当てライブラリを使用することで、ロック競合を減らし、パフォーマンスをさらに向上させることができます。有効にするには、ImageMagickをビルドする際に--with-tcmallocをconfigureコマンドラインに追加します。
スレッドのパフォーマンス
並列環境での動作を予測することは困難です。パフォーマンスは、コンパイラ、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つはハイパースレッドです。シャープニングはハイパースレッディングから恩恵を受けていないようです。
場合によっては、MAGICK_THREAD_LIMIT環境変数、-limitコマンドラインオプション、またはpolicy.xml設定ファイルを使用して、スレッド数を1に設定するか、OpenMPを完全に無効にすることが最適な場合があります。
異種分散処理
ImageMagickは、OpenCLフレームワークを使用した異種分散処理をサポートしています。ImageMagick内のOpenCLカーネルにより、CPU、GPU、その他のプロセッサで構成される異種プラットフォーム全体で画像処理アルゴリズムを実行できます。プラットフォームによっては、従来のシングルCPUよりも桁違いに高速化できます。
まず、お使いの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に伝えます。分類メソッドは、画像の先頭数バイトを調べて、画像が期待されるフォーマットかどうかを判断します。リーダーは、画像サイズ、カラー空間、その他のプロパティを設定し、ピクセルキャッシュにピクセルをロードします。リーダーは、単一の画像または画像シーケンス(フォーマットがファイルごとに複数の画像をサポートしている場合)、またはエラーが発生した場合は、例外とヌル画像を返します。ライターは逆の処理を行います。画像プロパティを取得し、ピクセルキャッシュをアンロードして、画像フォーマットで必要なように書き込みます。
サンプルカスタムコーダのリストを次に示します。これは、ID、画像の幅と高さ、RGBピクセル値が続くMGK画像形式の画像を読み書きします。
#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