チュートリアルメニューこのページの目次

建築

オズの住民は、恩人である全能の魔法使いにとても満足していました。彼らは、その力の誰が、なぜ、どこにあるのかを疑問視することなく、彼の知恵と慈悲を受け入れました。オズの住民のように、カーテンの裏で何が起こっているのかを知らずに、ImageMagick が画像を変換、編集、または合成するのに役立つと安心できる場合は、このセクションをスキップしてください。ただし、ImageMagick の背後にあるソフトウェアとアルゴリズムについて詳しく知りたい場合は、読み進めてください。この説明から十分に利益を得るには、画像の命名法に慣れ、コンピュータ プログラミングに精通している必要があります。

アーキテクチャの概要

画像は通常、ピクセルとメタデータの長方形領域で構成されます。画像を効率的に変換、編集、または合成するには、領域内 (場合によっては領域外) の任意のピクセルに簡単にアクセスする必要があります。また、画像シーケンスの場合は、シーケンス内の任意の画像の任意の領域の任意のピクセルにアクセスする必要があります。ただし、JPEG、TIFF、PNG、GIF など、画像形式は数百種類あり、オンデマンドでピクセルにアクセスするのは困難です。これらの形式には、次のような違いがあります:

  • 色空間 (例: sRGB、リニア RGB、リニア GRAY、CMYK、YUV、Lab など)
  • ビット深度 (例: 1、4、8、12、16 など)
  • 保存形式 (例: unsigned、signed、float、double など)
  • 圧縮 (例: 非圧縮、RLE、Zip、BZip など)
  • 方向 (例: 上から下、右から左など)
  • レイアウト (例: raw、opcode が散在するなど)

さらに、一部の画像ピクセルは減衰が必要な場合があり、一部の形式では複数のフレームが許可され、一部の形式には最初にラスタライズ(ベクターからピクセルへの変換)する必要があるベクターグラフィックが含まれています。

画像処理アルゴリズムを効率的に実装するには、次のものを取得または設定する必要があります。

  • 1 度に 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 pixel cache を実装し、画像領域内の任意の場所 (つまり authentic pixels) およびシーケンス内の任意の画像から、オンデマンドで任意のピクセルに便利な順次または並列アクセスを提供します。さらに、ピクセル キャッシュにより、画像で定義された境界外のピクセル (つまり virtual pixels) にアクセスできます。

ピクセルに加えて、画像にはさまざまな image properties and profiles があります。プロパティには、幅、高さ、深さ、色空間などのよく知られた属性が含まれます。画像には、画像の作成者、コメント、作成日などを含むオプションのプロパティがある場合があります。一部の画像には、色管理用のプロファイル、または EXIF、IPTC、8BIM、または XMP 情報プロファイルも含まれています。ImageMagick には、画像のプロパティまたはプロファイルを取得、設定、または表示したり、プロファイルを適用したりするためのコマンド ライン オプションとプログラミング メソッドが用意されています。

ImageMagick は、約 50 万行の C コードで構成されており、オプションで依存ライブラリ (JPEG、PNG、TIFF ライブラリなど) の数百万行のコードに依存しています。そのため、膨大なアーキテクチャ ドキュメントが期待されるかもしれません。ただし、画像処理の大部分はピクセルとそのメタデータにアクセスするだけであり、当社のシンプルでエレガントで効率的な実装により、ImageMagick 開発者にとってこれが簡単になります。次のいくつかのセクションでは、ピクセル キャッシュの実装と、イメージ プロパティとプロファイルの取得と設定について説明します。次に、実行の thread 内での ImageMagick の使用について説明します。最後のセクションでは、特定のイメージ フォーマットの読み取りまたは書き込みについて説明し、その後、カスタム要件に基づいてピクセルにアクセスまたは更新するための filter の作成について簡単に説明します。

ピクセル キャッシュ

ImageMagick ピクセル キャッシュは、最大 64 チャネルの画像ピクセルのリポジトリです。チャネルは、ImageMagick の構築時に指定された深度で連続して保存されます。チャネル深度は、ImageMagick の Q8 バージョンでは 8 ビット/ピクセル コンポーネント、Q16 バージョンでは 16 ビット/ピクセル コンポーネント、Q32 バージョンでは 32 ビット/ピクセル コンポーネントです。デフォルトでは、ピクセル コンポーネントは 32 ビット浮動小数点 high dynamic-range 量です。チャネルは任意の値を保持できますが、通常は赤、緑、青、アルファの強度、またはシアン、マゼンタ、黄、黒、アルファの強度が含まれます。チャネルには、カラーマップ イメージのカラーマップ インデックス、または 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 -versionVersion: ImageMagick 7.1.1-38 2024-05-05 Q16 https://imagemagick.org

ご覧のとおり、ピクセル キャッシュの利便性は、ストレージ (たとえば、1 ビットのモノクロ画像を 16 ビットとして保存するのは無駄です) と速度 (つまり、メモリに画像全体を保存すると、一度に 1 スキャンラインのピクセルにアクセスするよりも一般的に遅くなります) のトレードオフを伴う場合があります。ほとんどの場合、ピクセル キャッシュの利点は、通常、欠点を上回ります。

ピクセル キャッシュにアクセスする

ピクセル キャッシュがイメージに関連付けられると、通常はピクセルを取得、更新、または配置することになります。イメージ領域内のピクセルは authentic pixels、領域外のピクセルは virtual pixels と呼ばれます。キャッシュ内のピクセルにアクセスするには、次のメソッドを使用します:

  • 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() にはこの制約はありません。たとえば、

は、一部がイメージ領域の範囲内にない場合でも、要求したピクセルを問題なく提供します。

仮想ピクセル

関心のあるピクセルの周囲のピクセルを必要とする画像処理アルゴリズムは数多くあります。アルゴリズムには通常、エッジ ピクセルと呼ばれる画像境界周辺のピクセルの処理方法に関する注意事項が含まれています。仮想ピクセルを使用すると、アルゴリズムに最も適した仮想ピクセル メソッドを選択すること以外は、特別なエッジ処理について心配する必要はありません。

仮想ピクセルへのアクセスは、MagickCore API の SetImageVirtualPixelMethod() メソッドまたはコマンド ラインの -virtual-pixel オプションによって制御されます。方法には次のものがあります:

背景画像の周囲の領域は背景色です
画像の周囲の領域は黒です
チェッカータイル画像と背景色の交互の正方形
ディザ非ランダム 32x32 ディザパターン
エッジエッジ ピクセルを無限大に拡張します (デフォルト)
グレー画像の周囲の領域はグレーです
水平タイル画像を水平に並べて、背景色を上/下に配置します
水平タイル エッジ画像を水平に並べて、サイド エッジを複製しますピクセル
ミラーミラー 画像をタイル状に並べる
ランダム画像からランダムにピクセルを選択する
タイル画像をタイル状に並べる
透明画像の周囲の領域は透明な黒色です
垂直タイル画像を垂直にタイル状に並べ、側面は背景になります色
垂直タイルエッジ画像を垂直に並べて、サイド エッジ ピクセルを複製します
画像の周囲の領域は白です

キャッシュ ストレージとリソース要件

ImageMagick ピクセル キャッシュのこのシンプルで洗練された設計には、ストレージと処理速度の面でコストがかかることを思い出してください。ピクセル キャッシュ ストレージ要件は、画像の領域とピクセル コンポーネントのビット深度に応じて増減します。たとえば、640 x 480 の画像があり、ImageMagick の非 HDRI Q16 バージョンを使用している場合、ピクセル キャッシュは image width * height * bit-depth / 8 * channels バイト、つまり約 2.3 メビバイト (つまり 640 * 480 * 2 * 4) を消費します。それほど悪くはありませんが、画像が 25000 x 25000 ピクセルの場合はどうでしょうか。ピクセル キャッシュには約 4.7 ギビバイトのストレージが必要です。痛いですね。ImageMagick は、大きな画像をメモリではなくディスクにキャッシュすることで、膨大なストレージ要件に対応しています。通常、ピクセル キャッシュはヒープ メモリを使用してメモリに格納されます。ヒープ メモリが使い果たされた場合、ディスク上にピクセル キャッシュを作成し、メモリ マップを試みます。メモリ マップ メモリが使い果たされた場合は、標準のディスク I/O を使用します。ディスク ストレージは豊富で安価ですが、非常に遅く、メモリ内のピクセルにアクセスするよりも 1000 倍以上遅くなります。ディスクベースのキャッシュをメモリマップすると、最大 5 倍の速度向上が得られます。ストレージに関するこれらの決定は、ピクセル キャッシュ マネージャーがオペレーティング システムとネゴシエートすることによって行われます。ただし、ピクセル キャッシュ マネージャーがピクセル キャッシュを割り当てる方法を cache resource limits で制御できます。制限は次のとおりです:

画像の最大幅。この制限を超えると、例外がスローされ、操作が中止されます。
高さ画像の最大高さ。この制限を超えると例外がスローされ、操作は中止されます。
領域ピクセル キャッシュ メモリに存在できる 1 つの画像の最大領域 (バイト単位)。この制限を超えると、画像は自動的にディスクにキャッシュされ、オプションでメモリ マップされます。
メモリヒープからピクセル キャッシュに割り当てるメモリの最大量 (バイト単位)。
マップピクセル キャッシュに割り当てるメモリ マップの最大量 (バイト単位)。
ディスクピクセル キャッシュで使用できるディスク領域の最大量 (バイト単位)。この制限を超えると、致命的な例外がスローされ、すべての処理が停止します。
ファイル開いているピクセル キャッシュ ファイルの最大数。この制限を超えると、ディスクにキャッシュされた後続のピクセルは閉じられ、必要に応じて再度開かれます。この動作により、ピクセル キャッシュのオープン/クローズ システム コールの数を減らすことで、速度を低下させることなく、ディスク上で多数の画像に同時にアクセスできます。
スレッド並列実行が許可されるスレッドの最大数。システムは、この値より少ないスレッド数を選択する場合があります。ImageMagick は、デフォルトで最適なスレッド数を選択します。これは通常、ホスト上のコア数です。この値を 1 に設定すると、すべての並列領域が 1 つのスレッドで実行されます。
時間プロセスの実行が許可される最大秒数。この制限を超えると、例外がスローされ、処理が停止します。

これらの制限は 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

これらの制限は、security policy (policy.xml を参照)、environment variable-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/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 つのイメージにつき 1 つのピクセル キャッシュ領域しか処理できません。同じイメージの最初と最後のスキャンラインに同時にアクセスしたいとします。解決策は cache view を使用することです。キャッシュ ビューを使用すると、必要な数のピクセル キャッシュ領域に同時にアクセスできます。キャッシュ ビュー methods は、最初にビューを開いて、終了したら閉じる必要がある点を除けば、前のメソッドと似ています。以下は、イメージの最初と最後のピクセル行に同時にアクセスできる 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 Pixel Cache (MPC) 形式は、画像形式との間でピクセルをデコードおよびエンコードする際のオーバーヘッドをなくすように設計されています。MPC は 2 つのファイルを書き込みます。1 つは拡張子 .mpc で、画像または画像シーケンスに関連付けられたすべてのプロパティ (幅、高さ、色空間など) を保持し、もう 1 つは拡張子 .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() は、キャッシュ ビューの対応するものよりわずかに効率的です。ただし、イメージの複数の領域に同時にアクセスする必要がある場合、または複数の thread of execution がイメージにアクセスしている場合は、キャッシュ ビューが必要です。

GetVirtualPixels() または GetCacheViewVirtualPixels() を使用してイメージの境界外のピクセルを要求できますが、イメージ領域の範囲内のピクセルを要求する方が効率的です。

適切なリソース制限を使用してピクセル キャッシュをディスクに強制的に保存することもできますが、ディスク アクセスはメモリ アクセスよりも 1000 倍以上遅くなる可能性があります。ピクセル キャッシュへの高速で効率的なアクセスのために、ピクセル キャッシュをヒープ メモリ内に保持するようにしてください。

ImageMagick Q16 バージョンでは、スケーリングなしで 16 ビット イメージの読み取りと書き込みが可能ですが、ピクセル キャッシュは Q8 バージョンの 2 倍のリソースを消費します。システムのメモリまたはディスク リソースが制限されている場合は、ImageMagick Q8 バージョンを検討してください。また、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 は Web サイトに最適です。画像の読み取りと書き込みのオーバーヘッドが削減されます。私たちは online image studio でのみこれを使用しています。

画像のプロパティとプロファイル

画像には、プロパティ (幅、高さ、説明など) やプロファイル (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 は、すべてのチャネルが元の画像と同じ寸法とピクセル数を持つ multispectral imagery をサポートします。ただし、すべての画像形式でマルチスペクトル画像がサポートされているわけではありません。PSD、TIFF、MIFF、MPC、および FTXT は、最大 31 バンド (そのうち 21 はメタ チャネル) のマルチスペクトル画像を完全にサポートしています。ImageMagick を configure スクリプトの --enable-64bit-channel-masks オプションでビルドすると、最大 52 のメタ チャネルを持つ 62 バンドのマルチスペクトル画像を処理できることに注意してください。

現在画像形式でサポートされていないユース ケースがある場合は、discussion forum に投稿してください。 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 ビット オペレーティング システムでは最大 3,100 万行/列、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() メソッドでこれを行います。各実行スレッドで、1 つのソース イメージを異なる宛先イメージに合成するとします。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 にも同様のメソッドがあります。例として、MagickWandMagickCore の両方に実装されている同じシグモイド コントラスト アルゴリズムを参照してください。

ほとんどの場合、最適なパフォーマンスを得るために、デフォルトのスレッド数はシステム上のプロセッサ コアの数に設定されています。ただし、システムがハイパースレッド化されている場合、または仮想ホスト上で実行していて、プロセッサのサブセットのみがサーバー インスタンスで使用できる場合は、スレッド policy または 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、MagickkWand、Magick++ など) のいずれかを呼び出すプログラム モジュールから 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 を完全に無効にすることが最適な場合があります。

異種分散処理

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 画像を返します。ライターはその逆を行います。イメージ プロパティを取得し、ピクセル キャッシュをアンロードして、イメージ形式の要件に従って書き込みます。

以下は、サンプル custom coder の一覧です。これは、単に 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() から呼び出されます。

以下はサンプル custom image filter の一覧です。ピクセルの明るさと彩度の平均、標準偏差などの統計をいくつか計算します。

#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 
コメント一覧
読み込み中..