O cache de pixels • Propriedades e perfis de imagem • Imagens multiespectrais • Suporte a imagens grandes • Streaming de pixels • Threads de execução • Habilitar o controle dinâmico de carga para desempenho otimizado • Processamento distribuído heterogêneo • Coders de imagem personalizados • Filtros de imagem personalizados
Os cidadãos de Oz estavam bastante satisfeitos com seu benfeitor, o todo-poderoso Mágico. Eles aceitavam sua sabedoria e benevolência sem nunca questionar o quem, o porquê e o onde de seu poder. Como os cidadãos de Oz, se você se sente confortável com o fato de o ImageMagick poder ajudá-lo a converter, editar ou compor suas imagens sem saber o que acontece por trás das cortinas, sinta-se à vontade para pular esta seção. No entanto, se quiser saber mais sobre o software e os algoritmos por trás do ImageMagick, continue lendo. Para aproveitar plenamente esta discussão, você deve estar familiarizado com a nomenclatura de imagens e com programação de computadores.
Visão geral da arquitetura
Uma imagem normalmente consiste em uma região retangular de pixels e metadados. Para converter, editar ou compor uma imagem de forma eficiente, precisamos de acesso conveniente a qualquer pixel em qualquer lugar dentro da região (e às vezes fora dela). E, no caso de uma sequência de imagens, precisamos de acesso a qualquer pixel de qualquer região de qualquer imagem da sequência. No entanto, existem centenas de formatos de imagem como JPEG, TIFF, PNG, GIF, etc., que dificultam o acesso aos pixels sob demanda. Dentro desses formatos encontramos diferenças em:
- espaço de cor (por exemplo, sRGB, RGB linear, GRAY linear, CMYK, YUV, Lab, etc.)
- profundidade de bits (por exemplo, 1, 4, 8, 12, 16, etc.)
- formato de armazenamento (por exemplo, sem sinal, com sinal, float, double, etc.)
- compressão (por exemplo, sem compressão, RLE, Zip, BZip, etc.)
- orientação (isto é, de cima para baixo, da direita para a esquerda, etc.),
- layout (por exemplo, raw, intercalado com opcodes, etc.)
Além disso, alguns pixels de imagem podem exigir atenuação, alguns formatos permitem mais de um quadro e alguns formatos contêm gráficos vetoriais que primeiro precisam ser rasterizados (convertidos de vetor para pixels).
Uma implementação eficiente de um algoritmo de processamento de imagem pode exigir que obtenhamos ou definamos:
- um pixel por vez (por exemplo, o pixel na localização 10,3)
- uma única scanline (por exemplo, todos os pixels da linha 4)
- algumas scanlines de uma vez (por exemplo, as linhas de pixels 4-7)
- uma única coluna ou colunas de pixels (por exemplo, todos os pixels da coluna 11)
- uma região arbitrária de pixels da imagem (por exemplo, os pixels definidos de 10,7 a 10,19)
- um pixel em ordem aleatória (por exemplo, o pixel em 14,15 e em 640,480)
- pixels de duas imagens diferentes (por exemplo, o pixel em 5,1 da imagem 1 e o pixel em 5,1 da imagem 2)
- pixels fora dos limites da imagem (por exemplo, o pixel em -1,-3)
- um componente de pixel sem sinal (65311) ou em representação de ponto flutuante (por exemplo, 0.17836)
- um pixel de alta faixa dinâmica que pode incluir valores negativos (por exemplo, -0.0072973525628) bem como valores que excedem a profundidade de quantum (por exemplo, 65931)
- um ou mais pixels simultaneamente em diferentes threads de execução
- todos os pixels em memória para aproveitar os ganhos de velocidade oferecidos pela execução em conjunto em plataformas heterogêneas compostas por CPUs, GPUs e outros processadores
- traits associados a cada canal para especificar se o canal de pixel é copiado, atualizado ou mesclado
- máscaras que definem quais pixels estão elegíveis para serem atualizados
- canais extras que beneficiam o usuário mas, fora isso, permanecem intocados pelos algoritmos de processamento de imagem do ImageMagick
Dados os variados formatos de imagem e os requisitos de processamento de imagem, implementamos o cache de pixels do ImageMagick para fornecer acesso sequencial ou paralelo conveniente a qualquer pixel sob demanda em qualquer lugar dentro da região da imagem (isto é, pixels autênticos) e de qualquer imagem de uma sequência. Além disso, o cache de pixels permite o acesso a pixels fora dos limites definidos pela imagem (isto é, pixels virtuais).
Além dos pixels, as imagens têm uma infinidade de propriedades e perfis de imagem. As propriedades incluem os atributos bem conhecidos como largura, altura, profundidade e espaço de cor. Uma imagem pode ter propriedades opcionais que podem incluir o autor da imagem, um comentário, uma data de criação e outras. Algumas imagens também incluem perfis para gerenciamento de cores, ou perfis informativos EXIF, IPTC, 8BIM ou XMP. O ImageMagick fornece opções de linha de comando e métodos de programação para obter, definir ou visualizar propriedades ou perfis de imagem, ou aplicar perfis.
O ImageMagick consiste em quase meio milhão de linhas de código C e, opcionalmente, depende de vários milhões de linhas de código em bibliotecas dependentes (por exemplo, as bibliotecas JPEG, PNG, TIFF). Diante disso, poderíamos esperar um enorme documento de arquitetura. No entanto, a grande maioria do processamento de imagem é simplesmente acessar pixels e seus metadados, e nossa implementação simples, elegante e eficiente torna isso fácil para o desenvolvedor do ImageMagick. Discutimos a implementação do cache de pixels e a obtenção e definição de propriedades e perfis de imagem nas próximas seções. Em seguida, discutimos o uso do ImageMagick dentro de uma thread de execução. Nas seções finais, discutimos os coders de imagem para ler ou escrever um formato de imagem específico, seguido de algumas palavras sobre a criação de um filtro para acessar ou atualizar pixels com base em seus requisitos personalizados.
O cache de pixels
O cache de pixels do ImageMagick é um repositório para os pixels de uma imagem com até 64 canais. Os canais são armazenados de forma contígua na profundidade especificada quando o ImageMagick foi compilado. As profundidades de canal são de 8 bits por componente de pixel para a versão Q8 do ImageMagick, 16 bits por componente de pixel para a versão Q16 e 32 bits por componente de pixel para a versão Q32. Por padrão, os componentes de pixel são quantidades de alta faixa dinâmica de ponto flutuante de 32 bits. Os canais podem conter qualquer valor, mas normalmente contêm intensidades de vermelho, verde, azul e alfa, ou intensidades de ciano, magenta, amarelo, preto e alfa. Um canal pode conter os índices de mapa de cores para imagens com mapa de cores ou o canal preto para imagens CMYK. O armazenamento do cache de pixels pode ser memória heap, memória mapeada respaldada por disco, ou em disco. O cache de pixels usa contagem de referências. Apenas as propriedades do cache são copiadas quando o cache é clonado. Os pixels do cache só são copiados posteriormente quando você sinaliza sua intenção de atualizar algum dos pixels.
Criar o cache de pixels
O cache de pixels é associado a uma imagem quando ela é criada e é inicializado quando você tenta obter ou colocar pixels. Aqui estão três métodos comuns para associar um cache de pixels a uma imagem:
Crie uma tela de imagem inicializada com a cor de fundo:
image=AllocateImage(image_info);
if (SetImageExtent(image,640,480) == MagickFalse)
{ /* an exception was thrown */ }
(void) QueryMagickColor("red",&image->background_color,&image->exception);
SetImageBackgroundColor(image);
Crie uma imagem a partir de uma imagem JPEG em disco:
(void) strcpy(image_info->filename,"image.jpg"):
image=ReadImage(image_info,exception);
if (image == (Image *) NULL)
{ /* an exception was thrown */ }
Crie uma imagem a partir de uma imagem em memória:
image=BlobToImage(blob_info,blob,extent,exception);
if (image == (Image *) NULL)
{ /* an exception was thrown */ }
Em nossa discussão sobre o cache de pixels, usamos a API MagickCore para ilustrar nossos pontos; no entanto, os princípios são os mesmos para as outras interfaces de programa do ImageMagick.
Quando o cache de pixels é inicializado, os pixels são escalonados da profundidade de bits da qual se originaram para a exigida pelo cache de pixels. Por exemplo, uma imagem PBM monocromática de 1 canal e 1 bit é escalonada para uma imagem cinza de 8 bits, se você estiver usando a versão Q8 do ImageMagick, e para RGBA de 16 bits na versão Q16. Você pode determinar qual versão possui com a opção -version:
$ identify -version
Version: ImageMagick 7.1.2-25 2026-06-04 Q16-HDRI https://imagemagick.org
Como você pode ver, a conveniência do cache de pixels às vezes vem com um custo em armazenamento (por exemplo, armazenar uma imagem monocromática de 1 bit como 16 bits é um desperdício) e velocidade (isto é, armazenar a imagem inteira em memória é geralmente mais lento do que acessar uma scanline de pixels por vez). Na maioria dos casos, os benefícios do cache de pixels normalmente superam quaisquer desvantagens.
Acessar o cache de pixels
Uma vez que o cache de pixels esteja associado a uma imagem, normalmente você quer obter, atualizar ou colocar pixels nele. Chamamos os pixels dentro da região da imagem de pixels autênticos e os de fora da região de pixels virtuais. Use estes métodos para acessar os pixels no cache:
- GetVirtualPixels(): obtém pixels que você não pretende modificar ou pixels que estão fora da região da imagem (por exemplo, o pixel @ -1,-3)
- GetAuthenticPixels(): obtém pixels que você pretende modificar
- QueueAuthenticPixels(): enfileira pixels que você pretende definir
- SyncAuthenticPixels(): atualiza o cache de pixels com quaisquer pixels modificados
Aqui está um trecho típico de código MagickCore para manipular pixels no cache de pixels. Em nosso exemplo, copiamos pixels da imagem de entrada para a imagem de saída e diminuímos a intensidade em 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 */ }
Quando primeiro criamos a imagem de destino clonando a imagem de origem, os pixels do cache de pixels não são copiados. Eles só são copiados quando você sinaliza sua intenção de modificar ou definir o cache de pixels chamando GetAuthenticPixels() ou QueueAuthenticPixels(). Use QueueAuthenticPixels() se quiser definir novos valores de pixel em vez de atualizar os existentes. Você poderia usar GetAuthenticPixels() para definir valores de pixel, mas é ligeiramente mais eficiente usar QueueAuthenticPixels() em vez disso. Por fim, use SyncAuthenticPixels() para garantir que quaisquer pixels atualizados sejam enviados ao cache de pixels.
Você pode associar conteúdo arbitrário a cada pixel, chamado de conteúdo meta. Use GetVirtualMetacontent() (para ler o conteúdo) ou GetAuthenticMetacontent() (para atualizar o conteúdo) para obter acesso a esse conteúdo. Por exemplo, para imprimir o metaconteúdo, use:
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 */
O gerenciador do cache de pixels decide se concede a você acesso direto ou indireto aos pixels da imagem. Em alguns casos, os pixels são preparados em um buffer intermediário — e é por isso que você deve chamar SyncAuthenticPixels() para garantir que esse buffer seja enviado ao cache de pixels e assegurar que os pixels correspondentes no cache sejam atualizados. Por essa razão, recomendamos que você leia ou atualize apenas uma scanline ou algumas scanlines de pixels por vez. No entanto, você pode obter qualquer região retangular de pixels que quiser. GetAuthenticPixels() exige que a região que você solicita esteja dentro dos limites da área da imagem. Para uma imagem de 640 por 480, você pode obter uma scanline de 640 pixels na linha 479, mas se pedir uma scanline na linha 480, uma exceção é retornada (as linhas são numeradas a partir de 0). GetVirtualPixels() não tem essa restrição. Por exemplo,
p=GetVirtualPixels(source,-3,-3,source->columns+3,6,exception);
fornece os pixels que você pediu sem reclamar, mesmo que alguns não estejam dentro dos confins da região da imagem.
Pixels virtuais
Existe uma infinidade de algoritmos de processamento de imagem que exigem uma vizinhança de pixels ao redor de um pixel de interesse. O algoritmo normalmente inclui uma ressalva sobre como tratar os pixels ao redor dos limites da imagem, conhecidos como pixels de borda. Com os pixels virtuais, você não precisa se preocupar com o processamento especial de bordas, exceto escolher qual método de pixel virtual é mais apropriado para o seu algoritmo.
O acesso aos pixels virtuais é controlado pelo método SetImageVirtualPixelMethod() da API MagickCore ou pela opção -virtual-pixel da linha de comando. Os métodos incluem:
| background | a área ao redor da imagem é a cor de fundo |
|---|---|
| black | a área ao redor da imagem é preta |
| checker-tile | quadrados alternados com a imagem e a cor de fundo |
| dither | padrão pontilhado 32x32 não aleatório |
| edge | estende o pixel de borda em direção ao infinito (padrão) |
| gray | a área ao redor da imagem é cinza |
| horizontal-tile | ladrilha a imagem horizontalmente, com a cor de fundo acima/abaixo |
| horizontal-tile-edge | ladrilha a imagem horizontalmente e replica os pixels da borda lateral |
| mirror | ladrilha a imagem em espelho |
| random | escolhe um pixel aleatório da imagem |
| tile | ladrilha a imagem |
| transparent | a área ao redor da imagem é negritude transparente |
| vertical-tile | ladrilha a imagem verticalmente, os lados são a cor de fundo |
| vertical-tile-edge | ladrilha a imagem verticalmente e replica os pixels da borda lateral |
| white | a área ao redor da imagem é branca |
Armazenamento do cache e requisitos de recursos
Lembre-se de que este design simples e elegante do cache de pixels do ImageMagick tem um custo em termos de armazenamento e velocidade de processamento. Os requisitos de armazenamento do cache de pixels escalam com a área da imagem e com a profundidade de bits dos componentes de pixel. Por exemplo, se temos uma imagem de 640 por 480 e estamos usando a versão não-HDRI Q16 do ImageMagick, o cache de pixels consome largura da imagem * altura * profundidade de bits / 8 * canais bytes, ou aproximadamente 2,3 mebibytes (isto é, 640 * 480 * 2 * 4). Nada mal, mas e se a sua imagem for de 25000 por 25000 pixels? O cache de pixels exige aproximadamente 4,7 gibibytes de armazenamento. Ai. O ImageMagick lida com os possíveis requisitos enormes de armazenamento ao colocar imagens grandes em cache no disco em vez de na memória. Normalmente, o cache de pixels é armazenado em memória usando memória heap. Se a memória heap se esgotar, criamos o cache de pixels em disco e tentamos mapeá-lo em memória. Se a memória de mapeamento de memória se esgotar, simplesmente usamos E/S de disco padrão. O armazenamento em disco é abundante e barato, mas também é muito lento — até 1000 vezes mais lento do que acessar pixels em memória. Podemos obter algumas melhorias de velocidade, até 5 vezes, se mapearmos em memória o cache baseado em disco. Essas decisões sobre armazenamento são tomadas automagicamente pelo gerenciador do cache de pixels negociando com o sistema operacional. No entanto, você pode influenciar como o gerenciador do cache de pixels aloca o cache de pixels com limites de recursos do cache. Os limites incluem:
| width | largura máxima de uma imagem. Exceda este limite e uma exceção é lançada e a operação é descontinuada. |
|---|---|
| height | altura máxima de uma imagem. Exceda este limite e uma exceção é lançada e a operação é descontinuada. |
| area | área máxima em bytes de qualquer imagem que pode residir na memória do cache de pixels. Se este limite for excedido, a imagem é automagicamente colocada em cache no disco e, opcionalmente, mapeada em memória. |
| memory | quantidade máxima de memória em bytes a alocar para o cache de pixels a partir do heap. |
| map | quantidade máxima de mapeamento de memória em bytes a alocar para o cache de pixels. |
| disk | quantidade máxima de espaço em disco em bytes permitida para uso pelo cache de pixels. Se este limite for excedido, uma exceção fatal é lançada e todo o processamento para. |
| files | número máximo de arquivos de cache de pixels abertos. Quando este limite é excedido, quaisquer pixels subsequentes colocados em cache no disco são fechados e reabertos sob demanda. Esse comportamento permite que um grande número de imagens seja acessado simultaneamente em disco sem penalidade de velocidade, reduzindo o número de chamadas de sistema de abertura/fechamento do cache de pixels. |
| thread | número máximo de threads que podem ser executadas em paralelo. O seu sistema pode escolher um número de threads menor que este valor. O ImageMagick escolhe um número ótimo de threads por padrão, que normalmente é o número de núcleos do seu host. Defina este valor como 1 e todas as regiões paralelas serão executadas por uma única thread. |
| time | número máximo de segundos que o processo pode executar. Exceda este limite e uma exceção é lançada e o processamento para. |
Note que esses limites dizem respeito ao cache de pixels do ImageMagick. Certos algoritmos dentro do ImageMagick não respeitam esses limites, nem nenhuma das bibliotecas delegadas externas (por exemplo, JPEG, TIFF, etc.).
Para determinar a configuração atual desses limites, use este comando:
-> 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
Você pode definir esses limites como uma política de segurança (veja policy.xml), com uma variável de ambiente, com a opção de linha de comando -limit, ou com o método SetMagickResourceLimit() da API MagickCore. Como exemplo, nossa interface web online para o ImageMagick, o MagickStudio, inclui estes limites de política para ajudar a prevenir uma negação de serviço:
<?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>
Como processamos várias sessões simultâneas, não queremos que uma única sessão consuma toda a memória disponível. Com esta política, imagens grandes são colocadas em cache no disco. Se a imagem for grande demais e exceder o limite de disco do cache de pixels, o programa encerra. Além disso, impomos um limite de tempo para evitar tarefas de processamento descontroladas. Se qualquer imagem tiver uma largura ou altura que exceda 8192 pixels, uma exceção é lançada e o processamento para. A partir do ImageMagick 7.0.1-8, você pode impedir o uso de qualquer delegado ou de todos os delegados (defina o pattern como "*"). Note que, antes desta versão, deve-se usar um domínio "coder" para impedir o uso de delegados (por exemplo, domain="coder" rights="none" pattern="HTTPS"). A política também impede leituras indiretas. Se quiser, por exemplo, ler texto de um arquivo (por exemplo, caption:@myCaption.txt), você precisará remover esta política.
Note que os limites de cache são globais a cada invocação do ImageMagick, ou seja, se você criar várias imagens, os requisitos de recursos combinados são comparados com o limite para determinar a disposição do armazenamento do cache de pixels.
Para determinar qual tipo e quantos recursos são consumidos pelo cache de pixels, adicione a opção -debug cache à linha de comando:
$ 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]
Este comando utiliza um cache de pixels em memória. O logo consumiu 4.688MiB e, depois de ser afiado, 3.516MiB.
Cache de pixels distribuído
Um cache de pixels distribuído é uma extensão do cache de pixels tradicional disponível em um único host. O cache de pixels distribuído pode abranger vários servidores para que possa crescer em tamanho e em capacidade transacional, a fim de suportar imagens muito grandes. Inicie o servidor de cache de pixels em uma ou mais máquinas. Quando você lê ou opera sobre uma imagem e os recursos locais do cache de pixels se esgotam, o ImageMagick contata um ou mais desses servidores de pixels remotos para armazenar ou recuperar pixels. O cache de pixels distribuído depende da largura de banda da rede para transferir pixels de e para o servidor remoto. Por isso, ele provavelmente será significativamente mais lento do que um cache de pixels que utiliza armazenamento local (por exemplo, memória, disco, 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
Visões de cache
GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() e SyncAuthenticPixels(), da API MagickCore, só podem lidar com uma área de cache de pixels por imagem por vez. Suponha que você queira acessar a primeira e a última scanline da mesma imagem ao mesmo tempo? A solução é usar uma visão de cache. Uma visão de cache permite acessar simultaneamente quantas áreas do cache de pixels você precisar. Os métodos da visão de cache são análogos aos métodos anteriores, exceto que você deve primeiro abrir uma visão e fechá-la quando terminar de usá-la. Aqui está um trecho de código MagickCore que nos permite acessar a primeira e a última linha de pixels da imagem simultaneamente:
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 */ }
Formato Magick Pixel Cache
Lembre-se de que cada formato de imagem é decodificado pelo ImageMagick e os pixels são depositados no cache de pixels. Se você escreve uma imagem, os pixels são lidos do cache de pixels e codificados conforme exigido pelo formato que você está escrevendo (por exemplo, GIF, PNG, etc.). O formato Magick Pixel Cache (MPC) foi projetado para eliminar a sobrecarga de decodificar e codificar pixels de e para um formato de imagem. O MPC escreve dois arquivos. Um, com a extensão .mpc, retém todas as propriedades associadas à imagem ou à sequência de imagens (por exemplo, largura, altura, espaço de cor, etc.) e o segundo, com a extensão .cache, é o cache de pixels no formato raw nativo. Ao ler um arquivo de imagem MPC, o ImageMagick lê as propriedades da imagem e mapeia em memória o cache de pixels em disco, eliminando a necessidade de decodificar os pixels da imagem. O custo está no espaço em disco. O MPC é geralmente maior em tamanho de arquivo do que a maioria dos outros formatos de imagem.
O uso mais eficiente dos arquivos de imagem MPC é um padrão de escrever-uma-vez, ler-muitas-vezes. Por exemplo, seu fluxo de trabalho requer a extração de blocos aleatórios de pixels da imagem de origem. Em vez de reler e possivelmente descomprimir a imagem de origem a cada vez, usamos o MPC e mapeamos a imagem diretamente para a memória.
Práticas recomendadas para o cache de pixels
Embora você possa solicitar qualquer pixel do cache de pixels, qualquer bloco de pixels, qualquer scanline, várias scanlines, qualquer linha ou várias linhas com os métodos GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels, GetCacheViewVirtualPixels(), GetCacheViewAuthenticPixels() e QueueCacheViewAuthenticPixels(), o ImageMagick é otimizado para retornar alguns pixels ou algumas linhas de pixels por vez. Há otimizações adicionais se você solicitar uma única scanline ou algumas scanlines por vez. Esses métodos também permitem acesso aleatório ao cache de pixels; no entanto, o ImageMagick é otimizado para acesso sequencial. Embora você possa acessar scanlines de pixels sequencialmente da última linha da imagem para a primeira, você pode obter um ganho de desempenho se acessar as scanlines da primeira linha da imagem para a última, em ordem sequencial.
Você pode obter, modificar ou definir pixels em ordem de linha ou de coluna. No entanto, é mais eficiente acessar os pixels por linha do que por coluna.
Se você atualizar os pixels retornados por GetAuthenticPixels() ou GetCacheViewAuthenticPixels(), não se esqueça de chamar SyncAuthenticPixels() ou SyncCacheViewAuthenticPixels(), respectivamente, para garantir que suas alterações sejam sincronizadas com o cache de pixels.
Use QueueAuthenticPixels() ou QueueCacheViewAuthenticPixels() se estiver definindo um valor de pixel inicial. O método GetAuthenticPixels() ou GetCacheViewAuthenticPixels() lê pixels do cache e, se você estiver definindo um valor de pixel inicial, essa leitura é desnecessária. Não se esqueça de chamar SyncAuthenticPixels() ou SyncCacheViewAuthenticPixels(), respectivamente, para enviar quaisquer alterações de pixel ao cache de pixels.
GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() e SyncAuthenticPixels() são ligeiramente mais eficientes do que seus equivalentes de visão de cache. No entanto, as visões de cache são necessárias se você precisar de acesso a mais de uma região da imagem simultaneamente ou se mais de uma thread de execução estiver acessando a imagem.
Você pode solicitar pixels fora dos limites da imagem com GetVirtualPixels() ou GetCacheViewVirtualPixels(); no entanto, é mais eficiente solicitar pixels dentro dos confins da região da imagem.
Embora você possa forçar o cache de pixels para o disco usando limites de recursos apropriados, o acesso ao disco pode ser até 1000 vezes mais lento do que o acesso à memória. Para um acesso rápido e eficiente ao cache de pixels, tente manter o cache de pixels na memória heap.
A versão Q16 do ImageMagick permite ler e escrever imagens de 16 bits sem escalonamento, mas o cache de pixels consome o dobro de recursos da versão Q8. Se o seu sistema tem recursos de memória ou disco limitados, considere a versão Q8 do ImageMagick. Além disso, a versão Q8 normalmente executa mais rápido do que a versão Q16.
A grande maioria dos formatos e algoritmos de imagem se restringe a uma faixa fixa de valores de pixel de 0 a algum valor máximo; por exemplo, a versão Q16 do ImageMagick permite intensidades de 0 a 65535. A imagem de alta faixa dinâmica (HDRI), no entanto, permite uma faixa dinâmica de exposições muito maior (isto é, uma grande diferença entre áreas claras e escuras) do que as técnicas de imagem digital padrão. O HDRI representa com precisão a ampla faixa de níveis de intensidade encontrados em cenas reais, que vão da luz solar direta mais brilhante às sombras mais profundas e escuras. Habilite o HDRI no momento da compilação do ImageMagick para lidar com imagens de alta faixa dinâmica, mas tenha em mente que cada componente de pixel é um valor de ponto flutuante de 32 bits. Além disso, os valores de pixel não são limitados por padrão, então alguns algoritmos podem ter resultados inesperados devido a valores de pixel fora da faixa, em comparação com a versão não-HDRI.
Se você estiver lidando com imagens grandes, certifique-se de que o cache de pixels seja escrito em uma área de disco com bastante espaço livre. No Linux, isso normalmente é /tmp e, no Windows, c:/temp. Você pode dizer ao ImageMagick para escrever o cache de pixels em um local alternativo e economizar memória com estas opções:
magick -limit memory 2GB -limit map 4GB -define registry:temporary-path=/data/tmp ...
Defina os limites globais de recursos para o seu ambiente no arquivo de configuração policy.xml.
Se você planeja processar a mesma imagem muitas vezes, considere o formato MPC. Ler uma imagem MPC tem sobrecarga quase nula porque ela está no formato nativo do cache de pixels, eliminando a necessidade de decodificar os pixels da imagem. Aqui está um exemplo:
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
O MPC é ideal para sites. Ele reduz a sobrecarga de ler e escrever uma imagem. Nós o usamos exclusivamente em nosso estúdio de imagens online.
Propriedades e perfis de imagem
As imagens têm metadados associados a elas na forma de propriedades (por exemplo, largura, altura, descrição, etc.) e perfis (por exemplo, EXIF, IPTC, gerenciamento de cores). O ImageMagick fornece métodos convenientes para obter, definir ou atualizar propriedades de imagem e para obter, definir, atualizar ou aplicar perfis. Algumas das propriedades de imagem mais populares estão associadas à estrutura Image na API MagickCore. Por exemplo:
(void) printf("image width: %lu, height: %lu\n",image->columns,image->rows);
Para a grande maioria das propriedades de imagem, como um comentário ou descrição de imagem, usamos os métodos GetImageProperty() e SetImageProperty(). Aqui definimos uma propriedade e a obtemos de volta logo em seguida:
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);
O ImageMagick suporta artifacts com os métodos GetImageArtifact() e SetImageArtifact(). Os artifacts são propriedades furtivas que não são exportadas para os formatos de imagem (por exemplo, PNG).
Os perfis de imagem são tratados com os métodos GetImageProfile(), SetImageProfile() e ProfileImage(). Aqui definimos um perfil e o obtemos de volta logo em seguida:
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);
Imagens multiespectrais
O ImageMagick suporta imagens multiespectrais, nas quais todos os canais têm as mesmas dimensões e o mesmo número de pixels que a imagem original. No entanto, nem todos os formatos de imagem suportam imagens multiespectrais. PSD, TIFF, MIFF, MPC e FTXT têm suporte completo a imagens multiespectrais de até 31 bandas, 21 delas meta canais. Note que, se você compilar o ImageMagick com a opção --enable-64bit-channel-masks do script configure, poderá processar imagens multiespectrais de 62 bandas com até 52 meta canais.
Se você tem um caso de uso que atualmente não é suportado por um formato de imagem, publique-o no fórum de discussão. Há uma boa chance de podermos suportar o seu caso de uso em uma futura versão do ImageMagick.
Streaming de pixels
O ImageMagick oferece streaming de pixels à medida que eles são lidos de ou escritos em uma imagem. Isso tem várias vantagens sobre o cache de pixels. O tempo e os recursos consumidos pelo cache de pixels escalam com a área de uma imagem, enquanto os recursos do stream de pixels escalam com a largura de uma imagem. A desvantagem é que os pixels devem ser consumidos à medida que são transmitidos, portanto não há persistência.
Use ReadStream() ou WriteStream() com um método de callback apropriado em seu programa MagickCore para consumir os pixels à medida que são transmitidos. Aqui está um exemplo abreviado do uso 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);
Também fornecemos uma ferramenta leve, stream, para transmitir um ou mais componentes de pixel da imagem ou de uma parte da imagem para o formato de armazenamento de sua escolha. Ela escreve os componentes de pixel à medida que são lidos da imagem de entrada, uma linha por vez, tornando o stream desejável ao trabalhar com imagens grandes ou quando você precisa de componentes de pixel raw. A maioria dos formatos de imagem transmite os pixels (vermelho, verde e azul) da esquerda para a direita e de cima para baixo. No entanto, alguns formatos não suportam essa ordenação comum (por exemplo, o formato PSD).
Suporte a imagens grandes
O ImageMagick tem a capacidade de lidar com tamanhos de imagem que vão de mega a tera-pixels, abrangendo operações de leitura, processamento e escrita. Em teoria, as dimensões de imagem podem se estender até 31 milhões de linhas/colunas em um sistema operacional de 32 bits e até impressionantes 31 trilhões em um SO de 64 bits. No entanto, as dimensões realmente alcançáveis são substancialmente menores, dependendo dos recursos disponíveis no seu computador host. É essencial estar ciente de que certos formatos de imagem impõem limitações ao tamanho da imagem. Por exemplo, as imagens do Photoshop são limitadas a um máximo de 300.000 pixels de largura ou altura. Aqui redimensionamos uma imagem para um quarto de milhão de pixels ao quadrado:
magick logo: -resize 250000x250000 logo.miff
Para imagens grandes, os recursos de memória provavelmente se esgotarão e o ImageMagick criará, em vez disso, um cache de pixels em disco. Certifique-se de ter bastante espaço temporário em disco. Se a sua partição de disco temporária padrão for pequena demais, diga ao ImageMagick para usar outra partição com bastante espaço livre. Por exemplo:
magick -define registry:temporary-path=/data/tmp logo: \
-resize 250000x250000 logo.miff
Para garantir que imagens grandes não consumam toda a memória do seu sistema, force os pixels da imagem para o disco mapeado em memória com limites de recursos:
magick -define registry:temporary-path=/data/tmp -limit memory 16mb \
logo: -resize 250000x250000 logo.miff
Aqui forçamos todos os pixels da imagem para o disco:
magick -define registry:temporary-path=/data/tmp -limit area 0 \
logo: -resize 250000x250000 logo.miff
Colocar pixels em cache no disco é cerca de 1000 vezes mais lento do que em memória. Espere tempos de execução longos ao processar imagens grandes em disco com o ImageMagick. Você pode monitorar o progresso com este comando:
magick -monitor -limit memory 2GiB -limit map 4GiB -define registry:temporary-path=/data/tmp \
logo: -resize 250000x250000 logo.miff
Para imagens realmente grandes, ou se houver recursos limitados no seu host, você pode utilizar um cache de pixels distribuído em um ou mais hosts remotos:
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
Devido à latência da rede, espere uma desaceleração substancial no processamento do seu fluxo de trabalho.
Threads de execução
Muitos dos algoritmos internos do ImageMagick são paralelizados em threads para aproveitar os ganhos de velocidade oferecidos pelos chips de processador multinúcleo. No entanto, você pode usar os algoritmos do ImageMagick em suas threads de execução, com exceção dos métodos do cache de pixels GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels() ou SyncAuthenticPixels() do MagickCore. Esses métodos são destinados a uma única thread de execução, com exceção de uma seção paralela OpenMP. Para acessar o cache de pixels com mais de uma thread de execução, use uma visão de cache. Fazemos isso para o método CompositeImage(), por exemplo. Suponha que queiramos compor uma única imagem de origem sobre uma imagem de destino diferente em cada thread de execução. Se usarmos GetVirtualPixels(), os resultados são imprevisíveis porque várias threads provavelmente estariam pedindo áreas diferentes do cache de pixels simultaneamente. Em vez disso, usamos GetCacheViewVirtualPixels(), que cria uma visão única para cada thread de execução, garantindo que nosso programa se comporte corretamente independentemente de quantas threads sejam invocadas. As outras interfaces de programa, como a API MagickWand, são completamente thread-safe, portanto não há precauções especiais para threads de execução.
Aqui está um trecho de código MagickCore que aproveita as threads de execução com o paradigma de programação 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");
Este trecho de código converte um bitmap do Windows sem compressão em uma imagem 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;
}
Se você chama a API do ImageMagick a partir de seu aplicativo habilitado para OpenMP e pretende aumentar dinamicamente o número de threads disponíveis em regiões paralelas subsequentes, certifique-se de realizar o aumento antes de chamar a API; caso contrário, o ImageMagick pode falhar.
O MagickWand suporta visões de wand. Uma visão itera sobre toda a imagem, ou uma parte dela, em paralelo e, para cada linha de pixels, invoca um método de callback que você fornece. Isso limita a maior parte de sua atividade de programação paralela a apenas esse único módulo. Há métodos semelhantes no MagickCore. Para um exemplo, veja o mesmo algoritmo de contraste sigmoidal implementado tanto no MagickWand quanto no MagickCore.
Na maioria das circunstâncias, o número padrão de threads é definido como o número de núcleos de processador no seu sistema para um desempenho ótimo. No entanto, se o seu sistema tiver hyperthreading ou se você estiver executando em um host virtual e apenas um subconjunto dos processadores estiver disponível para a sua instância de servidor, você pode obter um aumento de desempenho ao definir a política de thread ou a variável de ambiente MAGICK_THREAD_LIMIT. Por exemplo, seu host virtual tem 8 processadores, mas apenas 2 estão atribuídos à sua instância de servidor. O padrão de 8 threads pode causar problemas graves de desempenho. Uma solução é limitar o número de threads aos processadores disponíveis no seu arquivo de configuração policy.xml:
<policy domain="resource" name="thread" value="2"/>
Ou suponha que seu computador de 12 núcleos com hyperthreading use por padrão 24 threads. Defina a variável de ambiente MAGICK_THREAD_LIMIT e você provavelmente obterá um desempenho melhor:
export MAGICK_THREAD_LIMIT=12
O comitê do OpenMP não definiu o comportamento de misturar OpenMP com outros modelos de threading, como threads Posix. No entanto, usando versões modernas do Linux, OpenMP e threads Posix parecem interoperar sem reclamações. Se você quiser usar threads Posix a partir de um módulo de programa que chama uma das interfaces de programação de aplicativos do ImageMagick (por exemplo, MagickCore, MagickWand, Magick++, etc.) no Mac OS X ou em uma versão mais antiga do Linux, talvez precise desabilitar o suporte a OpenMP dentro do ImageMagick. Adicione a opção --disable-openmp à linha de comando do script configure e recompile e reinstale o ImageMagick.
Você pode aumentar ainda mais o desempenho reduzindo a contenção de locks com a biblioteca de alocação de memória tcmalloc. Para habilitá-la, adicione --with-tcmalloc à linha de comando do configure ao compilar o ImageMagick.
Desempenho de threading
Pode ser difícil prever o comportamento em um ambiente paralelo. O desempenho pode depender de vários fatores, incluindo o compilador, a versão da biblioteca OpenMP, o tipo de processador, o número de núcleos, a quantidade de memória, se o hyperthreading está habilitado, a combinação de aplicativos que estão sendo executados simultaneamente com o ImageMagick, ou o algoritmo de processamento de imagem específico que você utiliza. A única maneira de ter certeza de um desempenho ótimo, em termos do número de threads, é fazer benchmark. O ImageMagick inclui threading progressivo ao fazer benchmark de um comando e retorna o tempo decorrido e a eficiência para uma ou mais threads. Isso pode ajudá-lo a identificar quantas threads são as mais eficientes no seu ambiente. Para este benchmark, afiamos uma imagem de 1920x1080 de um modelo 10 vezes com 1 a 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
O ponto ideal para este exemplo é 6 threads. Isso faz sentido, já que há 6 núcleos físicos. Os outros 6 são hyperthreads. Parece que a afiação não se beneficia do hyperthreading.
Em certos casos, pode ser ótimo definir o número de threads como 1 ou desabilitar o OpenMP completamente com a variável de ambiente MAGICK_THREAD_LIMIT, a opção de linha de comando -limit, ou o arquivo de configuração policy.xml.
Habilitar o controle dinâmico de carga para desempenho otimizado
Outra opção eficaz é habilitar o controle dinâmico de carga (dynamic throttling) no ImageMagick. Quando este recurso é ativado, o ImageMagick introduz automaticamente um pequeno atraso entre o processamento das scanlines da imagem com base na carga média atual do seu sistema. Isso ajuda a evitar o esgotamento de recursos e garante um desempenho mais suave, especialmente sob cargas de trabalho pesadas.
Para habilitar o controle dinâmico de carga, adicione a seguinte configuração de política:
<policy domain="resource" name="dynamic-throttle" value="true"/>
Processamento distribuído heterogêneo
O ImageMagick inclui suporte a processamento distribuído heterogêneo com o framework OpenCL. Os kernels OpenCL dentro do ImageMagick permitem que os algoritmos de processamento de imagem sejam executados em plataformas heterogêneas compostas por CPUs, GPUs e outros processadores. Dependendo da sua plataforma, os ganhos de velocidade podem ser uma ordem de magnitude mais rápidos do que a CPU única tradicional.
Primeiro, verifique se a sua versão do ImageMagick inclui suporte ao recurso OpenCL:
magick identify -version
Features: DPC Cipher Modules OpenCL OpenMP(4.5)
Em caso afirmativo, execute este comando para obter um ganho de velocidade significativo na convolução de imagem:
magick image.png -convolve '-1, -1, -1, -1, 9, -1, -1, -1, -1' convolve.png
Se um acelerador não estiver disponível ou se o acelerador falhar em responder, o ImageMagick reverte para o algoritmo de convolução não acelerado.
Aqui está um exemplo de kernel OpenCL que convolve uma imagem:
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);
};
Veja MagickCore/accelerate.c para uma implementação completa da convolução de imagem com um kernel OpenCL.
Note que, no Windows, você pode ter um problema com o TDR (Timeout Detection and Recovery de GPUs). Seu propósito é detectar tarefas descontroladas que travam a GPU usando um limite de tempo de execução. Para algumas GPUs mais antigas e de baixo desempenho executando os filtros OpenCL no ImageMagick, tempos de execução mais longos podem disparar o mecanismo TDR e interromper o filtro de imagem da GPU. Quando isso acontece, o ImageMagick automaticamente recorre ao caminho de código da CPU e retorna os resultados esperados. Para evitar a interrupção, aumente a chave de registro TdrDelay.
Coders de imagem personalizados
Um coder de imagem (isto é, codificador / decodificador) é responsável por registrar, opcionalmente classificar, opcionalmente ler, opcionalmente escrever e desregistrar um formato de imagem (por exemplo, PNG, GIF, JPEG, etc.). Registrar um coder de imagem alerta o ImageMagick de que um formato específico está disponível para leitura ou escrita. Já desregistrar informa ao ImageMagick que o formato não está mais disponível. O método de classificação examina os primeiros bytes de uma imagem e determina se a imagem está no formato esperado. O leitor define o tamanho, o espaço de cor e outras propriedades da imagem e carrega o cache de pixels com os pixels. O leitor retorna uma única imagem ou uma sequência de imagens (se o formato suportar múltiplas imagens por arquivo) ou, se ocorrer um erro, uma exceção e uma imagem nula. O escritor faz o inverso. Ele toma as propriedades da imagem, descarrega o cache de pixels e as escreve conforme exigido pelo formato de imagem.
Aqui está uma listagem de um exemplo de coder personalizado. Ele lê e escreve imagens no formato de imagem MGK, que é simplesmente um ID seguido pela largura e altura da imagem, seguidos pelos valores 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);
}
Para invocar o coder personalizado a partir da linha de comando, use estes comandos:
magick logo: logo.mgk
display logo.mgk
Fornecemos o Magick Coder Kit para ajudá-lo a começar a escrever seu próprio coder personalizado.
Antes de compilar, defina a variável de ambiente PKG_CONFIG_PATH se o ImageMagick não estiver no caminho padrão do seu sistema:
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
Filtros de imagem personalizados
O ImageMagick fornece um mecanismo conveniente para adicionar seus próprios algoritmos personalizados de processamento de imagem. Chamamos esses de filtros de imagem e eles são invocados a partir da linha de comando com a opção -process ou pelo método ExecuteModuleProcess() da API MagickCore.
Aqui está uma listagem de um exemplo de filtro de imagem personalizado. Ele calcula algumas estatísticas, como a média e o desvio-padrão do brilho e da saturação dos 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);
}
Para invocar o filtro personalizado a partir da linha de comando, use este comando:
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
Fornecemos o Magick Filter Kit para ajudá-lo a começar a escrever seu próprio filtro de imagem personalizado.
Antes de compilar, defina a variável de ambiente PKG_CONFIG_PATH se o ImageMagick não estiver no caminho padrão do seu sistema:
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig