Výukové menuObsah na této stránce

Architektura

Občané země Oz byli docela spokojeni se svým dobrodincem, všemocným čarodějem. Přijali jeho moudrost a shovívavost, aniž by se kdy ptali, kdo, proč a kde je jeho moc. Stejně jako občané země Oz, pokud se cítíte dobře, že vám ImageMagick může pomoci převádět, upravovat nebo skládat vaše obrázky, aniž byste věděli, co se děje za oponou, klidně tuto část přeskočte. Pokud se však chcete dozvědět více o softwaru a algoritmech za ImageMagick, čtěte dále. Chcete-li plně využít této diskuse, měli byste znát názvosloví obrázků a znát počítačové programování.

Přehled architektury

Obrázek se obvykle skládá z obdélníkové oblasti pixelů a metadat. Abychom mohli efektivně převádět, upravovat nebo skládat obrázek, potřebujeme pohodlný přístup k jakémukoli pixelu kdekoli v oblasti (a někdy i mimo oblast). A v případě sekvence obrázků potřebujeme přístup k libovolnému pixelu libovolné oblasti libovolného obrázku v sekvenci. Existují však stovky obrazových formátů jako JPEG, TIFF, PNG, GIF atd., které znesnadňují přístup k pixelům na vyžádání. V rámci těchto formátů najdeme rozdíly v:

  • barevný prostor (např. sRGB, lineární RGB, lineární GREY, CMYK, YUV, Lab atd.)
  • bitová hloubka (např. 1, 4, 8, 12, 16 atd.)
  • formát úložiště (např. nepodepsaný, podepsaný, plovoucí, dvojitý atd.)
  • komprese (např. nekomprimovaná, RLE, Zip, BZip atd.)
  • orientace (tj. shora dolů, zprava doleva atd.),
  • rozvržení (např. nezpracované, proložené operačními kódy atd.)

Kromě toho mohou některé obrazové body vyžadovat zeslabení, některé formáty umožňují více než jeden snímek a některé formáty obsahují vektorovou grafiku, která musí být nejprve rastrována (převedena z vektoru na obrazové body).

Efektivní implementace algoritmu zpracování obrazu může vyžadovat, abychom získali nebo nastavili:

  • jeden pixel za čas (např. pixel na pozici 10,3)
  • jeden skenovací řádek (např. všechny pixely z řádku 4)
  • několik skenovacích řádků najednou (např. pixelové řádky 4–7)
  • jeden sloupec nebo sloupce pixelů (např. všechny pixely ze sloupce 11)
  • libovolná oblast pixelů z obrázku (např. pixely definované jako 10,7 až 10,19)
  • pixel v náhodném pořadí (např. pixel na 14, 15 a 640 480)
  • pixely ze dvou různých obrázků (např. pixel na 5,1 z obrázku 1 a pixel na 5,1 z obrázku 2)
  • pixely mimo hranice obrázku (např. pixel na -1,-3)
  • pixelová komponenta, která je bez znaménka (65311) nebo v plovoucí desetinné čárce (např. 0,17836)
  • pixel s vysokým dynamickým rozsahem, který může obsahovat záporné hodnoty (např. -0,0072973525628) i hodnoty přesahující kvantovou hloubku (např. 65931)
  • jeden nebo více pixelů současně v různých vláknech provádění
  • všechny pixely v paměti, aby bylo možné využít zrychlení nabízené společným spouštěním napříč heterogenními platformami skládajícími se z CPU, GPU a dalších procesorů
  • vlastnosti spojené s každým kanálem, abyste určili, zda se kanál pixelů zkopíruje, aktualizuje nebo prolne
  • masky, které definují, které pixely mohou být aktualizovány
  • další kanály, které jsou pro uživatele přínosné, ale jinak zůstávají nedotčeny algoritmy pro zpracování obrazu ImageMagick

Vzhledem k různým formátům obrázků a požadavkům na zpracování obrázků jsme implementovali ImageMagick pixel cache, abychom poskytli pohodlný sekvenční nebo paralelní přístup k jakémukoli pixelu na vyžádání kdekoli v oblasti obrázku (tj. authentic pixels) a z jakéhokoli obrázku v sekvenci. Navíc pixelová cache umožňuje přístup k pixelům mimo hranice definované obrázkem (tj. virtual pixels).

Kromě pixelů mají obrázky spoustu image properties and profiles. Vlastnosti zahrnují dobře známé atributy, jako je šířka, výška, hloubka a barevný prostor. Obrázek může mít volitelné vlastnosti, které mohou zahrnovat autora obrázku, komentář, datum vytvoření a další. Některé obrázky obsahují také profily pro správu barev nebo informační profily EXIF, IPTC, 8BIM nebo XMP. ImageMagick poskytuje možnosti příkazového řádku a programovací metody pro získání, nastavení nebo zobrazení vlastností nebo profilů obrázku nebo použití profilů.

ImageMagick se skládá z téměř půl milionu řádků kódu C a volitelně závisí na několika milionech řádků kódu v závislých knihovnách (např. JPEG, PNG, TIFF). Vzhledem k tomu by se dalo očekávat obrovský dokument o architektuře. Velká většina zpracování obrazu však spočívá pouze v přístupu k pixelům a jejich metadatům a naše jednoduchá, elegantní a efektivní implementace to vývojářům ImageMagick usnadňuje. Implementaci mezipaměti pixelů a získávání a nastavení vlastností a profilů obrázků probereme v několika následujících částech. Dále diskutujeme o použití ImageMagick v rámci thread provádění. V závěrečných sekcích probíráme image coders pro čtení nebo zápis konkrétního formátu obrázku a poté pár slov o vytvoření filter pro přístup k pixelům nebo jejich aktualizaci na základě vašich vlastních požadavků.

Mezipaměť pixelů

ImageMagick pixel cache je úložiště pro obrazové pixely s až 64 kanály. Kanály jsou uloženy souvisle v hloubce určené při sestavení ImageMagick. Hloubky kanálů jsou 8 bitů na pixel pro verzi Q8 ImageMagick, 16 bitů na pixel pro verzi Q16 a 32 bitů na pixel pro verzi Q32. Ve výchozím nastavení jsou komponenty pixelů 32bitové plovoucí bity high dynamic-range. Kanály mohou obsahovat libovolnou hodnotu, ale obvykle obsahují intenzity červené, zelené, modré a alfa nebo azurové, purpurové, žluté, černé a alfa intenzity. Kanál může obsahovat indexy barevných map pro barevné obrázky nebo černý kanál pro obrázky CMYK. Úložiště mezipaměti pixelů může být haldová paměť, namapovaná paměť zálohovaná na disku nebo na disku. Mezipaměť pixelů se počítá s referencemi. Při klonování mezipaměti se zkopírují pouze vlastnosti mezipaměti. Pixely mezipaměti jsou následně zkopírovány pouze tehdy, když dáte najevo svůj záměr aktualizovat některý z pixelů.

Vytvořte mezipaměť pixelů

Mezipaměť pixelů je spojena s obrázkem při jeho vytvoření a je inicializována, když se pokusíte získat nebo vložit pixely. Zde jsou tři běžné způsoby, jak přidružit mezipaměť pixelů k obrázku:

Vytvořte obrazové plátno inicializované na barvu pozadí:
image=AllocateImage(image_info);
if (SetImageExtent(image,640,480) == MagickFalse)
  { /* an exception was thrown */ }
(void) QueryMagickColor("red",&image->background_color,&image->exception);
SetImageBackgroundColor(image);
Vytvořte obrázek z obrázku JPEG na disku:
(void) strcpy(image_info->filename,"image.jpg"):
image=ReadImage(image_info,exception);
if (image == (Image *) NULL)
  { /* an exception was thrown */ }
Vytvořte obrázek z obrázku založeného na paměti:
image=BlobToImage(blob_info,blob,extent,exception);
if (image == (Image *) NULL)
  { /* an exception was thrown */ }

V naší diskusi o mezipaměti pixelů používáme k ilustraci našich bodů MagickCore API, nicméně principy jsou stejné pro ostatní programová rozhraní k ImageMagick.

Když je inicializována mezipaměť pixelů, pixely jsou škálovány z jakékoli bitové hloubky, ze které vznikly, do požadované mezipaměti pixelů. Například 1kanálový 1bitový monochromatický obraz PBM je zmenšen na 8bitový šedý obraz, pokud používáte verzi ImageMagick Q8, a 16bitové RGBA pro verzi Q16. Jakou verzi máte, můžete určit pomocí volby -version:

$ identify -versionVersion: ImageMagick 7.1.1-38 2024-05-05 Q16 https://imagemagick.org

Jak vidíte, pohodlí pixelové mezipaměti někdy přichází s kompromisem v úložišti (např. ukládání 1bitového monochromatického obrázku jako 16bitového je plýtvání) a rychlosti (tj. ukládání celého obrázku do paměti je obecně pomalejší než přístup k jedné skenovací řadě pixelů najednou). Ve většině případů výhody pixelové mezipaměti obvykle převažují nad všemi nevýhodami.

Přístup k vyrovnávací paměti pixelů

Jakmile je pixelová mezipaměť spojena s obrázkem, obvykle do ní chcete získat, aktualizovat nebo vložit pixely. Pixely uvnitř oblasti obrázku označujeme jako authentic pixels a mimo oblast jako virtual pixels. Pro přístup k pixelům v mezipaměti použijte tyto metody:

Zde je typický fragment kódu MagickCore pro manipulaci s pixely v pixelové mezipaměti. V našem příkladu zkopírujeme pixely ze vstupního obrázku do výstupního obrázku a snížíme intenzitu o 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 */ }

Když poprvé vytvoříme cílový obrázek klonováním zdrojového obrázku, pixely mezipaměti pixelů se nezkopírují. Jsou zkopírovány pouze tehdy, když signalizujete svůj záměr upravit nebo nastavit mezipaměť pixelů voláním GetAuthenticPixels() nebo QueueAuthenticPixels(). Použijte QueueAuthenticPixels(), pokud chcete nastavit nové hodnoty pixelů místo aktualizace stávajících. Můžete použít GetAuthenticPixels() k nastavení hodnot pixelů, ale je o něco efektivnější místo toho použít QueueAuthenticPixels(). Nakonec použijte SyncAuthenticPixels(), abyste zajistili, že všechny aktualizované pixely budou přeneseny do mezipaměti pixelů.

Ke každému pixelu můžete přiřadit libovolný obsah, tzvmetaobsah. K získání přístupu k tomuto obsahu použijte GetVirtualMetacontent() (pro čtení obsahu) nebo GetAuthenticMetacontent() (pro aktualizaci obsahu). Chcete-li například vytisknout metaobsah, použijte:

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 */

Správce mezipaměti pixelů rozhodne, zda vám poskytne přímý nebo nepřímý přístup k pixelům obrázku. V některých případech jsou pixely naskládány do mezipaměti – a proto musíte zavolat SyncAuthenticPixels(), abyste zajistili, že tato vyrovnávací paměť bude pushed ven do mezipaměti pixelů, aby bylo zaručeno, že budou aktualizovány odpovídající pixely v mezipaměti. Z tohoto důvodu doporučujeme číst nebo aktualizovat pouze skenovací řádek nebo několik skenovacích řádků pixelů najednou. Můžete však získat jakoukoli obdélníkovou oblast pixelů, kterou chcete. GetAuthenticPixels() vyžaduje, aby oblast, kterou požadujete, byla v rámci oblasti obrázku. Pro obrázek 640 x 480 můžete získat skenovací řádek 640 pixelů na řádku 479, ale pokud požádáte o skenovací řádek na řádku 480, vrátí se výjimka (řádky jsou číslovány od 0). GetVirtualPixels() toto omezení nemá. Například,

p=GetVirtualPixels(source,-3,-3,source->columns+3,6,exception);

vám bez stížností poskytne pixely, o které jste požádali, i když některé nejsou v rámci oblasti obrázku.

Virtuální pixely

Existuje nepřeberné množství algoritmů pro zpracování obrazu, které vyžadují sousedství pixelů kolem pixelu zájmu. Algoritmus obvykle obsahuje upozornění týkající se toho, jak zacházet s pixely kolem hranic obrazu, známé jako okrajové pixely. S virtuálními pixely se nemusíte starat o speciální zpracování hran kromě výběru, která metoda virtuálních pixelů je pro váš algoritmus nejvhodnější.

Přístup k virtuálním pixelům je řízen metodou SetImageVirtualPixelMethod() z MagickCore API nebo volbou -virtual-pixel z příkazového řádku. Mezi metody patří:

pozadíoblast kolem obrázku je barva pozadí
černýoblast kolem obrázku je černá
šachovnicestřídejte čtverce s obrázkem a barvou pozadí
váhatnenáhodný tónovaný vzor 32x32
okrajrozšířit okrajový pixel směrem k nekonečnu (výchozí)
šedáoblast kolem obrázku je šedá
horizontální dlaždicehorizontálně uspořádat obrázek, barva pozadí nahoře/dole
horizontální-dlaždice-hranahorizontálně uspořádat obraz a replikovat pixely bočních okrajů
zrcadlozrcadlové dlaždice obrazu
náhodnývyberte náhodný pixel z obrázku
dlaždicedlaždice obrázku
průhlednýoblast kolem obrazu je průhledná černá
vertikální dlaždicevertikálně uspořádat obrázek, strany mají barvu pozadí
vertikální-dlaždice-hranavertikálně uspořádat obraz a replikovat pixely bočních okrajů
bílýoblast kolem obrázku je bílá

Požadavky na mezipaměť a zdroje

Připomeňme, že tento jednoduchý a elegantní design pixelové mezipaměti ImageMagick je nákladný z hlediska rychlosti ukládání a zpracování. Požadavky na paměť cache pro pixely se mění podle oblasti obrazu a bitové hloubky komponent pixelů. Například, pokud máme obrázek 640 x 480 a používáme verzi ImageMagick bez HDRI Q16, pixelová mezipaměť spotřebuje obrázek width * height * bit-depth / 8 * channels bajtů nebo přibližně 2,3 mebibajtů (tj. 640 * 480 * 2 * 4). Není to špatné, ale co když má váš obrázek 25 000 x 25 000 pixelů? Mezipaměť pixelů vyžaduje přibližně 4,7 gibibajtů úložiště. Au. ImageMagick zohledňuje možné obrovské požadavky na úložiště ukládáním velkých obrázků do mezipaměti na disk, nikoli do paměti. Pixelová cache je obvykle uložena v paměti pomocí haldové paměti. Pokud je paměť haldy vyčerpána, vytvoříme mezipaměť pixelů na disku a pokusíme se ji zmapovat. Pokud je paměť paměťové mapy vyčerpána, jednoduše použijeme standardní diskové I/O. Diskové úložiště je bohaté a levné, ale je také velmi pomalé – až 1000krát pomalejší než přístup k pixelům v paměti. Pokud namapujeme mezipaměť na disku, můžeme dosáhnout určitého zvýšení rychlosti, až 5krát. Tato rozhodnutí o úložišti jsou učiněna automagically správcem mezipaměti pixelů, který vyjednává s operačním systémem. Můžete však ovlivnit, jak správce mezipaměti pixelů přiděluje mezipaměť pixelů pomocí cache resource limits. Mezi limity patří:

šířkamaximální šířka obrázku. Překročíte-li tento limit, vyvolá se výjimka a operace se přeruší.
výškamaximální výška obrázku. Překročíte-li tento limit, vyvolá se výjimka a operace se přeruší.
plochamaximální plocha v bajtech jakéhokoli jednoho obrázku, který může být umístěn v pixelové mezipaměti. Pokud je tento limit překročen, obraz je automaticky uložen do mezipaměti na disk a volitelně mapován do paměti.
paměťmaximální množství paměti v bajtech k přidělení pro mezipaměť pixelů z haldy.
mapamaximální množství mapy paměti v bajtech k přidělení pro mezipaměť pixelů.
diskmaximální množství místa na disku v bajtech povolené pro použití mezipamětí pixelů. Pokud je tento limit překročen, je vyvolána závažná výjimka a veškeré zpracování se zastaví.
souborymaximální počet otevřených souborů mezipaměti pixelů. Když je tento limit překročen, všechny následující pixely uložené v mezipaměti na disku se uzavřou a na požádání znovu otevřou. Toto chování umožňuje současný přístup k velkému počtu obrázků na disku bez omezení rychlosti snížením počtu systémových volání otevření/zavření mezipaměti pixelů.
vláknomaximální počet vláken, která mohou běžet paralelně. Váš systém může zvolit počet vláken, který je menší než tato hodnota. ImageMagick ve výchozím nastavení vybere optimální počet vláken, což je obvykle počet jader na vašem hostiteli. Nastavte tuto hodnotu na 1 a všechny paralelní oblasti budou provedeny jedním vláknem.
časmaximální počet sekund, po které má proces povoleno provést. Překročíte-li tento limit, bude vyvolána výjimka a zpracování se zastaví.

Všimněte si, že tato omezení se týkají mezipaměti pixelů ImageMagick. Některé algoritmy v rámci ImageMagick nerespektují tato omezení ani žádná z externích delegovaných knihoven (např. JPEG, TIFF atd.).

Chcete-li zjistit aktuální nastavení těchto limitů, použijte tento příkaz:

-> 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

Tyto limity můžete nastavit buď jako security policy (viz policy.xml), s environment variable, s volbou -limit příkazového řádku, nebo s metodou SetMagickResourceLimit() MagickCore API. Například naše online webové rozhraní pro ImageMagick, MagickStudio, obsahuje tato omezení zásad, která pomáhají zabránit odmítnutí služby:

<?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>

Protože zpracováváme několik simultánních relací, nechceme, aby jedna relace spotřebovala veškerou dostupnou paměť. Díky této zásadě se velké obrázky ukládají do mezipaměti na disk. Pokud je obrázek příliš velký a překračuje limit disku mezipaměti pixelů, program se ukončí. Kromě toho zavádíme časový limit, abychom zabránili případným úlohám zpracování. Pokud má některý obrázek šířku nebo výšku přesahující 8192 pixelů, vyvolá se výjimka a zpracování se zastaví. Od verze ImageMagick 7.0.1-8 můžete zabránit použití kteréhokoli delegáta nebo všech delegátů (vzor nastavte na "*"). Všimněte si, že před tímto vydáním používejte doménu "coder", abyste zabránili použití delegátů (např. domain="coder" rights="none" pattern="HTTPS"). Tato zásada také zabraňuje nepřímému čtení. Pokud chcete například číst text ze souboru (např. caption:@myCaption.txt), budete muset tuto zásadu odstranit.

Všimněte si, že limity mezipaměti jsou globální pro každé vyvolání ImageMagick, což znamená, že pokud vytvoříte několik obrázků, kombinované požadavky na zdroje jsou porovnány s limitem, aby se určila velikost úložiště pixel cache.

Chcete-li zjistit, jaký typ a kolik zdrojů spotřebovává mezipaměť pixelů, přidejte na příkazový řádek volbu -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]

Tento příkaz využívá mezipaměť pixelů v paměti. Logo spotřebovalo 4,688MiB a po doostření 3,516MiB.

Distribuovaná mezipaměť pixelů

Distribuovaná mezipaměť pixelů je rozšířením tradiční mezipaměti pixelů dostupné na jednom hostiteli. Distribuovaná mezipaměť pixelů může zahrnovat více serverů, takže může růst ve velikosti a transakční kapacitě pro podporu velmi velkých obrázků. Spusťte server mezipaměti pixelů na jednom nebo více počítačích. Když čtete obrázek nebo s ním pracujete a místní zdroje mezipaměti pixelů jsou vyčerpány, ImageMagick kontaktuje jeden nebo více těchto vzdálených serverů pixelů, aby uložil nebo načetl pixely. Distribuovaná mezipaměť pixelů závisí na šířce pásma sítě pro zařazování pixelů do a ze vzdáleného serveru. Jako takový bude pravděpodobně výrazně pomalejší než mezipaměť pixelů využívající místní úložiště (např. paměť, disk atd.).

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

Zobrazení mezipaměti

GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() a SyncAuthenticPixels() z rozhraní MagickCore API umí pracovat pouze s jednou oblastí mezipaměti pixelů na obrázek najednou. Předpokládejme, že chcete mít přístup k prvnímu a poslednímu skenovacímu řádku ze stejného obrázku ve stejnou dobu? Řešením je použít cache view. Zobrazení mezipaměti vám umožňuje přistupovat k tolika oblastem v pixelové mezipaměti současně, kolik potřebujete. Zobrazení mezipaměti methods je analogické s předchozími metodami s tím rozdílem, že musíte nejprve otevřít pohled a zavřít jej, když s ním skončíte. Zde je úryvek kódu MagickCore, který nám umožňuje současný přístup k prvnímu a poslednímu pixelovému řádku obrázku:

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 Pixel Cache Format

Připomeňme, že každý obrazový formát je dekódován programem ImageMagick a pixely jsou uloženy do mezipaměti pixelů. Pokud napíšete obrázek, pixely se načtou z mezipaměti pixelů a zakódují se tak, jak to vyžaduje formát, který zapisujete (např. GIF, PNG atd.). Formát Magick Pixel Cache (MPC) je navržen tak, aby eliminoval režii při dekódování a kódování pixelů do a z obrazového formátu. MPC zapisuje dva soubory. Jedna s příponou .mpc zachovává všechny vlastnosti spojené s obrázkem nebo sekvencí obrázků (např. šířka, výška, barevný prostor atd.) a druhá s příponou .cache je mezipaměť pixelů v nativním formátu raw. Při čtení souboru obrázku MPC ImageMagick čte vlastnosti obrázku a paměť mapuje mezipaměť pixelů na disku, čímž eliminuje potřebu dekódování pixelů obrázku. Kompromisem je místo na disku. MPC má obecně větší velikost souboru než většina jiných formátů obrázků.

Nejúčinnějším využitím obrazových souborů MPC je vzor typu „jednorázový zápis a vícenásobné čtení“. Váš pracovní postup například vyžaduje extrahování náhodných bloků obrazových bodů ze zdrojového obrázku. Spíše než pokaždé znovu číst a případně dekomprimovat zdrojový obrázek, používáme MPC a mapujeme obrázek přímo do paměti.

Doporučené postupy pro Pixel Cache

Ačkoli můžete pomocí funkcí GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels, GetCacheViewVirtualPixels(), GetCacheViewAuthenticPixels() a Queueueue vyžádat libovolný pixel z mezipaměti pixelů, jakýkoli blok pixelů, jakýkoli skenovací řádek, více řádků, libovolný řádek nebo více řádků () je ImageMagick optimalizován tak, aby vracel několik pixelů nebo několik řádků pixelů najednou. Pokud požadujete jeden skenovací řádek nebo několik skenovacích řádků najednou, existují další optimalizace. Tyto metody také umožňují náhodný přístup k mezipaměti pixelů, nicméně ImageMagick je optimalizován pro sekvenční přístup. Ačkoli můžete přistupovat ke skenovacím řádkům obrazových bodů postupně od posledního řádku obrazu k prvnímu, můžete dosáhnout zvýšení výkonu, pokud budete přistupovat ke skenovacím řádkům od prvního řádku obrazu k poslednímu v sekvenčním pořadí.

Pixely můžete získat, upravit nebo nastavit v pořadí řádků nebo sloupců. Je však efektivnější přistupovat k pixelům po řádcích než po sloupcích.

Pokud aktualizujete pixely vrácené z GetAuthenticPixels() nebo GetCacheViewAuthenticPixels(), nezapomeňte zavolat SyncAuthenticPixels() nebo SyncCacheViewAuthenticPixels(), abyste zajistili, že vaše změny budou synchronizovány s mezipamětí pixelů.

Pokud nastavujete počáteční hodnotu pixelu, použijte QueueAuthenticPixels() nebo QueueCacheViewAuthenticPixels(). Metoda GetAuthenticPixels() nebo GetCacheViewAuthenticPixels() čte pixely z mezipaměti a pokud nastavujete počáteční hodnotu pixelu, toto čtení není nutné. Nezapomeňte zavolat SyncAuthenticPixels() nebo SyncCacheViewAuthenticPixels() k přenesení jakýchkoli změn pixelů do mezipaměti pixelů.

GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() a SyncAuthenticPixels() jsou o něco efektivnější než jejich protějšky zobrazení mezipaměti. Zobrazení mezipaměti jsou však vyžadována, pokud potřebujete přístup k více než jedné oblasti obrazu současně nebo pokud k obrazu přistupuje více než jedna thread of execution.

Pomocí GetVirtualPixels() nebo GetCacheViewVirtualPixels() můžete požádat o obrazové body mimo hranice obrazu, avšak efektivnější je požadovat obrazové body v rámci oblasti obrazu.

Ačkoli můžete vynutit mezipaměť pixelů na disk pomocí příslušných limitů prostředků, přístup na disk může být až 1000krát pomalejší než přístup do paměti. Pro rychlý a efektivní přístup k mezipaměti pixelů se snažte ponechat mezipaměť pixelů v paměti haldy.

Verze ImageMagick Q16 ImageMagick vám umožňuje číst a zapisovat 16bitové obrázky bez změny měřítka, ale mezipaměť pixelů spotřebovává dvakrát více zdrojů než verze Q8. Pokud má váš systém omezenou paměť nebo diskové prostředky, zvažte verzi Q8 ImageMagick. Kromě toho se verze Q8 obvykle provádí rychleji než verze Q16.

Velká většina obrazových formátů a algoritmů se omezuje na pevný rozsah hodnot pixelů od 0 do nějaké maximální hodnoty, například verze Q16 ImageMagick povoluje intenzity od 0 do 65535. High dynamic-range imaging (HDRI), nicméně, umožňuje mnohem větší dynamický rozsah expozic (tj. velký rozdíl mezi světlými a tmavými oblastmi) než standardní digitální zobrazovací techniky. HDRI přesně reprezentuje široký rozsah úrovní intenzity nalezených ve skutečných scénách, od nejjasnějšího přímého slunečního světla až po nejhlubší temné stíny. Povolte HDRI v době sestavení ImageMagick, aby se vypořádalo s obrázky s vysokým dynamickým rozsahem, ale mějte na paměti, že každý pixel je 32bitová hodnota s pohyblivou řádovou čárkou. Kromě toho hodnoty pixelů nejsou ve výchozím nastavení uchyceny, takže některé algoritmy mohou mít neočekávané výsledky kvůli hodnotám pixelů mimo pásmo než verze bez HDRI.

Pokud máte co do činění s velkými obrázky, ujistěte se, že je mezipaměť pixelů zapsána na diskovou oblast s dostatkem volného místa. V Linuxu je to typicky /tmp a pro Windows c:/temp. ImageMagick můžete sdělit, aby zapisoval mezipaměť pixelů do alternativního umístění a šetřil paměť pomocí těchto možností:

magick -limit memory 2GB -limit map 4GB -define registry:temporary-path=/data/tmp ...

Nastavte globální limity prostředků pro vaše prostředí v konfiguračním souboru policy.xml.

Pokud plánujete zpracovat stejný obrázek mnohokrát, zvažte formát MPC. Čtení MPC obrazu má téměř nulovou režii, protože je v nativním formátu pixel cache, což eliminuje potřebu dekódování obrazových pixelů. Zde je příklad:

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 je ideální pro webové stránky. Snižuje režii čtení a zápisu obrázku. Používáme jej výhradně na našem online image studio.

Vlastnosti obrazu a profily

S obrázky jsou spojena metadata ve formě vlastností (např. šířka, výška, popis atd.) a profilů (např. EXIF, IPTC, správa barev). ImageMagick poskytuje pohodlné metody pro získání, nastavení nebo aktualizaci vlastností obrázku a získání, nastavení, aktualizaci nebo použití profilů. Některé z populárnějších vlastností obrazu jsou spojeny se strukturou obrazu v MagickCore API. Například:

(void) printf("image width: %lu, height: %lu\n",image->columns,image->rows);

Pro velkou většinu vlastností obrázku, jako je komentář k obrázku nebo popis, používáme metody GetImageProperty() a SetImageProperty(). Zde nastavíme vlastnost a načteme ji hned zpět:

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 podporuje artefakty pomocí metod GetImageArtifact() a SetImageArtifact(). Artefakty jsou stealth vlastnosti, které se neexportují do obrazových formátů (např. PNG).

Obrazové profily jsou zpracovávány metodami GetImageProfile(), SetImageProfile() a ProfileImage(). Zde nastavíme profil a načteme jej zpět:

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);

Multispektrální snímky

ImageMagick podporuje multispectral imagery, kde všechny kanály mají stejné rozměry a počet pixelů jako původní obrázek. Ne všechny formáty obrázků však podporují multispektrální obrázky. PSD, TIFF, MIFF, MPC a FTXT mají plnou podporu pro multispektrální obrazy až do 31 pásem, z toho 21 meta kanálů. Všimněte si, že pokud vytvoříte ImageMagick s volbou configure script --enable-64bit-channel-masks, můžete zpracovat 62 pásmové multispektrální obrazy s až 52 meta kanály.

Pokud máte případ použití, který není aktuálně podporován formátem obrázku, odešlete jej na discussion forum. Je velká šance, že můžeme podpořit váš případ použití v budoucí verzi ImageMagick.

Streamovací pixely

ImageMagick poskytuje streamování pixelů při jejich čtení nebo zápisu do obrázku. To má oproti mezipaměti pixelů několik výhod. Čas a zdroje spotřebované pixelovou mezipamětí se měří podle oblasti obrazu, zatímco zdroje toku pixelů se měří podle šířky obrazu. Nevýhodou je, že pixely musí být spotřebovány při streamování, takže nedochází k přetrvávání.

Použijte ReadStream() nebo WriteStream() s vhodnou metodou zpětného volání ve vašem programu MagickCore ke spotřebování pixelů při jejich streamování. Zde je zkrácený příklad použití 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);

Poskytujeme také odlehčený nástroj stream pro streamování jedné nebo více pixelových komponent obrazu nebo části obrazu do vámi vybraných formátů úložiště. Zapisuje pixelové komponenty tak, jak jsou čteny ze vstupního obrazu po řadě, takže stream je žádoucí při práci s velkými obrázky nebo když požadujete nezpracované pixelové komponenty. Většina obrazových formátů streamuje pixely (červený, zelený a modrý) zleva doprava a shora dolů. Některé formáty však toto běžné řazení nepodporují (např. formát PSD).

Podpora velkých obrázků

ImageMagick má schopnost zvládnout velikosti obrázků od mega- až po tera-pixely, včetně operací čtení, zpracování a zápisu. Teoreticky mohou rozměry obrázku dosáhnout až 31 milionů řádků/sloupců na 32bitovém operačním systému a až neuvěřitelných 31 bilionů na 64bitovém OS. Skutečné dosažitelné rozměry jsou však podstatně menší, závisí na zdrojích dostupných na vašem hostitelském počítači. Je důležité si uvědomit, že určité formáty obrázků ukládají omezení velikosti obrázku. Například obrazy Photoshopu jsou omezeny na šířku nebo výšku maximálně 300 000 pixelů. Zde změníme velikost obrázku na čtvrt milionu pixelů čtverečních:

magick logo: -resize 250000x250000 logo.miff

U velkých obrázků budou zdroje paměti pravděpodobně vyčerpány a ImageMagick místo toho vytvoří mezipaměť pixelů na disku. Ujistěte se, že máte dostatek dočasného místa na disku. Pokud je váš výchozí dočasný diskový oddíl příliš malý, řekněte ImageMagick, aby použil jiný oddíl s dostatkem volného místa. Například:

magick -define registry:temporary-path=/data/tmp logo:  \      -resize 250000x250000 logo.miff

Chcete-li zajistit, aby velké obrazy nespotřebovávaly veškerou paměť ve vašem systému, vynuťte pixely obrazu na paměťově mapovaný disk s limity prostředků:

magick -define registry:temporary-path=/data/tmp -limit memory 16mb \
  logo: -resize 250000x250000 logo.miff

Zde vynutíme všechny obrazové pixely na disk:

magick -define registry:temporary-path=/data/tmp -limit area 0 \
  logo: -resize 250000x250000 logo.miff

Ukládání pixelů na disk je asi 1000krát pomalejší než paměť. Při zpracování velkých obrázků na disku pomocí ImageMagick počítejte s dlouhou dobou běhu. Průběh můžete sledovat pomocí tohoto příkazu:

magick -monitor -limit memory 2GiB -limit map 4GiB -define registry:temporary-path=/data/tmp \
  logo: -resize 250000x250000 logo.miff

Pro opravdu velké obrázky nebo pokud jsou na vašem hostiteli omezené zdroje, můžete použít distribuovanou mezipaměť pixelů na jednom nebo více vzdálených hostitelích:

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

Kvůli latenci sítě počítejte s výrazným zpomalením zpracování vašeho pracovního postupu.

Vlákna provedení

Mnoho interních algoritmů ImageMagick je vláknitých, aby bylo možné využít zrychlení, které nabízejí čipy vícejádrových procesorů. Můžete však používat algoritmy ImageMagick ve vašich vláknech provádění s výjimkou metod mezipaměti pixelů GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() nebo SyncAuthenticPixels() MagickCore. Tyto metody jsou určeny pouze pro provádění jednoho vlákna s výjimkou paralelní sekce OpenMP. Chcete-li přistupovat k mezipaměti pixelů s více než jedním vláknem provádění, použijte zobrazení mezipaměti. Děláme to například pro metodu CompositeImage(). Předpokládejme, že chceme skládat jeden zdrojový obrázek přes jiný cílový obrázek v každém vláknu provádění. Pokud použijeme GetVirtualPixels(), výsledky jsou nepředvídatelné, protože více vláken by pravděpodobně současně požadovalo různé oblasti mezipaměti pixelů. Místo toho používáme GetCacheViewVirtualPixels(), který vytváří jedinečný pohled pro každé vlákno provádění, což zajišťuje, že se náš program chová správně bez ohledu na to, kolik vláken je vyvoláno. Ostatní programová rozhraní, jako je MagickWand API, jsou zcela bezpečná pro vlákna, takže neexistují žádná zvláštní opatření pro spouštění vláken.

Zde je úryvek kódu MagickCore, který využívá spouštění vláken s programovacím paradigmatem 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");

Tento fragment kódu převede nekomprimovanou bitmapu Windows na obrázek 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;
}

Pokud voláte API ImageMagick z vaší aplikace s podporou OpenMP a máte v úmyslu dynamicky zvýšit počet vláken dostupných v následujících paralelních oblastech, nezapomeňte provést zvýšení before, které voláte API, jinak může ImageMagick selhat.

MagickWand podporuje zobrazení hůlky. Zobrazení se iteruje přes celý obraz nebo jeho část paralelně a pro každý řádek pixelů vyvolá vámi poskytnutou metodu zpětného volání. To omezuje většinu vaší aktivity paralelního programování pouze na jeden modul. Podobné metody jsou v MagickCore. Příklad viz stejný algoritmus sigmoidálního kontrastu implementovaný v obou MagickWand a MagickCore.

Ve většině případů je výchozí počet vláken nastaven na počet procesorových jader ve vašem systému, aby byl zajištěn optimální výkon. Pokud je však váš systém hyperthreaded nebo pokud běží na virtuálním hostiteli a pro vaši instanci serveru je dostupná pouze podmnožina procesorů, můžete dosáhnout zvýšení výkonu nastavením proměnné prostředí podproces policy nebo MAGICK_THREAD_LIMIT. Například váš virtuální hostitel má 8 procesorů, ale pouze 2 jsou přiřazeny k vaší instanci serveru. Výchozí hodnota 8 vláken může způsobit vážné problémy s výkonem. Jedním z řešení je omezit počet vláken na dostupné procesory v konfiguračním souboru policy.xml:

<policy domain="resource" name="thread" value="2"/>

Nebo předpokládejme, že váš 12jádrový počítač s hypervláknem má výchozí hodnotu 24 vláken. Nastavte proměnnou prostředí MAGICK_THREAD_LIMIT a pravděpodobně získáte lepší výkon:

export MAGICK_THREAD_LIMIT=12

Výbor OpenMP nedefinoval chování při míchání OpenMP s jinými modely vláken, jako jsou vlákna Posix. Při použití moderních verzí Linuxu se však zdá, že vlákna OpenMP a Posix spolupracují bez stížností. Pokud chcete používat vlákna Posix z programového modulu, který volá jedno z rozhraní pro programování aplikací ImageMagick (např. MagickCore, MagickWand, Magick++ atd.) z Mac OS X nebo starší verze Linuxu, možná budete muset zakázat podporu OpenMP v ImageMagick. . Přidejte volbu --disable-openmp do příkazového řádku konfiguračního skriptu a znovu sestavte a přeinstalujte ImageMagick.

Výkon můžete dále zvýšit omezením sporů o zámek pomocí knihovny alokace paměti tcmalloc. Chcete-li to povolit, přidejte --with-tcmalloc do příkazového řádku configure při sestavování ImageMagick.

Výkon závitování

V paralelním prostředí může být obtížné předvídat chování. Výkon může záviset na řadě faktorů včetně kompilátoru, verze knihovny OpenMP, typu procesoru, počtu jader, množství paměti, zda je povoleno hyperthreading, směsi aplikací, které se spouštějí souběžně s ImageMagick, nebo konkrétní algoritmus zpracování obrazu, který používáte. Jediným způsobem, jak si být jisti optimálním výkonem, pokud jde o počet vláken, je benchmark. ImageMagick zahrnuje progresivní vytváření vláken při testování příkazu a vrací uplynulý čas a efektivitu pro jedno nebo více vláken. To vám může pomoci určit, kolik vláken je ve vašem prostředí nejúčinnější. Pro tento benchmark 10krát zaostříme 1920x1080 obraz modelu s 1 až 12 vlákny:

$ 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

Sladká tečka pro tento příklad je 6 vláken. To dává smysl, protože existuje 6 fyzických jader. Dalších 6 jsou hypervlákna. Zdá se, že ostření neprospívá hyperthreadingu.

V určitých případech může být optimální nastavit počet vláken na 1 nebo úplně zakázat OpenMP pomocí proměnné prostředí MAGICK_THREAD_LIMIT, volby příkazového řádku -limit nebo konfiguračního souboru policy.xml.

Heterogenní distribuované zpracování

ImageMagick zahrnuje podporu pro heterogenní distribuované zpracování s rámcem OpenCL. Jádra OpenCL v ImageMagick umožňují spouštění algoritmů pro zpracování obrazu napříč heterogenními platformami skládajícími se z CPU, GPU a dalších procesorů. V závislosti na vaší platformě může být zrychlení řádově rychlejší než u tradičního jediného CPU.

Nejprve ověřte, že vaše verze ImageMagick obsahuje podporu pro funkci OpenCL:

magick identify -version
Features: DPC Cipher Modules OpenCL OpenMP(4.5)

Pokud ano, spusťte tento příkaz, abyste dosáhli výrazného zrychlení konvoluce obrazu:

magick image.png -convolve '-1, -1, -1, -1, 9, -1, -1, -1, -1' convolve.png

Pokud akcelerátor není dostupný nebo pokud akcelerátor nereaguje, ImageMagick se vrátí k nezrychlenému konvolučnímu algoritmu.

Zde je příklad jádra OpenCL, které sdružuje obrázek:

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);
};

Viz MagickCore/accelerate.c pro kompletní implementaci konvoluce obrazu s jádrem OpenCL.

Všimněte si, že v systému Windows můžete mít problém s TDR (Detekce časového limitu a obnova GPU). Jeho účelem je detekovat nekontrolované úlohy, které visí na GPU, pomocí prahu doby provádění. U některých starších GPU nižší třídy s filtry OpenCL v ImageMagick může delší doba spuštění spustit mechanismus TDR a zabránit filtrování obrázků GPU. Když k tomu dojde, ImageMagick automaticky přejde zpět na cestu kódu CPU a vrátí očekávané výsledky. Chcete-li se vyhnout preempci, zvyšte klíč registru TdrDelay.

Vlastní kodéry obrázků

Kodér obrázků (tj. kodér / dekodér) je zodpovědný za registraci, volitelnou klasifikaci, volitelně čtení, volitelně zápis a zrušení registrace jednoho formátu obrazu (např. PNG, GIF, JPEG atd.). Registrace kodéru obrázků upozorní ImageMagick konkrétní formát pro čtení nebo zápis. Zatímco zrušení registrace informuje ImageMagick, že formát již není dostupný. Metoda klasifikace se podívá na prvních několik bajtů obrázku a určí, zda je obrázek v očekávaném formátu. Čtečka nastaví velikost obrázku, barevný prostor a další vlastnosti a načte pixely do mezipaměti pixelů. Čtečka vrátí jeden obrázek nebo sekvenci obrázků (pokud formát podporuje více obrázků na soubor), nebo pokud dojde k chybě, výjimku a nulový obrázek. Spisovatel to dělá obráceně. Převezme vlastnosti obrázku a uvolní mezipaměť pixelů a zapíše je podle požadavků formátu obrázku.

Zde je výpis vzorku custom coder. Čte a zapisuje obrázky v obrazovém formátu MGK, což je jednoduše ID následované šířkou a výškou obrazu následovanými hodnotami RGB pixelů.

#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);
}

Chcete-li vyvolat vlastní kodér z příkazového řádku, použijte tyto příkazy:

magick logo: logo.mgk
display logo.mgk

Poskytujeme Magick Coder Kit, který vám pomůže začít psát svůj vlastní kodér.

Před sestavením nastavte proměnnou prostředí PKG_CONFIG_PATH, pokud ImageMagick není ve vaší výchozí systémové cestě:

export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig 

Vlastní obrazové filtry

ImageMagick poskytuje pohodlný mechanismus pro přidávání vlastních vlastních algoritmů pro zpracování obrazu. Tyto obrazové filtry nazýváme a jsou vyvolány z příkazového řádku s volbou -process nebo z metody MagickCore API ExecuteModuleProcess().

Zde je výpis vzorku custom image filter. Vypočítává několik statistik, jako je průměr jasu a saturace pixelů a standardní odchylka.

#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);
}

Chcete-li vyvolat vlastní filtr z příkazového řádku, použijte tento příkaz:

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

Poskytujeme Magick Filter Kit, který vám pomůže začít psát vlastní filtr obrázků.

Před sestavením nastavte proměnnou prostředí PKG_CONFIG_PATH, pokud ImageMagick není ve vaší výchozí systémové cestě:

export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig 
Seznam komentářů
Načítání...