Color Modes

架構

像素快取影像屬性和配置文件多光譜影像大型影像支援像素串流執行緒異構分散式處理自訂影像編解碼器自訂影像濾鏡

奧茲國的居民對他們的恩人,全能的巫師感到非常滿意。他們接受了他的智慧和仁慈,卻從未質疑他的力量從何而來、為何而來以及在哪裡。就像奧茲國的居民一樣,如果您覺得 ImageMagick 可以在您不知道幕後發生什麼事的情況下幫助您轉換、編輯或合成影像,請隨時跳過本節。但是,如果您想進一步瞭解 ImageMagick 背後的軟體和演算法,請继续阅读。要充分理解本討論,您應該熟悉影像術語並熟悉電腦程式設計。

架構概述

影像通常由一個矩形像素區域和中繼資料組成。為了有效地轉換、編輯或合成影像,我們需要方便地存取區域內(有時是區域外)任何位置的任何像素。如果是影像序列,我們需要存取序列中任何影像的任何區域的任何像素。然而,有數百種影像格式,例如 JPEG、TIFF、PNG、GIF 等,這使得按需存取像素變得困難。在這些格式中,我們發現以下方面的差異:

  • 色彩空間(例如 sRGB、線性 RGB、線性灰階、CMYK、YUV、Lab 等)
  • 位元深度(例如 1、4、8、12、16 等)
  • 儲存格式(例如 無符號、有符號、浮點數、雙精度浮點數等)
  • 壓縮(例如 未壓縮、RLE、Zip、BZip 等)
  • 方向(即 由上而下、由右至左等)
  • 佈局(例如 原始資料、穿插操作碼等)

此外,某些影像像素可能需要衰減,某些格式允許多個影格,而某些格式包含必須先進行光柵化(從向量轉換為像素)的向量圖形。

有效實作影像處理演算法可能需要我們取得或設定

  • 一次一個像素(例如 位置 10,3 的像素)
  • 單一掃描線(例如 第 4 列的所有像素)
  • 一次多個掃描線(例如 像素列 4-7)
  • 單一行或多行像素(例如 第 11 行的所有像素)
  • 影像中任意區域的像素(例如 定義在 10,7 到 10,19 的像素)
  • 隨機順序的像素(例如 14,15 和 640,480 的像素)
  • 來自兩個不同影像的像素(例如 影像 1 中 5,1 的像素和影像 2 中 5,1 的像素)
  • 影像邊界外的像素(例如 -1,-3 的像素)
  • 無符號 (65311) 或浮點數表示法(例如 0.17836)的像素分量
  • 可以包含負值(例如 -0.0072973525628)以及超過量子深度的值(例如 65931)的高動態範圍像素
  • 在不同執行緒中同時處理一個或多個像素
  • 記憶體中的所有像素,以利用在由 CPU、GPU 和其他處理器組成的異構平台上協同執行所提供的加速
  • 與每個通道相關聯的特性,以指定像素通道是複製、更新還是混合
  • 定義哪些像素可以更新的遮罩
  • 對使用者有利但 ImageMagick 影像處理演算法不會觸及的額外通道

鑒於圖像格式和圖像處理需求的多樣性,我們實作了 ImageMagick 像素快取,以便在圖像區域內(即 真實像素)和序列中的任何圖像中,根據需求提供對任何像素的便捷順序或並行存取。此外,像素快取允許存取圖像定義邊界之外的像素(即 虛擬像素)。

除了像素之外,圖像還有大量的 圖像屬性和配置文件。屬性包括眾所周知的屬性,如寬度、高度、深度和色彩空間。圖像可能具有可選屬性,例如圖像作者、註釋、創建日期等。某些圖像還包含用於色彩管理的配置文件,或 EXIF、IPTC、8BIM 或 XMP 信息配置文件。ImageMagick 提供了命令行選項和編程方法來獲取、設置或查看圖像屬性或配置文件,或應用配置文件。

ImageMagick 由近五十萬行 C 代碼組成,並且可選地依賴於依賴庫(例如 JPEG、PNG、TIFF 庫)中的數百萬行代碼。考慮到這一點,您可能會期望有一個龐大的架構文檔。但是,絕大多數圖像處理只是存取像素及其元數據,而我們簡單、優雅且高效的實作使 ImageMagick 開發人員可以輕鬆完成此操作。我們將在接下來的幾節中討論像素快取的實作以及圖像屬性和配置文件的獲取和設置。接下來,我們將討論在執行 線程 中使用 ImageMagick。在最後幾節中,我們將討論用於讀取或寫入特定圖像格式的 圖像編解碼器,然後討論一些關於創建 濾鏡 以根據您的自定義需求存取或更新像素的內容。

像素快取

ImageMagick 像素快取是圖像像素的儲存庫,最多具有 64 個通道。通道以構建 ImageMagick 時指定的深度連續存儲。對於 Q8 版本的 ImageMagick,通道深度為每個像素組件 8 位,對於 Q16 版本,通道深度為每個像素組件 16 位,對於 Q32 版本,通道深度為每個像素組件 32 位。默認情況下,像素組件是 32 位浮點 高動態範圍 量。這些通道可以保存任何值,但通常包含紅色、綠色、藍色和 alpha 強度,或青色、洋紅色、黃色、黑色和 alpha 強度。通道可能包含彩色映射圖像的顏色映射索引或 CMYK 圖像的黑色通道。像素快取存儲可以是堆內存、磁盤支持的內存映射或磁盤上的內存。像素快取是引用計數的。克隆快取時,只會複製快取屬性。只有當您表示要更新任何像素時,才會複製快取像素。

創建像素快取

像素快取在創建時與圖像相關聯,並在您嘗試獲取或放置像素時初始化。以下有三種將像素快取與圖像關聯的常用方法

創建一個初始化為背景色的圖像畫布

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 的其他程序接口也是相同的。

初始化像素快取時,像素會從其來源的任何位深度縮放到像素快取所需的位深度。例如,如果您使用的是 Q8 版本的 ImageMagick,則 1 通道 1 位單色 PBM 圖像將縮放到 8 位灰度圖像,而對於 Q16 版本,則縮放到 16 位 RGBA 圖像。您可以使用 -version 選項確定您擁有的版本


    $
    identify -version
    Version: ImageMagick 7.1.1-38 2024-05-05 Q16 https://imagemagick.dev.org.tw

如您所見,像素緩存的便利性有時需要在存儲(例如,將 1 位單色圖像存儲為 16 位很浪費)和速度(即將整個圖像存儲在內存中通常比一次訪問一行像素慢)方面做出權衡。在大多數情況下,像素緩存的好處通常大於任何缺點。

訪問像素緩存

將像素緩存與圖像關聯後,您通常希望獲取、更新或將像素放入其中。我們將圖像區域內的像素稱為真實像素,將區域外的像素稱為虛擬像素。使用以下方法訪問緩存中的像素

以下是用於操作像素緩存中像素的典型 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() 來確保將此緩衝區_推_送到像素緩存,以確保緩存中相應的像素得到更新。因此,我們建議您一次只讀取或更新一行或幾行像素。但是,您可以獲得所需的任何矩形像素區域。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 的圖像,並且我們使用的是非 HDRI Q16 版本的 ImageMagick,則像素快取會消耗圖像 寬度 * 高度 * 位深度 / 8 * 通道數 位元組,大約是 2.3 兆位元組(即 640 * 480 * 2 * 4)。還不錯,但是如果您的圖像是 25000 x 25000 像素呢?像素快取需要大約 4.7 吉位元組的儲存空間。哇。ImageMagick 通過將大型圖像快取到磁碟而不是記憶體中來解決可能出現的巨大儲存需求。通常,像素快取使用堆記憶體儲存在記憶體中。如果堆記憶體耗盡,我們會在磁碟上建立像素快取,並嘗試將其記憶體映射。如果記憶體映射記憶體耗盡,我們就簡單地使用標準磁碟 I/O。磁碟儲存空間充足且便宜,但速度也非常慢——比訪問記憶體中的像素慢 1000 倍以上。如果我們對基於磁碟的快取進行記憶體映射,我們可以獲得一些速度提升,最多可達 5 倍。有關儲存的這些決策是由像素快取管理器與作業系統協商後 自動 做出的。但是,您可以使用 快取資源限制 來影響像素快取管理器分配像素快取的方式。這些限制包括:

width(寬度) 圖像的最大寬度。超過此限制,將拋出異常並停止操作。
height(高度) 圖像的最大高度。超過此限制,將拋出異常並停止操作。
area(面積) 像素快取記憶體中可以容納的任何單個圖像的最大面積(以位元組為單位)。如果超過此限制,圖像將自動快取到磁碟並選擇性地進行記憶體映射。
memory(記憶體) 從堆中分配給像素快取的最大記憶體量(以位元組為單位)。
map(映射) 分配給像素快取的最大記憶體映射量(以位元組為單位)。
disk(磁碟) 允許像素快取使用的最大磁碟空間量(以位元組為單位)。如果超過此限制,將拋出致命異常,並且所有處理都將停止。
files(檔案) 打開的像素快取檔案的最大數量。當超過此限制時,任何後續快取到磁碟的像素都將關閉並按需重新打開。這種行為允許通過減少像素快取打開/關閉系統呼叫的次數,在磁碟上同時訪問大量圖像而不會降低速度。
thread(執行緒) 允許並行執行的最大執行緒數。您的系統可能會選擇一個小於此值的執行緒數。ImageMagick 預設會選擇最佳執行緒數,通常是主機上的核心數。將此值設定為 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

您可以將這些限制設定為安全性策略(請參閱policy.xml)、使用環境變數、使用-limit命令列選項,或使用SetMagickResourceLimit() MagickCore API 方法。例如,我們 ImageMagick 的線上網路介面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.dev.org.tw/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>

由於我們會同時處理多個工作階段,因此我們不希望任何一個工作階段耗盡所有可用記憶體。根據此策略,大型影像會快取到磁碟。如果影像太大且超過像素快取磁碟限制,則程式會退出。此外,我們還會設定時間限制,以防止任何失控的處理任務。如果任何影像的寬度或高度超過 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。

分散式像素快取

分散式像素快取是單一主機上可用傳統像素快取的擴展。分散式像素快取可以跨越多台伺服器,以便其大小和交易能力可以增長以支援非常大的影像。在一台或多台機器上啟動像素快取伺服器。當您讀取或操作影像且本機像素快取資源耗盡時,ImageMagick 會聯繫一台或多台這些遠端像素伺服器來儲存或擷取像素。分散式像素快取依賴網路頻寬將像素傳送到遠端伺服器並從中傳回。因此,它可能會比使用本機儲存體(例如記憶體、磁碟等)的像素快取慢得多。

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() 一次只能處理影像中的一個像素快取區域。如果您想同時存取同一個影像的第一行和最後一行掃描線怎麼辦?解決方案是使用快取檢視。快取檢視允許您根據需要同時存取像素快取中的多個區域。快取檢視的方法類似於之前的方法,只是您必須先開啟一個檢視,並在完成後關閉它。以下是一段 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 會寫入兩個檔案。一個檔案的副檔名為 .mpc,用於保留與影像或影像序列相關聯的所有屬性(例如寬度、高度、色彩空間等);另一個檔案的副檔名為 .cache,是用於儲存原生原始格式的像素快取。當讀取 MPC 影像檔案時,ImageMagick 會讀取影像屬性,並將磁碟上的像素快取映射到記憶體,從而无需解碼影像像素。缺點是需要佔用磁碟空間。MPC 的檔案大小通常大於大多數其他影像格式。

MPC 影像檔案最有效的用途是一次寫入、多次讀取的模式。例如,您的工作流程需要從來源影像中提取隨機的像素塊。我們可以使用 MPC 並將影像直接映射到記憶體,而不是每次都重新讀取甚至解壓縮來源影像。

像素快取的建議做法

儘管您可以使用 GetVirtualPixels()、GetAuthenticPixels()、QueueAuthenticPixels、GetCacheViewVirtualPixels()、GetCacheViewAuthenticPixels() 和 QueueCacheViewAuthenticPixels() 方法請求像素快取中的任何像素、任何像素塊、任何掃描線、多個掃描線、任何行或多行,但 ImageMagick 已針對一次返回幾個像素或幾行像素進行了優化。如果您一次請求單個掃描線或幾個掃描線,則還有其他優化。這些方法也允許隨機存取像素快取,但是,ImageMagick 已針對順序存取進行了優化。儘管您可以從影像的最後一行到第一行依次存取像素的掃描線,但如果您從影像的第一行到最後一行依次存取掃描線,則可能會提高效能。

您可以按行或列的順序取得、修改或設定像素。但是,按行存取像素比按列存取像素更有效率。

如果您更新從 GetAuthenticPixels() 或 GetCacheViewAuthenticPixels() 返回的像素,請不要忘記分別呼叫 SyncAuthenticPixels() 或 SyncCacheViewAuthenticPixels(),以確保您的更改與像素快取同步。

如果您要設定初始像素值,請使用 QueueAuthenticPixels() 或 QueueCacheViewAuthenticPixels() 方法。GetAuthenticPixels() 或 GetCacheViewAuthenticPixels() 方法會從快取中讀取像素,如果您要設定初始像素值,則不需要進行此讀取操作。請不要忘記分別呼叫 SyncAuthenticPixels() 或 SyncCacheViewAuthenticPixels(),以將任何像素更改推送到像素快取。

GetVirtualPixels()、GetAuthenticPixels()、QueueAuthenticPixels() 和 SyncAuthenticPixels() 比它們的快取檢視對應方法略微有效率。但是,如果您需要同時存取影像的多個區域,或者如果多個執行緒正在存取影像,則需要使用快取檢視。

您可以使用 GetVirtualPixels() 或 GetCacheViewVirtualPixels() 請求圖像邊界外的像素,但是,請求圖像區域內的像素效率更高。

雖然您可以使用適當的資源限制將像素緩存強制寫入磁盤,但磁盤訪問速度比內存訪問速度慢 1000 倍以上。為了快速、高效地訪問像素緩存,請嘗試將像素緩存保留在堆內存中。

ImageMagick 的 Q16 版本允許您讀取和寫入 16 位圖像而無需縮放,但像素緩存消耗的資源是 Q8 版本的兩倍。如果您的系統的內存或磁盤資源有限,請考慮使用 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 是網站的理想選擇。它減少了讀取和寫入圖像的開銷。我們在 線上圖像工作室 中專門使用它。

圖像屬性和配置文件

圖像具有與之關聯的元數據,這些元數據採用屬性(例如寬度、高度、描述等)和配置文件(例如 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() 方法處理 artifacts。Artifacts 是隱藏屬性,不會導出到圖像格式(例如 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,可以將影像的一個或多個像素分量或影像的一部分串流到您選擇的儲存格式。 它在從輸入影像逐行讀取像素分量時將其寫入,這使得在處理大型影像或需要原始像素分量時,stream 非常有用。 大多數影像格式都從左到右、從上到下串流像素(紅色、綠色和藍色)。 但是,少數格式不支援這種常見的排序(例如 PSD 格式)。

大型影像支援

ImageMagick 具有處理從百萬像素到兆像素級別的影像大小的能力,包括讀取、處理和寫入操作。 理論上,在 32 位元作業系統上,影像尺寸可以擴展到 3100 萬行/列,在 64 位元作業系統上,影像尺寸可以擴展到驚人的 31 兆。 但是,實際可實現的尺寸要小得多,具體取決於主機電腦上的可用資源。 請務必注意,某些影像格式對影像大小有限制。 例如,Photoshop 影像的寬度或高度限制為最多 300,000 像素。 在這裡,我們將影像調整為 250 萬像素的正方形

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

對於真正的大型影像,或者如果主機上的資源有限,您可以在一台或多台遠端主機上使用分散式像素快取

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 的許多內部演算法都採用多執行緒,以利用多核心處理器晶片的加速優勢。 然而,歡迎您在自己的執行緒中使用 ImageMagick 演算法,但 MagickCore 的 GetVirtualPixels()、GetAuthenticPixels()、QueueAuthenticPixels() 或 SyncAuthenticPixels() 像素快取方法除外。 這些方法僅供單一執行緒使用,OpenMP 並行區段除外。 若要使用多個執行緒存取像素快取,請使用快取檢視。 例如,我們在 CompositeImage() 方法中就是這樣做的。 假設我們想要在每個執行緒中將單一來源影像合成到不同的目標影像上。 如果我們使用 GetVirtualPixels(),結果將無法預測,因為多個執行緒可能會同時請求像素快取的不同區域。 我們改用 GetCacheViewVirtualPixels(),它會為每個執行緒建立唯一的檢視,以確保我們的程式在任何執行緒數量下都能正常運作。 其他程式介面,例如 MagickWand API,是完全執行緒安全的,因此不需要對執行緒採取任何特殊預防措施。

以下是一個 MagickCore 程式碼片段,它利用 OpenMP 程式設計範例的執行緒優勢

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 支援魔杖檢視。 檢視會以並行方式迭代整個影像或部分影像,並針對每一列像素呼叫您提供的回呼方法。 這會將您大部分的並行程式設計活動限制在該模組中。 MagickCore 中也有類似的方法。 例如,請參閱在 MagickWandMagickCore 中實現的相同 sigmoid 對比度演算法。

在大多數情況下,預設執行緒數量會設定為系統上的處理器核心數量,以獲得最佳效能。 但是,如果您的系統是超執行緒的,或者如果您在虛擬主機上執行,並且伺服器執行個體只能使用部分處理器,則可以透過設定執行緒 策略MAGICK_THREAD_LIMIT 環境變數來提高效能。 例如,您的虛擬主機有 8 個處理器,但只有 2 個分配給您的伺服器執行個體。 預設的 8 個執行緒可能會導致嚴重的效能問題。 一種解決方案是在您的 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 執行緒似乎可以互通運作,而不會發生衝突。 如果您想從呼叫其中一個 ImageMagick 應用程式程式設計介面(例如 MagickCore、MagickWand、Magick++ 等)的程式模組中使用 Posix 執行緒,您可能需要在 ImageMagick 中停用 OpenMP 支援。 在 configure 指令碼命令列中新增 --disable-openmp 選項,然後重建並重新安裝 ImageMagick。

您可以透過使用 tcmalloc 記憶體配置程式庫減少鎖定爭用,進一步提高效能。 若要啟用,請在建置 ImageMagick 時,在 configure 命令列中新增 --with-tcmalloc

執行緒效能

在平行環境中預測行為可能很困難。效能可能取決於許多因素,包括編譯器、OpenMP 庫的版本、處理器類型、核心數、記憶體量、是否啟用超執行緒、與 ImageMagick 同時執行的應用程式組合,或您使用的特定影像處理演算法。確定最佳執行緒數的唯一方法是進行基準測試。ImageMagick 在對命令進行基準測試時包含漸進式執行緒,並傳回一個或多個執行緒的耗用時間和效率。這可以幫助您識別在您的環境中多少個執行緒最有效率。對於此基準測試,我們使用 1 到 12 個執行緒將 1920x1080 的模型影像銳化 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 快一個數量級。

首先確認您的 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 登錄機碼。

自訂影像編解碼器

影像編解碼器(即編碼器/解碼器)負責註冊、選擇性地分類、選擇性地讀取、選擇性地寫入和取消註冊一種影像格式(例如 PNG、GIF、JPEG 等)。註冊影像編解碼器會提醒 ImageMagick 特定格式可供讀取或寫入。而取消註冊則告訴 ImageMagick 該格式不再可用。分類方法會查看影像的前幾個位元組,並確定影像是否採用預期的格式。讀取器會設定影像大小、色彩空間和其他屬性,並使用像素載入像素快取。讀取器會傳回單個影像或影像序列(如果格式支援每個檔案多個影像),或者如果發生錯誤,則傳回異常和空影像。寫入器則相反。它會取得影像屬性並卸載像素快取,並根據影像格式的要求寫入它們。

以下列出了一個範例 自訂編碼器。 它以 MGK 影像格式讀取和寫入影像,該格式僅僅是一個 ID,後跟影像寬度和高度,再後跟 RGB 像素值。

#include <MagickCore/studio.h>
#include <MagickCore/blob.h>
#include <MagickCore/cache.h>
#include <MagickCore/colorspace.h>
#include <MagickCore/exception.h>
#include <MagickCore/image.h>
#include <MagickCore/list.h>
#include <MagickCore/magick.h>
#include <MagickCore/memory_.h>
#include <MagickCore/monitor.h>
#include <MagickCore/pixel-accessor.h>
#include <MagickCore/string_.h>
#include <MagickCore/module.h>
#include "filter/blob-private.h"
#include "filter/exception-private.h"
#include "filter/image-private.h"
#include "filter/monitor-private.h"
#include "filter/quantum-private.h"

/*
  Forward declarations.
*/
static MagickBooleanType
  WriteMGKImage(const ImageInfo *,Image *,ExceptionInfo *);

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   I s M G K                                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  IsMGK() returns MagickTrue if the image format type, identified by the
%  magick string, is MGK.
%
%  The format of the IsMGK method is:
%
%      MagickBooleanType IsMGK(const unsigned char *magick,const size_t length)
%
%  A description of each parameter follows:
%
%    o magick: This string is generally the first few bytes of an image file
%      or blob.
%
%    o length: Specifies the length of the magick string.
%
*/
static MagickBooleanType IsMGK(const unsigned char *magick,const size_t length)
{
  if (length < 7)
    return(MagickFalse);
  if (LocaleNCompare((char *) magick,"id=mgk",7) == 0)
    return(MagickTrue);
  return(MagickFalse);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d M G K I m a g e                                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ReadMGKImage() reads a MGK image file and returns it.  It allocates the
%  memory necessary for the new Image structure and returns a pointer to the
%  new image.
%
%  The format of the ReadMGKImage method is:
%
%      Image *ReadMGKImage(const ImageInfo *image_info,
%        ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image_info: the image info.
%
%    o exception: return any errors or warnings in this structure.
%
*/
static Image *ReadMGKImage(const ImageInfo *image_info,ExceptionInfo *exception)
{
  char
    buffer[MaxTextExtent];

  Image
    *image;

  long
    y;

  MagickBooleanType
    status;

  register long
    x;

  register Quantum
    *q;

  register unsigned char
    *p;

  ssize_t
    count;

  unsigned char
    *pixels;

  unsigned long
    columns,
    rows;

  /*
    Open image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  if (image_info->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
      image_info->filename);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  image=AcquireImage(image_info,exception);
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == MagickFalse)
    {
      image=DestroyImageList(image);
      return((Image *) NULL);
    }
  /*
    Read MGK image.
  */
  (void) ReadBlobString(image,buffer);  /* read magic number */
  if (IsMGK(buffer,7) == MagickFalse)
    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
  (void) ReadBlobString(image,buffer);
  count=(ssize_t) sscanf(buffer,"%lu %lu\n",&columns,&rows);
  if (count <= 0)
    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
  do
  {
    /*
      Initialize image structure.
    */
    image->columns=columns;
    image->rows=rows;
    image->depth=8;
    if ((image_info->ping != MagickFalse) && (image_info->number_scenes != 0))
      if (image->scene >= (image_info->scene+image_info->number_scenes-1))
        break;
    /*
      Convert MGK raster image to pixel packets.
    */
    if (SetImageExtent(image,image->columns,image->rows,exception) == MagickFalse)
      return(DestroyImageList(image));
    pixels=(unsigned char *) AcquireQuantumMemory((size_t) image->columns,
      3UL*sizeof(*pixels));
    if (pixels == (unsigned char *) NULL)
      ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
    for (y=0; y < (long) image->rows; y++)
    {
      count=(ssize_t) ReadBlob(image,(size_t) (3*image->columns),pixels);
      if (count != (ssize_t) (3*image->columns))
        ThrowReaderException(CorruptImageError,"UnableToReadImageData");
      p=pixels;
      q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
      if (q == (Quantum *) NULL)
        break;
      for (x=0; x < (long) image->columns; x++)
      {
        SetPixelRed(image,ScaleCharToQuantum(*p++),q);
        SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
        SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
        q+=GetPixelChannels(image);
      }
      if (SyncAuthenticPixels(image,exception) == MagickFalse)
        break;
      if (image->previous == (Image *) NULL)
        if ((image->progress_monitor != (MagickProgressMonitor) NULL) &&
            (QuantumTick(y,image->rows) != MagickFalse))
          {
            status=image->progress_monitor(LoadImageTag,y,image->rows,
              image->client_data);
            if (status == MagickFalse)
              break;
          }
    }
    pixels=(unsigned char *) RelinquishMagickMemory(pixels);
    if (EOFBlob(image) != MagickFalse)
      {
        ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
          image->filename);
        break;
      }
    /*
      Proceed to next image.
    */
    if (image_info->number_scenes != 0)
      if (image->scene >= (image_info->scene+image_info->number_scenes-1))
        break;
    *buffer='\0';
    (void) ReadBlobString(image,buffer);
    count=(ssize_t) sscanf(buffer,"%lu %lu\n",&columns,&rows);
    if (count > 0)
      {
        /*
          Allocate next image structure.
        */
        AcquireNextImage(image_info,image,exception);
        if (GetNextImageInList(image) == (Image *) NULL)
          {
            image=DestroyImageList(image);
            return((Image *) NULL);
          }
        image=SyncNextImageInList(image);
        if (image->progress_monitor != (MagickProgressMonitor) NULL)
          {
            status=SetImageProgress(image,LoadImageTag,TellBlob(image),
              GetBlobSize(image));
            if (status == MagickFalse)
              break;
          }
      }
  } while (count > 0);
  (void) CloseBlob(image);
  return(GetFirstImageInList(image));
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r M G K I m a g e                                           %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  RegisterMGKImage() adds attributes for the MGK image format to
%  the list of supported formats.  The attributes include the image format
%  tag, a method to read and/or write the format, whether the format
%  supports the saving of more than one frame to the same file or blob,
%  whether the format supports native in-memory I/O, and a brief
%  description of the format.
%
%  The format of the RegisterMGKImage method is:
%
%      unsigned long RegisterMGKImage(void)
%
*/
ModuleExport unsigned long RegisterMGKImage(void)
{
  MagickInfo
    *entry;

  entry=AcquireMagickInfo("MGK","MGK","MGK image");
  entry->decoder=(DecodeImageHandler *) ReadMGKImage;
  entry->encoder=(EncodeImageHandler *) WriteMGKImage;
  entry->magick=(IsImageFormatHandler *) IsMGK;
  (void) RegisterMagickInfo(entry);
  return(MagickImageCoderSignature);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r M G K I m a g e                                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  UnregisterMGKImage() removes format registrations made by the
%  MGK module from the list of supported formats.
%
%  The format of the UnregisterMGKImage method is:
%
%      UnregisterMGKImage(void)
%
*/
ModuleExport void UnregisterMGKImage(void)
{
  (void) UnregisterMagickInfo("MGK");
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e M G K I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  WriteMGKImage() writes an image to a file in red, green, and blue MGK
%  rasterfile format.
%
%  The format of the WriteMGKImage method is:
%
%      MagickBooleanType WriteMGKImage(const ImageInfo *image_info,
%        Image *image)
%
%  A description of each parameter follows.
%
%    o image_info: the image info.
%
%    o image:  The image.
%
%    o exception:  return any errors or warnings in this structure.
%
*/
static MagickBooleanType WriteMGKImage(const ImageInfo *image_info,Image *image,
  ExceptionInfo *exception)
{
  char
    buffer[MaxTextExtent];

  long
    y;

  MagickBooleanType
    status;

  MagickOffsetType
    scene;

  register const Quantum
    *p;

  register long
    x;

  register unsigned char
    *q;

  unsigned char
    *pixels;

  /*
    Open output image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
  if (status == MagickFalse)
    return(status);
  scene=0;
  do
  {
    /*
      Allocate memory for pixels.
    */
    if (image->colorspace != RGBColorspace)
      (void) SetImageColorspace(image,RGBColorspace,exception);
    pixels=(unsigned char *) AcquireQuantumMemory((size_t) image->columns,
      3UL*sizeof(*pixels));
    if (pixels == (unsigned char *) NULL)
      ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
    /*
      Initialize raster file header.
    */
    (void) WriteBlobString(image,"id=mgk\n");
    (void) FormatLocaleString(buffer,MaxTextExtent,"%lu %lu\n",image->columns,
       image->rows);
    (void) WriteBlobString(image,buffer);
    for (y=0; y < (long) image->rows; y++)
    {
      p=GetVirtualPixels(image,0,y,image->columns,1,exception);
      if (p == (const Quantum *) NULL)
        break;
      q=pixels;
      for (x=0; x < (long) image->columns; x++)
      {
        *q++=ScaleQuantumToChar(GetPixelRed(image,p));
        *q++=ScaleQuantumToChar(GetPixelGreen(image,p));
        *q++=ScaleQuantumToChar(GetPixelBlue(image,p));
        p+=GetPixelChannels(image);
      }
      (void) WriteBlob(image,(size_t) (q-pixels),pixels);
      if (image->previous == (Image *) NULL)
        if ((image->progress_monitor != (MagickProgressMonitor) NULL) &&
            (QuantumTick(y,image->rows) != MagickFalse))
          {
            status=image->progress_monitor(SaveImageTag,y,image->rows,
              image->client_data);
            if (status == MagickFalse)
              break;
          }
    }
    pixels=(unsigned char *) RelinquishMagickMemory(pixels);
    if (GetNextImageInList(image) == (Image *) NULL)
      break;
    image=SyncNextImageInList(image);
    status=SetImageProgress(image,SaveImagesTag,scene,
      GetImageListLength(image));
    if (status == MagickFalse)
      break;
    scene++;
  } while (image_info->adjoin != MagickFalse);
  (void) CloseBlob(image);
  return(MagickTrue);
}

要從命令列呼叫自訂編碼器,請使用以下命令

magick logo: logo.mgk
display logo.mgk

我們提供了 Magick 編碼器套件 來幫助您開始編寫自己的自訂編碼器。

在建置之前,如果 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 濾鏡套件 來幫助您開始編寫自己的自訂影像濾鏡。

在建置之前,如果 ImageMagick 不在您的預設系統路徑中,請設定 PKG_CONFIG_PATH 環境變數

export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig