Le cache de pixels • Propriétés et profils d'image • Imagerie multispectrale • Prise en charge des grandes images • Streaming de pixels • Threads d'exécution • Activer la limitation dynamique pour des performances optimisées • Traitement distribué hétérogène • Codeurs d'image personnalisés • Filtres d'image personnalisés
Les citoyens d'Oz étaient tout à fait satisfaits de leur bienfaiteur, le tout-puissant Magicien. Ils acceptaient sa sagesse et sa bienveillance sans jamais s'interroger sur le qui, le pourquoi et le où de son pouvoir. Comme les citoyens d'Oz, si vous êtes à l'aise avec l'idée qu'ImageMagick peut vous aider à convertir, éditer ou composer vos images sans savoir ce qui se passe derrière le rideau, n'hésitez pas à passer cette section. Cependant, si vous voulez en savoir plus sur le logiciel et les algorithmes qui se cachent derrière ImageMagick, lisez la suite. Pour tirer pleinement parti de cette discussion, vous devez être à l'aise avec la nomenclature des images et familier avec la programmation informatique.
Vue d'ensemble de l'architecture
Une image se compose généralement d'une région rectangulaire de pixels et de métadonnées. Pour convertir, éditer ou composer une image de manière efficace, nous avons besoin d'un accès pratique à n'importe quel pixel n'importe où dans la région (et parfois en dehors de la région). Et dans le cas d'une séquence d'images, nous avons besoin d'un accès à n'importe quel pixel de n'importe quelle région de n'importe quelle image de la séquence. Cependant, il existe des centaines de formats d'image tels que JPEG, TIFF, PNG, GIF, etc., ce qui rend difficile l'accès aux pixels à la demande. Au sein de ces formats, on trouve des différences en matière de :
- espace colorimétrique (p. ex. sRGB, RGB linéaire, GRAY linéaire, CMYK, YUV, Lab, etc.)
- profondeur de bits (p. ex. 1, 4, 8, 12, 16, etc.)
- format de stockage (p. ex. non signé, signé, float, double, etc.)
- compression (p. ex. non compressé, RLE, Zip, BZip, etc.)
- orientation (c.-à-d. de haut en bas, de droite à gauche, etc.),
- disposition (p. ex. brut, entremêlé d'opcodes, etc.)
De plus, certains pixels d'image peuvent nécessiter une atténuation, certains formats autorisent plus d'une trame, et certains formats contiennent des graphiques vectoriels qui doivent d'abord être rastérisés (convertis du vectoriel en pixels).
Une implémentation efficace d'un algorithme de traitement d'image peut nécessiter d'obtenir ou de définir :
- un pixel à la fois (p. ex. le pixel à la position 10,3)
- une seule ligne de balayage (p. ex. tous les pixels de la rangée 4)
- quelques lignes de balayage à la fois (p. ex. les rangées de pixels 4-7)
- une seule colonne ou plusieurs colonnes de pixels (p. ex. tous les pixels de la colonne 11)
- une région arbitraire de pixels de l'image (p. ex. les pixels définis de 10,7 à 10,19)
- un pixel dans un ordre aléatoire (p. ex. le pixel à 14,15 et 640,480)
- des pixels de deux images différentes (p. ex. le pixel à 5,1 de l'image 1 et le pixel à 5,1 de l'image 2)
- des pixels en dehors des limites de l'image (p. ex. le pixel à -1,-3)
- un composant de pixel non signé (65311) ou dans une représentation en virgule flottante (p. ex. 0.17836)
- un pixel à plage dynamique élevée qui peut inclure des valeurs négatives (p. ex. -0.0072973525628) ainsi que des valeurs qui dépassent la profondeur du quantum (p. ex. 65931)
- un ou plusieurs pixels simultanément dans différents threads d'exécution
- tous les pixels en mémoire pour tirer parti des accélérations offertes par une exécution conjointe sur des plateformes hétérogènes composées de CPU, de GPU et d'autres processeurs
- les traits associés à chaque canal pour spécifier si le canal de pixel est copié, mis à jour ou fondu
- les masques qui définissent quels pixels sont éligibles à la mise à jour
- des canaux supplémentaires qui profitent à l'utilisateur mais qui restent par ailleurs intacts pour les algorithmes de traitement d'image d'ImageMagick
Compte tenu des formats d'image variés et des exigences de traitement d'image, nous avons implémenté le cache de pixels d'ImageMagick pour fournir un accès séquentiel ou parallèle pratique à n'importe quel pixel à la demande, n'importe où à l'intérieur de la région de l'image (c.-à-d. les pixels authentiques) et de n'importe quelle image d'une séquence. De plus, le cache de pixels permet l'accès aux pixels en dehors des limites définies par l'image (c.-à-d. les pixels virtuels).
Outre les pixels, les images possèdent une pléthore de propriétés et de profils d'image. Les propriétés incluent les attributs bien connus tels que la largeur, la hauteur, la profondeur et l'espace colorimétrique. Une image peut avoir des propriétés optionnelles qui peuvent inclure l'auteur de l'image, un commentaire, une date de création et d'autres. Certaines images incluent également des profils pour la gestion des couleurs, ou des profils informationnels EXIF, IPTC, 8BIM ou XMP. ImageMagick fournit des options de ligne de commande et des méthodes de programmation pour obtenir, définir ou afficher les propriétés ou profils d'image, ou pour appliquer des profils.
ImageMagick se compose de près d'un demi-million de lignes de code C et dépend, en option, de plusieurs millions de lignes de code dans des bibliothèques dépendantes (p. ex. les bibliothèques JPEG, PNG, TIFF). Compte tenu de cela, on pourrait s'attendre à un énorme document d'architecture. Cependant, la grande majorité du traitement d'image consiste simplement à accéder aux pixels et à leurs métadonnées, et notre implémentation simple, élégante et efficace rend cela facile pour le développeur ImageMagick. Nous abordons l'implémentation du cache de pixels et l'obtention et la définition des propriétés et profils d'image dans les sections suivantes. Ensuite, nous abordons l'utilisation d'ImageMagick au sein d'un thread d'exécution. Dans les dernières sections, nous abordons les codeurs d'image pour lire ou écrire un format d'image particulier, suivis de quelques mots sur la création d'un filtre pour accéder aux pixels ou les mettre à jour selon vos exigences personnalisées.
Le cache de pixels
Le cache de pixels d'ImageMagick est un dépôt pour les pixels d'image comportant jusqu'à 64 canaux. Les canaux sont stockés de manière contiguë à la profondeur spécifiée lors de la compilation d'ImageMagick. Les profondeurs de canal sont de 8 bits par composant de pixel pour la version Q8 d'ImageMagick, de 16 bits par composant de pixel pour la version Q16, et de 32 bits par composant de pixel pour la version Q32. Par défaut, les composants de pixel sont des quantités à plage dynamique élevée en virgule flottante 32 bits. Les canaux peuvent contenir n'importe quelle valeur mais contiennent généralement les intensités de rouge, vert, bleu et alpha, ou les intensités de cyan, magenta, jaune, noir et alpha. Un canal peut contenir les index de la table de couleurs pour les images à palette ou le canal noir pour les images CMYK. Le stockage du cache de pixels peut être de la mémoire de tas (heap), de la mémoire mappée adossée au disque, ou sur disque. Le cache de pixels est compté par référence. Seules les propriétés du cache sont copiées lorsque le cache est cloné. Les pixels du cache ne sont copiés par la suite que lorsque vous signalez votre intention de mettre à jour l'un des pixels.
Créer le cache de pixels
Le cache de pixels est associé à une image lors de sa création et il est initialisé lorsque vous essayez d'obtenir ou de placer des pixels. Voici trois méthodes courantes pour associer un cache de pixels à une image :
Créer un canevas d'image initialisé à la couleur d'arrière-plan :
image=AllocateImage(image_info);
if (SetImageExtent(image,640,480) == MagickFalse)
{ /* an exception was thrown */ }
(void) QueryMagickColor("red",&image->background_color,&image->exception);
SetImageBackgroundColor(image);
Créer une image à partir d'une image JPEG sur disque :
(void) strcpy(image_info->filename,"image.jpg"):
image=ReadImage(image_info,exception);
if (image == (Image *) NULL)
{ /* an exception was thrown */ }
Créer une image à partir d'une image en mémoire :
image=BlobToImage(blob_info,blob,extent,exception);
if (image == (Image *) NULL)
{ /* an exception was thrown */ }
Dans notre discussion sur le cache de pixels, nous utilisons l'API MagickCore pour illustrer nos propos, cependant, les principes sont les mêmes pour les autres interfaces de programmation d'ImageMagick.
Lorsque le cache de pixels est initialisé, les pixels sont mis à l'échelle depuis la profondeur de bits dont ils proviennent vers celle requise par le cache de pixels. Par exemple, une image PBM monochrome 1-bit à 1 canal est mise à l'échelle en une image grise 8-bit si vous utilisez la version Q8 d'ImageMagick, et en RGBA 16-bit pour la version Q16. Vous pouvez déterminer la version dont vous disposez avec l'option -version :
$ identify -version
Version: ImageMagick 7.1.2-25 2026-06-04 Q16-HDRI https://imagemagick.org
Comme vous pouvez le voir, la commodité du cache de pixels s'accompagne parfois d'un compromis en matière de stockage (p. ex. stocker une image monochrome 1-bit sur 16 bits est un gaspillage) et de vitesse (c.-à-d. stocker l'image entière en mémoire est généralement plus lent que d'accéder à une ligne de balayage de pixels à la fois). Dans la plupart des cas, les avantages du cache de pixels l'emportent généralement sur les inconvénients.
Accéder au cache de pixels
Une fois le cache de pixels associé à une image, vous voulez généralement obtenir, mettre à jour ou placer des pixels dedans. Nous appelons pixels authentiques les pixels situés à l'intérieur de la région de l'image et pixels virtuels ceux situés en dehors de la région. Utilisez ces méthodes pour accéder aux pixels du cache :
- GetVirtualPixels() : obtient les pixels que vous n'avez pas l'intention de modifier ou les pixels qui se trouvent en dehors de la région de l'image (p. ex. le pixel @ -1,-3)
- GetAuthenticPixels() : obtient les pixels que vous avez l'intention de modifier
- QueueAuthenticPixels() : met en file d'attente les pixels que vous avez l'intention de définir
- SyncAuthenticPixels() : met à jour le cache de pixels avec les pixels modifiés
Voici un extrait de code MagickCore typique pour manipuler les pixels dans le cache de pixels. Dans notre exemple, nous copions les pixels de l'image d'entrée vers l'image de sortie et diminuons l'intensité de 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 */ }
Lorsque nous créons pour la première fois l'image de destination en clonant l'image source, les pixels du cache de pixels ne sont pas copiés. Ils ne sont copiés que lorsque vous signalez vos intentions de modifier ou de définir le cache de pixels en appelant GetAuthenticPixels() ou QueueAuthenticPixels(). Utilisez QueueAuthenticPixels() si vous voulez définir de nouvelles valeurs de pixel plutôt que de mettre à jour des valeurs existantes. Vous pourriez utiliser GetAuthenticPixels() pour définir des valeurs de pixel, mais il est légèrement plus efficace d'utiliser QueueAuthenticPixels() à la place. Enfin, utilisez SyncAuthenticPixels() pour vous assurer que les pixels mis à jour sont poussés vers le cache de pixels.
Vous pouvez associer un contenu arbitraire à chaque pixel, appelé contenu méta. Utilisez GetVirtualMetacontent() (pour lire le contenu) ou GetAuthenticMetacontent() (pour mettre à jour le contenu) pour accéder à ce contenu. Par exemple, pour imprimer le métacontenu, utilisez :
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 */
Le gestionnaire du cache de pixels décide de vous donner un accès direct ou indirect aux pixels de l'image. Dans certains cas, les pixels sont préparés dans un tampon intermédiaire — et c'est pourquoi vous devez appeler SyncAuthenticPixels() pour vous assurer que ce tampon est poussé vers le cache de pixels afin de garantir la mise à jour des pixels correspondants dans le cache. Pour cette raison, nous vous recommandons de ne lire ou mettre à jour qu'une ligne de balayage ou quelques lignes de balayage de pixels à la fois. Cependant, vous pouvez obtenir n'importe quelle région rectangulaire de pixels que vous voulez. GetAuthenticPixels() exige que la région que vous demandez se trouve dans les limites de la zone de l'image. Pour une image de 640 sur 480, vous pouvez obtenir une ligne de balayage de 640 pixels à la rangée 479, mais si vous demandez une ligne de balayage à la rangée 480, une exception est renvoyée (les rangées sont numérotées à partir de 0). GetVirtualPixels() n'a pas cette contrainte. Par exemple,
p=GetVirtualPixels(source,-3,-3,source->columns+3,6,exception);
vous donne les pixels que vous avez demandés sans se plaindre, même si certains ne se trouvent pas dans les confins de la région de l'image.
Pixels virtuels
Il existe une pléthore d'algorithmes de traitement d'image qui nécessitent un voisinage de pixels autour d'un pixel d'intérêt. L'algorithme inclut généralement une mise en garde concernant la manière de gérer les pixels autour des limites de l'image, appelés pixels de bordure. Avec les pixels virtuels, vous n'avez pas à vous soucier d'un traitement spécial des bordures, si ce n'est de choisir quelle méthode de pixel virtuel est la plus appropriée pour votre algorithme.
L'accès aux pixels virtuels est contrôlé par la méthode SetImageVirtualPixelMethod() de l'API MagickCore ou l'option -virtual-pixel en ligne de commande. Les méthodes incluent :
| background | la zone entourant l'image est la couleur d'arrière-plan |
|---|---|
| black | la zone entourant l'image est noire |
| checker-tile | alterne des carrés avec l'image et la couleur d'arrière-plan |
| dither | motif tramé 32x32 non aléatoire |
| edge | étend le pixel de bordure vers l'infini (par défaut) |
| gray | la zone entourant l'image est grise |
| horizontal-tile | mosaïque horizontale de l'image, couleur d'arrière-plan au-dessus/en dessous |
| horizontal-tile-edge | mosaïque horizontale de l'image et réplication des pixels de bordure latérale |
| mirror | mosaïque en miroir de l'image |
| random | choisit un pixel aléatoire de l'image |
| tile | mosaïque de l'image |
| transparent | la zone entourant l'image est d'un noir transparent |
| vertical-tile | mosaïque verticale de l'image, les côtés sont de la couleur d'arrière-plan |
| vertical-tile-edge | mosaïque verticale de l'image et réplication des pixels de bordure latérale |
| white | la zone entourant l'image est blanche |
Stockage du cache et besoins en ressources
Rappelez-vous que cette conception simple et élégante du cache de pixels d'ImageMagick a un coût en matière de stockage et de vitesse de traitement. Les besoins en stockage du cache de pixels évoluent avec la surface de l'image et la profondeur de bits des composants de pixel. Par exemple, si nous avons une image de 640 sur 480 et que nous utilisons la version Q16 non-HDRI d'ImageMagick, le cache de pixels consomme largeur de l'image * hauteur * profondeur de bits / 8 * canaux octets, soit environ 2,3 mébioctets (c.-à-d. 640 * 480 * 2 * 4). Pas trop mal, mais que se passe-t-il si votre image fait 25000 sur 25000 pixels ? Le cache de pixels nécessite environ 4,7 gibioctets de stockage. Aïe. ImageMagick tient compte des éventuels besoins de stockage énormes en mettant en cache les grandes images sur disque plutôt qu'en mémoire. Typiquement, le cache de pixels est stocké en mémoire à l'aide de la mémoire de tas (heap). Si la mémoire de tas est épuisée, nous créons le cache de pixels sur disque et tentons de le mapper en mémoire. Si la mémoire mappée est épuisée, nous utilisons simplement les E/S disque standard. Le stockage sur disque est abondant et bon marché, mais il est aussi très lent — jusqu'à 1000 fois plus lent que l'accès aux pixels en mémoire. Nous pouvons obtenir quelques améliorations de vitesse, jusqu'à 5 fois, si nous mappons en mémoire le cache adossé au disque. Ces décisions concernant le stockage sont prises automagiquement par le gestionnaire du cache de pixels en négociant avec le système d'exploitation. Cependant, vous pouvez influencer la manière dont le gestionnaire du cache de pixels alloue le cache de pixels avec des limites de ressources de cache. Les limites incluent :
| width | largeur maximale d'une image. Dépassez cette limite et une exception est levée et l'opération s'interrompt. |
|---|---|
| height | hauteur maximale d'une image. Dépassez cette limite et une exception est levée et l'opération s'interrompt. |
| area | surface maximale en octets d'une seule image pouvant résider dans la mémoire du cache de pixels. Si cette limite est dépassée, l'image est automagiquement mise en cache sur disque et optionnellement mappée en mémoire. |
| memory | quantité maximale de mémoire en octets à allouer pour le cache de pixels depuis le tas. |
| map | quantité maximale de mémoire mappée en octets à allouer pour le cache de pixels. |
| disk | quantité maximale d'espace disque en octets autorisée pour le cache de pixels. Si cette limite est dépassée, une exception fatale est levée et tout traitement s'arrête. |
| files | nombre maximal de fichiers de cache de pixels ouverts. Lorsque cette limite est dépassée, tous les pixels mis en cache sur disque par la suite sont fermés et rouverts à la demande. Ce comportement permet d'accéder simultanément à un grand nombre d'images sur disque sans pénalité de vitesse en réduisant le nombre d'appels système d'ouverture/fermeture du cache de pixels. |
| thread | nombre maximal de threads autorisés à s'exécuter en parallèle. Votre système peut choisir un nombre de threads inférieur à cette valeur. ImageMagick choisit un nombre optimal de threads par défaut, qui correspond généralement au nombre de cœurs de votre hôte. Réglez cette valeur sur 1 et toutes les régions parallèles sont exécutées par un seul thread. |
| time | nombre maximal de secondes pendant lesquelles le processus est autorisé à s'exécuter. Dépassez cette limite et une exception est levée et le traitement s'arrête. |
Notez que ces limites concernent le cache de pixels d'ImageMagick. Certains algorithmes au sein d'ImageMagick ne respectent pas ces limites, pas plus qu'aucune des bibliothèques déléguées externes (p. ex. JPEG, TIFF, etc.).
Pour déterminer le réglage actuel de ces limites, utilisez cette commande :
-> 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
Vous pouvez définir ces limites soit comme une politique de sécurité (voir policy.xml), avec une variable d'environnement, avec l'option de ligne de commande -limit, ou avec la méthode SetMagickResourceLimit() de l'API MagickCore. À titre d'exemple, notre interface web en ligne pour ImageMagick, MagickStudio, inclut ces limites de politique pour aider à prévenir un déni de service :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policymap [
<!ELEMENT policymap (policy)*>
<!ATTLIST policymap xmlns CDATA #FIXED "">
<!ELEMENT policy EMPTY>
<!ATTLIST policy xmlns CDATA #FIXED "">
<!ATTLIST policy domain NMTOKEN #REQUIRED>
<!ATTLIST policy name NMTOKEN #IMPLIED>
<!ATTLIST policy pattern CDATA #IMPLIED>
<!ATTLIST policy rights NMTOKEN #IMPLIED>
<!ATTLIST policy stealth NMTOKEN #IMPLIED>
<!ATTLIST policy value CDATA #IMPLIED>
]>
<!--
Creating a security policy that fits your specific local environment
before making use of ImageMagick is highly advised. You can find guidance on
setting up this policy at https://imagemagick.org/security-policy/,
and it's important to verify your policy using the validation tool located
at https://imagemagick-secevaluator.doyensec.com/.
Secure ImageMagick security policy:
This stringent security policy prioritizes the implementation of
rigorous controls and restricted resource utilization to establish a
profoundly secure setting while employing ImageMagick. It deactivates
conceivably hazardous functionalities, including specific coders like
SVG or HTTP. The policy promotes the tailoring of security measures to
harmonize with the requirements of the local environment and the guidelines
of the organization. This protocol encompasses explicit particulars like
limitations on memory consumption, sanctioned pathways for reading and
writing, confines on image sequences, the utmost permissible duration of
workflows, allocation of disk space intended for image data, and even an
undisclosed passphrase for remote connections. By adopting this robust
policy, entities can elevate their overall security stance and alleviate
potential vulnerabilities.
-->
<policymap>
<!-- Set maximum parallel threads. -->
<policy domain="resource" name="thread" value="2"/>
<!-- Set maximum time in seconds. When this limit is exceeded, an exception
is thrown and processing stops. -->
<policy domain="resource" name="time" value="120"/>
<!-- Set maximum number of open pixel cache files. When this limit is
exceeded, any subsequent pixels cached to disk are closed and reopened
on demand. -->
<policy domain="resource" name="file" value="768"/>
<!-- Set maximum amount of memory in bytes to allocate for the pixel cache
from the heap. When this limit is exceeded, the image pixels are cached
to memory-mapped disk. -->
<policy domain="resource" name="memory" value="256MiB"/>
<!-- Set maximum amount of memory map in bytes to allocate for the pixel
cache. When this limit is exceeded, the image pixels are cached to
disk. -->
<policy domain="resource" name="map" value="512MiB"/>
<!-- Set the maximum width * height of an image that can reside in the pixel
cache memory. Images that exceed the area limit are cached to disk. -->
<policy domain="resource" name="area" value="16KP"/>
<!-- Set maximum amount of disk space in bytes permitted for use by the pixel
cache. When this limit is exceeded, the pixel cache is not be created
and an exception is thrown. -->
<policy domain="resource" name="disk" value="1GiB"/>
<!-- Set the maximum length of an image sequence. When this limit is
exceeded, an exception is thrown. -->
<policy domain="resource" name="list-length" value="32"/>
<!-- Set the maximum width of an image. When this limit is exceeded, an
exception is thrown. -->
<policy domain="resource" name="width" value="8KP"/>
<!-- Set the maximum height of an image. When this limit is exceeded, an
exception is thrown. -->
<policy domain="resource" name="height" value="8KP"/>
<!-- Periodically yield the CPU for at least the time specified in
milliseconds. -->
<!-- -->
<!-- Do not create temporary files in the default shared directories, instead
specify a private area to store only ImageMagick temporary files. -->
<!-- -->
<!-- Force memory initialization by memory mapping select memory
allocations. -->
<policy domain="cache" name="memory-map" value="anonymous"/>
<!-- Ensure all image data is fully flushed and synchronized to disk. -->
<policy domain="cache" name="synchronize" value="true"/>
<!-- Replace passphrase for secure distributed processing -->
<!-- -->
<!-- Do not permit any delegates to execute. -->
<policy domain="delegate" rights="none" pattern="*"/>
<!-- Do not permit any image filters to load. -->
<policy domain="filter" rights="none" pattern="*"/>
<!-- Don't read/write from/to stdin/stdout. -->
<policy domain="path" rights="none" pattern="-"/>
<!-- don't read sensitive paths. -->
<policy domain="path" rights="none" pattern="/etc/*"/>
<!-- Indirect reads are not permitted. -->
<policy domain="path" rights="none" pattern="@*"/>
<!-- These image types are security risks on read, but write is fine -->
<policy domain="module" rights="write" pattern="{MSL,MVG,PS,SVG,URL,XPS}"/>
<!-- This policy sets the number of times to replace content of certain
memory buffers and temporary files before they are freed or deleted. -->
<policy domain="system" name="shred" value="1"/>
<!-- Enable the initialization of buffers with zeros, resulting in a minor
performance penalty but with improved security. -->
<policy domain="system" name="memory-map" value="anonymous"/>
<!-- Set the maximum amount of memory in bytes that is permitted for
allocation requests. -->
<policy domain="system" name="max-memory-request" value="256MiB"/>
</policymap>
Comme nous traitons plusieurs sessions simultanées, nous ne voulons pas qu'une seule session consomme toute la mémoire disponible. Avec cette politique, les grandes images sont mises en cache sur disque. Si l'image est trop grande et dépasse la limite de disque du cache de pixels, le programme se termine. De plus, nous imposons une limite de temps pour empêcher toute tâche de traitement incontrôlée. Si une image a une largeur ou une hauteur dépassant 8192 pixels, une exception est levée et le traitement s'arrête. À partir d'ImageMagick 7.0.1-8, vous pouvez empêcher l'utilisation d'un délégué quelconque ou de tous les délégués (réglez le motif sur "*"). Notez qu'avant cette version, il fallait utiliser un domaine "coder" pour empêcher l'utilisation des délégués (p. ex. domain="coder" rights="none" pattern="HTTPS"). La politique empêche également les lectures indirectes. Si vous voulez, par exemple, lire du texte depuis un fichier (p. ex. caption:@myCaption.txt), vous devrez supprimer cette politique.
Notez que les limites du cache sont globales à chaque invocation d'ImageMagick, ce qui signifie que si vous créez plusieurs images, les besoins en ressources combinés sont comparés à la limite pour déterminer la disposition du stockage du cache de pixels.
Pour déterminer quel type de ressources et quelle quantité sont consommés par le cache de pixels, ajoutez l'option -debug cache à la ligne de commande :
$ 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]
Cette commande utilise un cache de pixels en mémoire. Le logo a consommé 4.688MiB et après son affûtage (sharpen), 3.516MiB.
Cache de pixels distribué
Un cache de pixels distribué est une extension du cache de pixels traditionnel disponible sur un seul hôte. Le cache de pixels distribué peut s'étendre sur plusieurs serveurs afin de croître en taille et en capacité transactionnelle pour prendre en charge de très grandes images. Démarrez le serveur de cache de pixels sur une ou plusieurs machines. Lorsque vous lisez ou opérez sur une image et que les ressources locales du cache de pixels sont épuisées, ImageMagick contacte un ou plusieurs de ces serveurs de pixels distants pour stocker ou récupérer des pixels. Le cache de pixels distribué dépend de la bande passante réseau pour acheminer les pixels vers et depuis le serveur distant. À ce titre, il sera probablement nettement plus lent qu'un cache de pixels utilisant un stockage local (p. ex. mémoire, disque, etc.).
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
Vues de cache
GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() et SyncAuthenticPixels(), de l'API MagickCore, ne peuvent traiter qu'une seule zone de cache de pixels par image à la fois. Supposons que vous vouliez accéder à la première et à la dernière ligne de balayage de la même image en même temps ? La solution est d'utiliser une vue de cache. Une vue de cache vous permet d'accéder à autant de zones simultanément dans le cache de pixels que vous le souhaitez. Les méthodes de la vue de cache sont analogues aux méthodes précédentes, sauf que vous devez d'abord ouvrir une vue et la fermer lorsque vous en avez terminé. Voici un extrait de code MagickCore qui nous permet d'accéder simultanément à la première et à la dernière rangée de pixels de l'image :
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 */ }
Format Magick Pixel Cache
Rappelez-vous que chaque format d'image est décodé par ImageMagick et que les pixels sont déposés dans le cache de pixels. Si vous écrivez une image, les pixels sont lus depuis le cache de pixels et encodés selon les besoins du format dans lequel vous écrivez (p. ex. GIF, PNG, etc.). Le format Magick Pixel Cache (MPC) est conçu pour éliminer la surcharge du décodage et de l'encodage des pixels vers et depuis un format d'image. MPC écrit deux fichiers. L'un, avec l'extension .mpc, conserve toutes les propriétés associées à l'image ou à la séquence d'images (p. ex. largeur, hauteur, espace colorimétrique, etc.) et le second, avec l'extension .cache, est le cache de pixels dans le format brut natif. Lors de la lecture d'un fichier image MPC, ImageMagick lit les propriétés de l'image et mappe en mémoire le cache de pixels sur disque, éliminant le besoin de décoder les pixels de l'image. Le compromis se situe au niveau de l'espace disque. MPC est généralement plus volumineux en taille de fichier que la plupart des autres formats d'image.
L'utilisation la plus efficace des fichiers image MPC est un modèle d'écriture unique, lecture multiple. Par exemple, votre flux de travail nécessite l'extraction de blocs aléatoires de pixels de l'image source. Plutôt que de relire et éventuellement de décompresser l'image source à chaque fois, nous utilisons MPC et mappons l'image directement en mémoire.
Pratiques recommandées pour le cache de pixels
Bien que vous puissiez demander n'importe quel pixel du cache de pixels, n'importe quel bloc de pixels, n'importe quelle ligne de balayage, plusieurs lignes de balayage, n'importe quelle rangée ou plusieurs rangées avec les méthodes GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels, GetCacheViewVirtualPixels(), GetCacheViewAuthenticPixels() et QueueCacheViewAuthenticPixels(), ImageMagick est optimisé pour renvoyer quelques pixels ou quelques rangées de pixels à la fois. Il existe des optimisations supplémentaires si vous demandez une seule ligne de balayage ou quelques lignes de balayage à la fois. Ces méthodes permettent également l'accès aléatoire au cache de pixels, cependant, ImageMagick est optimisé pour l'accès séquentiel. Bien que vous puissiez accéder séquentiellement aux lignes de balayage de pixels de la dernière rangée de l'image à la première, vous pouvez obtenir un gain de performance si vous accédez aux lignes de balayage de la première rangée de l'image à la dernière, dans l'ordre séquentiel.
Vous pouvez obtenir, modifier ou définir des pixels par rangée ou par colonne. Cependant, il est plus efficace d'accéder aux pixels par rangée que par colonne.
Si vous mettez à jour les pixels renvoyés par GetAuthenticPixels() ou GetCacheViewAuthenticPixels(), n'oubliez pas d'appeler respectivement SyncAuthenticPixels() ou SyncCacheViewAuthenticPixels() pour vous assurer que vos modifications sont synchronisées avec le cache de pixels.
Utilisez QueueAuthenticPixels() ou QueueCacheViewAuthenticPixels() si vous définissez une valeur de pixel initiale. La méthode GetAuthenticPixels() ou GetCacheViewAuthenticPixels() lit les pixels depuis le cache et, si vous définissez une valeur de pixel initiale, cette lecture est inutile. N'oubliez pas d'appeler respectivement SyncAuthenticPixels() ou SyncCacheViewAuthenticPixels() pour pousser toute modification de pixel vers le cache de pixels.
GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() et SyncAuthenticPixels() sont légèrement plus efficaces que leurs équivalents en vue de cache. Cependant, les vues de cache sont requises si vous avez besoin d'accéder à plus d'une région de l'image simultanément ou si plus d'un thread d'exécution accède à l'image.
Vous pouvez demander des pixels en dehors des limites de l'image avec GetVirtualPixels() ou GetCacheViewVirtualPixels(), cependant, il est plus efficace de demander des pixels dans les confins de la région de l'image.
Bien que vous puissiez forcer le cache de pixels sur disque à l'aide de limites de ressources appropriées, l'accès disque peut être jusqu'à 1000 fois plus lent que l'accès mémoire. Pour un accès rapide et efficace au cache de pixels, essayez de conserver le cache de pixels en mémoire de tas (heap).
La version Q16 d'ImageMagick vous permet de lire et d'écrire des images 16 bits sans mise à l'échelle, mais le cache de pixels consomme deux fois plus de ressources que la version Q8. Si votre système dispose de ressources mémoire ou disque limitées, envisagez la version Q8 d'ImageMagick. De plus, la version Q8 s'exécute généralement plus vite que la version Q16.
La grande majorité des formats d'image et des algorithmes se limitent à une plage fixe de valeurs de pixel allant de 0 à une valeur maximale, par exemple, la version Q16 d'ImageMagick autorise des intensités de 0 à 65535. L'imagerie à plage dynamique élevée (HDRI), cependant, permet une plage dynamique d'expositions bien plus grande (c.-à-d. une grande différence entre les zones claires et sombres) que les techniques d'imagerie numérique standard. La HDRI représente avec précision la large gamme de niveaux d'intensité que l'on trouve dans les scènes réelles, allant de la lumière directe du soleil la plus vive jusqu'aux ombres les plus profondes et les plus sombres. Activez la HDRI lors de la compilation d'ImageMagick pour traiter des images à plage dynamique élevée, mais gardez à l'esprit que chaque composant de pixel est une valeur en virgule flottante 32 bits. De plus, les valeurs de pixel ne sont pas bornées (clamped) par défaut, de sorte que certains algorithmes peuvent donner des résultats inattendus en raison de valeurs de pixel hors limites que la version non-HDRI.
Si vous traitez de grandes images, assurez-vous que le cache de pixels est écrit sur une zone disque disposant de beaucoup d'espace libre. Sous Linux, il s'agit généralement de /tmp et pour Windows, de c:/temp. Vous pouvez indiquer à ImageMagick d'écrire le cache de pixels à un autre emplacement et de préserver la mémoire avec ces options :
magick -limit memory 2GB -limit map 4GB -define registry:temporary-path=/data/tmp ...
Définissez des limites de ressources globales pour votre environnement dans le fichier de configuration policy.xml.
Si vous prévoyez de traiter la même image plusieurs fois, envisagez le format MPC. La lecture d'une image MPC a une surcharge proche de zéro car elle est dans le format natif du cache de pixels, éliminant le besoin de décoder les pixels de l'image. Voici un exemple :
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 est idéal pour les sites web. Il réduit la surcharge de la lecture et de l'écriture d'une image. Nous l'utilisons exclusivement dans notre studio d'image en ligne.
Propriétés et profils d'image
Les images ont des métadonnées qui leur sont associées sous la forme de propriétés (p. ex. largeur, hauteur, description, etc.) et de profils (p. ex. EXIF, IPTC, gestion des couleurs). ImageMagick fournit des méthodes pratiques pour obtenir, définir ou mettre à jour les propriétés d'image et pour obtenir, définir, mettre à jour ou appliquer des profils. Certaines des propriétés d'image les plus courantes sont associées à la structure Image dans l'API MagickCore. Par exemple :
(void) printf("image width: %lu, height: %lu\n",image->columns,image->rows);
Pour la grande majorité des propriétés d'image, comme un commentaire ou une description d'image, nous utilisons les méthodes GetImageProperty() et SetImageProperty(). Ici, nous définissons une propriété et la récupérons aussitô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 prend en charge les artefacts avec les méthodes GetImageArtifact() et SetImageArtifact(). Les artefacts sont des propriétés furtives qui ne sont pas exportées vers les formats d'image (p. ex. PNG).
Les profils d'image sont gérés avec les méthodes GetImageProfile(), SetImageProfile() et ProfileImage(). Ici, nous définissons un profil et le récupérons aussitô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);
Imagerie multispectrale
ImageMagick prend en charge l'imagerie multispectrale où tous les canaux ont les mêmes dimensions et le même nombre de pixels que l'image originale. Cependant, tous les formats d'image ne prennent pas en charge les images multispectrales. PSD, TIFF, MIFF, MPC et FTXT prennent pleinement en charge les images multispectrales jusqu'à 31 bandes, dont 21 sont des méta-canaux. Notez que si vous compilez ImageMagick avec l'option --enable-64bit-channel-masks du script configure, vous pouvez traiter des images multispectrales de 62 bandes avec jusqu'à 52 méta-canaux.
Si vous avez un cas d'usage qui n'est pas actuellement pris en charge par un format d'image, publiez-le sur le forum de discussion. Il y a de bonnes chances que nous puissions prendre en charge votre cas d'usage dans une future version d'ImageMagick.
Streaming de pixels
ImageMagick permet le streaming des pixels au fur et à mesure qu'ils sont lus depuis ou écrits vers une image. Cela présente plusieurs avantages par rapport au cache de pixels. Le temps et les ressources consommés par le cache de pixels évoluent avec la surface d'une image, tandis que les ressources du flux de pixels évoluent avec la largeur d'une image. L'inconvénient est que les pixels doivent être consommés au fur et à mesure de leur diffusion, de sorte qu'il n'y a pas de persistance.
Utilisez ReadStream() ou WriteStream() avec une méthode de rappel appropriée dans votre programme MagickCore pour consommer les pixels au fur et à mesure de leur diffusion. Voici un exemple abrégé d'utilisation de 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);
Nous fournissons également un outil léger, stream, pour diffuser un ou plusieurs composants de pixel de l'image ou d'une portion de l'image vers le format de stockage de votre choix. Il écrit les composants de pixel au fur et à mesure qu'ils sont lus depuis l'image d'entrée, une rangée à la fois, ce qui rend stream souhaitable lorsque vous travaillez avec de grandes images ou lorsque vous avez besoin de composants de pixel bruts. La majorité des formats d'image diffusent les pixels (rouge, vert et bleu) de gauche à droite et de haut en bas. Cependant, quelques formats ne prennent pas en charge cet ordre courant (p. ex. le format PSD).
Prise en charge des grandes images
ImageMagick a la capacité de gérer des tailles d'image allant du méga- au téra-pixel, englobant les opérations de lecture, de traitement et d'écriture. En théorie, les dimensions des images peuvent s'étendre jusqu'à 31 millions de rangées/colonnes sur un système d'exploitation 32 bits et jusqu'à un faramineux 31 billions sur un OS 64 bits. Cependant, les dimensions réellement atteignables sont nettement inférieures, dépendant des ressources disponibles sur votre ordinateur hôte. Il est essentiel de savoir que certains formats d'image imposent des limites à la taille de l'image. Par exemple, les images Photoshop sont limitées à un maximum de 300 000 pixels de largeur ou de hauteur. Ici, nous redimensionnons une image à un quart de million de pixels de côté :
magick logo: -resize 250000x250000 logo.miff
Pour les grandes images, les ressources mémoire seront probablement épuisées et ImageMagick créera à la place un cache de pixels sur disque. Assurez-vous de disposer de beaucoup d'espace disque temporaire. Si votre partition disque temporaire par défaut est trop petite, indiquez à ImageMagick d'utiliser une autre partition disposant de beaucoup d'espace libre. Par exemple :
magick -define registry:temporary-path=/data/tmp logo: \
-resize 250000x250000 logo.miff
Pour vous assurer que les grandes images ne consomment pas toute la mémoire de votre système, forcez les pixels de l'image vers un disque mappé en mémoire avec des limites de ressources :
magick -define registry:temporary-path=/data/tmp -limit memory 16mb \
logo: -resize 250000x250000 logo.miff
Ici, nous forçons tous les pixels de l'image sur disque :
magick -define registry:temporary-path=/data/tmp -limit area 0 \
logo: -resize 250000x250000 logo.miff
La mise en cache des pixels sur disque est environ 1000 fois plus lente que la mémoire. Attendez-vous à de longs temps d'exécution lors du traitement de grandes images sur disque avec ImageMagick. Vous pouvez surveiller la progression avec cette commande :
magick -monitor -limit memory 2GiB -limit map 4GiB -define registry:temporary-path=/data/tmp \
logo: -resize 250000x250000 logo.miff
Pour des images vraiment grandes, ou si les ressources de votre hôte sont limitées, vous pouvez utiliser un cache de pixels distribué sur un ou plusieurs hôtes distants :
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
En raison de la latence du réseau, attendez-vous à un ralentissement substantiel du traitement de votre flux de travail.
Threads d'exécution
Beaucoup des algorithmes internes d'ImageMagick sont threadés pour tirer parti des accélérations offertes par les puces de processeur multicœurs. Cependant, vous êtes libre d'utiliser les algorithmes d'ImageMagick dans vos threads d'exécution, à l'exception des méthodes de cache de pixels GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() ou SyncAuthenticPixels() de MagickCore. Ces méthodes sont destinées à un seul thread d'exécution, à l'exception d'une section parallèle OpenMP. Pour accéder au cache de pixels avec plus d'un thread d'exécution, utilisez une vue de cache. Nous le faisons pour la méthode CompositeImage(), par exemple. Supposons que nous voulions composer une seule image source par-dessus une image de destination différente dans chaque thread d'exécution. Si nous utilisons GetVirtualPixels(), les résultats sont imprévisibles car plusieurs threads demanderaient probablement différentes zones du cache de pixels simultanément. À la place, nous utilisons GetCacheViewVirtualPixels() qui crée une vue unique pour chaque thread d'exécution, garantissant que notre programme se comporte correctement quel que soit le nombre de threads invoqués. Les autres interfaces de programmation, telles que l'API MagickWand, sont entièrement thread-safe, il n'y a donc aucune précaution particulière pour les threads d'exécution.
Voici un extrait de code MagickCore qui tire parti des threads d'exécution avec le paradigme de programmation 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");
Cet extrait de code convertit un bitmap Windows non compressé en une image 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;
}
Si vous appelez l'API ImageMagick depuis votre application compatible OpenMP et que vous avez l'intention d'augmenter dynamiquement le nombre de threads disponibles dans les régions parallèles suivantes, veillez à effectuer l'augmentation avant d'appeler l'API, sinon ImageMagick risque de planter.
MagickWand prend en charge les vues de wand. Une vue itère sur l'ensemble, ou une portion, de l'image en parallèle et, pour chaque rangée de pixels, elle invoque une méthode de rappel que vous fournissez. Cela limite la majeure partie de votre activité de programmation parallèle à ce seul module. Il existe des méthodes similaires dans MagickCore. Pour un exemple, voir le même algorithme de contraste sigmoïdal implémenté à la fois dans MagickWand et MagickCore.
Dans la plupart des cas, le nombre de threads par défaut est réglé sur le nombre de cœurs de processeur de votre système pour des performances optimales. Cependant, si votre système est hyperthreadé ou si vous vous exécutez sur un hôte virtuel et que seul un sous-ensemble des processeurs est disponible pour votre instance de serveur, vous pourriez obtenir une augmentation des performances en réglant la politique de thread ou la variable d'environnement MAGICK_THREAD_LIMIT. Par exemple, votre hôte virtuel dispose de 8 processeurs mais seuls 2 sont assignés à votre instance de serveur. La valeur par défaut de 8 threads peut causer de graves problèmes de performance. Une solution consiste à limiter le nombre de threads aux processeurs disponibles dans votre fichier de configuration policy.xml :
<policy domain="resource" name="thread" value="2"/>
Ou supposons que votre ordinateur hyperthreadé à 12 cœurs utilise par défaut 24 threads. Réglez la variable d'environnement MAGICK_THREAD_LIMIT et vous obtiendrez probablement de meilleures performances :
export MAGICK_THREAD_LIMIT=12
Le comité OpenMP n'a pas défini le comportement de la combinaison d'OpenMP avec d'autres modèles de threading tels que les threads Posix. Cependant, avec les versions modernes de Linux, OpenMP et les threads Posix semblent interopérer sans problème. Si vous voulez utiliser les threads Posix depuis un module de programme qui appelle l'une des interfaces de programmation d'application d'ImageMagick (p. ex. MagickCore, MagickWand, Magick++, etc.) sous Mac OS X ou une version Linux plus ancienne, vous devrez peut-être désactiver la prise en charge d'OpenMP au sein d'ImageMagick. Ajoutez l'option --disable-openmp à la ligne de commande du script configure, puis recompilez et réinstallez ImageMagick.
Vous pouvez encore augmenter les performances en réduisant la contention de verrous avec la bibliothèque d'allocation mémoire tcmalloc. Pour l'activer, ajoutez --with-tcmalloc à la ligne de commande configure lorsque vous compilez ImageMagick.
Performance des threads
Il peut être difficile de prédire le comportement dans un environnement parallèle. Les performances peuvent dépendre d'un certain nombre de facteurs, notamment le compilateur, la version de la bibliothèque OpenMP, le type de processeur, le nombre de cœurs, la quantité de mémoire, l'activation ou non de l'hyperthreading, le mélange d'applications s'exécutant simultanément avec ImageMagick, ou l'algorithme de traitement d'image particulier que vous utilisez. La seule façon d'être certain d'obtenir des performances optimales, en termes de nombre de threads, est de réaliser un benchmark. ImageMagick inclut un threading progressif lors du benchmark d'une commande et renvoie le temps écoulé et l'efficacité pour un ou plusieurs threads. Cela peut vous aider à identifier le nombre de threads le plus efficace dans votre environnement. Pour ce benchmark, nous affûtons (sharpen) une image de modèle de 1920x1080, 10 fois, avec 1 à 12 threads :
$ 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
Le point optimal pour cet exemple est de 6 threads. Cela a du sens puisqu'il y a 6 cœurs physiques. Les 6 autres sont des hyperthreads. Il semble que l'affûtage (sharpening) ne bénéficie pas de l'hyperthreading.
Dans certains cas, il peut être optimal de régler le nombre de threads sur 1 ou de désactiver complètement OpenMP avec la variable d'environnement MAGICK_THREAD_LIMIT, l'option de ligne de commande -limit, ou le fichier de configuration policy.xml.
Activer la limitation dynamique pour des performances optimisées
Une autre option efficace consiste à activer la limitation dynamique (dynamic throttling) dans ImageMagick. Lorsque cette fonctionnalité est activée, ImageMagick introduit automatiquement un léger délai entre le traitement des lignes de balayage de l'image en fonction de la charge moyenne actuelle de votre système. Cela aide à prévenir l'épuisement des ressources et garantit des performances plus régulières, en particulier sous de lourdes charges de travail.
Pour activer la limitation dynamique, ajoutez la configuration de politique suivante :
<policy domain="resource" name="dynamic-throttle" value="true"/>
Traitement distribué hétérogène
ImageMagick inclut la prise en charge du traitement distribué hétérogène avec le framework OpenCL. Les kernels OpenCL au sein d'ImageMagick permettent aux algorithmes de traitement d'image de s'exécuter sur des plateformes hétérogènes composées de CPU, de GPU et d'autres processeurs. Selon votre plateforme, les accélérations peuvent être d'un ordre de grandeur plus rapides que le CPU unique traditionnel.
Vérifiez d'abord que votre version d'ImageMagick inclut la prise en charge de la fonctionnalité OpenCL :
magick identify -version
Features: DPC Cipher Modules OpenCL OpenMP(4.5)
Si c'est le cas, exécutez cette commande pour réaliser une accélération significative de la convolution d'image :
magick image.png -convolve '-1, -1, -1, -1, 9, -1, -1, -1, -1' convolve.png
Si un accélérateur n'est pas disponible ou si l'accélérateur ne répond pas, ImageMagick revient à l'algorithme de convolution non accéléré.
Voici un exemple de kernel OpenCL qui effectue la convolution d'une image :
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);
};
Voir MagickCore/accelerate.c pour une implémentation complète de la convolution d'image avec un kernel OpenCL.
Notez que sous Windows, vous pourriez rencontrer un problème avec le TDR (Timeout Detection and Recovery des GPU). Son but est de détecter les tâches incontrôlées qui bloquent le GPU en utilisant un seuil de temps d'exécution. Pour certains GPU bas de gamme plus anciens exécutant les filtres OpenCL dans ImageMagick, des temps d'exécution plus longs pourraient déclencher le mécanisme TDR et préempter le filtre d'image GPU. Lorsque cela se produit, ImageMagick revient automatiquement au chemin de code CPU et renvoie les résultats attendus. Pour éviter la préemption, augmentez la clé de registre TdrDelay.
Codeurs d'image personnalisés
Un codeur d'image (c.-à-d. encodeur / décodeur) est responsable de l'enregistrement, de la classification optionnelle, de la lecture optionnelle, de l'écriture optionnelle et de la désinscription d'un format d'image (p. ex. PNG, GIF, JPEG, etc.). L'enregistrement d'un codeur d'image signale à ImageMagick qu'un format particulier est disponible en lecture ou en écriture. Tandis que la désinscription indique à ImageMagick que le format n'est plus disponible. La méthode de classification examine les premiers octets d'une image et détermine si l'image est dans le format attendu. Le lecteur définit la taille de l'image, l'espace colorimétrique et d'autres propriétés et charge le cache de pixels avec les pixels. Le lecteur renvoie une seule image ou une séquence d'images (si le format prend en charge plusieurs images par fichier), ou, si une erreur survient, une exception et une image nulle. L'écrivain fait l'inverse. Il prend les propriétés de l'image, décharge le cache de pixels et les écrit selon les besoins du format d'image.
Voici la liste d'un exemple de codeur personnalisé. Il lit et écrit des images au format d'image MGK, qui est simplement un ID suivi de la largeur et de la hauteur de l'image, suivies des valeurs de pixel 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);
}
Pour invoquer le codeur personnalisé depuis la ligne de commande, utilisez ces commandes :
magick logo: logo.mgk
display logo.mgk
Nous fournissons le Magick Coder Kit pour vous aider à démarrer l'écriture de votre propre codeur personnalisé.
Avant de compiler, définissez la variable d'environnement PKG_CONFIG_PATH si ImageMagick n'est pas dans le chemin système par défaut :
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
Filtres d'image personnalisés
ImageMagick fournit un mécanisme pratique pour ajouter vos propres algorithmes de traitement d'image personnalisés. Nous appelons ceux-ci des filtres d'image et ils sont invoqués depuis la ligne de commande avec l'option -process ou depuis la méthode ExecuteModuleProcess() de l'API MagickCore.
Voici la liste d'un exemple de filtre d'image personnalisé. Il calcule quelques statistiques telles que la moyenne et l'écart-type de la luminosité et de la saturation des pixels.
#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);
}
Pour invoquer le filtre personnalisé depuis la ligne de commande, utilisez cette commande :
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
Nous fournissons le Magick Filter Kit pour vous aider à démarrer l'écriture de votre propre filtre d'image personnalisé.
Avant de compiler, définissez la variable d'environnement PKG_CONFIG_PATH si ImageMagick n'est pas dans le chemin système par défaut :
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig