Cache Piksel • Properti dan Profil Gambar • Gambar Multispektral • Dukungan Gambar Besar • Streaming Piksel • Thread Eksekusi • Mengaktifkan Throttling Dinamis untuk Performa yang Dioptimalkan • Pemrosesan Terdistribusi Heterogen • Coder Gambar Kustom • Filter Gambar Kustom
Penduduk Oz cukup puas dengan pelindung mereka, Sang Penyihir yang mahakuasa. Mereka menerima kebijaksanaan dan kebaikan hatinya tanpa pernah mempertanyakan siapa, mengapa, dan dari mana kekuatannya berasal. Seperti penduduk Oz, jika Anda merasa cukup nyaman mengetahui bahwa ImageMagick dapat membantu Anda mengonversi, mengedit, atau menyusun gambar tanpa perlu tahu apa yang terjadi di balik layar, Anda bisa melewati bagian ini. Namun, jika Anda ingin tahu lebih banyak tentang perangkat lunak dan algoritma di balik ImageMagick, teruskan membaca. Untuk benar-benar memanfaatkan pembahasan ini, Anda perlu memahami tata nama gambar dan terbiasa dengan pemrograman komputer.
Gambaran arsitektur
Gambar biasanya terdiri atas wilayah piksel berbentuk persegi panjang beserta metadata. Untuk mengonversi, mengedit, atau menyusun gambar secara efisien, kita memerlukan akses yang mudah ke piksel mana pun di dalam wilayah tersebut (dan kadang di luar wilayah itu). Dalam kasus urutan gambar, kita memerlukan akses ke piksel mana pun dari wilayah mana pun pada gambar mana pun dalam urutan tersebut. Namun, ada ratusan format gambar seperti JPEG, TIFF, PNG, GIF, dan lainnya, yang membuat akses piksel sesuai permintaan menjadi sulit. Di antara format-format ini kita menemukan perbedaan pada:
- ruang warna (mis. sRGB, RGB linear, GRAY linear, CMYK, YUV, Lab, dll.)
- kedalaman bit (mis. 1, 4, 8, 12, 16, dll.)
- format penyimpanan (mis. unsigned, signed, float, double, dll.)
- kompresi (mis. tanpa kompresi, RLE, Zip, BZip, dll.)
- orientasi (yaitu atas-ke-bawah, kanan-ke-kiri, dll.),
- tata letak (mis. raw, diselingi dengan opcode, dll.)
Selain itu, sebagian piksel gambar mungkin memerlukan atenuasi, sebagian format mengizinkan lebih dari satu frame, dan sebagian format berisi grafik vektor yang harus dirasterisasi terlebih dahulu (dikonversi dari vektor menjadi piksel).
Implementasi yang efisien dari algoritma pemrosesan gambar mungkin mengharuskan kita mendapatkan atau menetapkan:
- satu piksel dalam satu waktu (mis. piksel di lokasi 10,3)
- satu scanline (mis. semua piksel dari baris 4)
- beberapa scanline sekaligus (mis. baris piksel 4-7)
- satu kolom atau beberapa kolom piksel (mis. semua piksel dari kolom 11)
- wilayah piksel sembarang dari gambar (mis. piksel yang ditentukan dari 10,7 hingga 10,19)
- piksel dalam urutan acak (mis. piksel di 14,15 dan 640,480)
- piksel dari dua gambar berbeda (mis. piksel di 5,1 dari gambar 1 dan piksel di 5,1 dari gambar 2)
- piksel di luar batas gambar (mis. piksel di -1,-3)
- komponen piksel yang unsigned (65311) atau dalam representasi floating-point (mis. 0.17836)
- piksel high-dynamic-range yang dapat mencakup nilai negatif (mis. -0.0072973525628) maupun nilai yang melebihi kedalaman quantum (mis. 65931)
- satu atau beberapa piksel secara bersamaan di thread eksekusi yang berbeda
- semua piksel di memori untuk memanfaatkan percepatan yang ditawarkan dengan mengeksekusi secara serentak di berbagai platform heterogen yang terdiri atas CPU, GPU, dan prosesor lain
- trait yang terkait dengan setiap kanal untuk menentukan apakah kanal piksel disalin, diperbarui, atau dicampur (blend)
- mask yang menentukan piksel mana yang boleh diperbarui
- kanal tambahan yang bermanfaat bagi pengguna tetapi selain itu tidak disentuh oleh algoritma pemrosesan gambar ImageMagick
Mengingat beragamnya format gambar dan kebutuhan pemrosesan gambar, kami mengimplementasikan cache piksel ImageMagick untuk menyediakan akses sekuensial maupun paralel yang mudah ke piksel mana pun sesuai permintaan, di mana pun di dalam wilayah gambar (yaitu piksel autentik) dan dari gambar mana pun dalam suatu urutan. Selain itu, cache piksel mengizinkan akses ke piksel di luar batas yang ditentukan oleh gambar (yaitu piksel virtual).
Selain piksel, gambar memiliki banyak sekali properti dan profil gambar. Properti mencakup atribut yang sudah dikenal seperti lebar, tinggi, kedalaman, dan ruang warna. Gambar dapat memiliki properti opsional yang mungkin mencakup penulis gambar, komentar, tanggal pembuatan, dan lainnya. Sebagian gambar juga menyertakan profil untuk manajemen warna, atau profil informasi EXIF, IPTC, 8BIM, atau XMP. ImageMagick menyediakan opsi baris perintah dan metode pemrograman untuk mendapatkan, menetapkan, atau melihat properti atau profil gambar, atau menerapkan profil.
ImageMagick terdiri atas hampir setengah juta baris kode C dan secara opsional bergantung pada beberapa juta baris kode di pustaka (library) yang menjadi dependensinya (mis. pustaka JPEG, PNG, TIFF). Mengingat hal itu, orang mungkin mengira dokumen arsitekturnya akan sangat besar. Namun, sebagian besar pemrosesan gambar sekadar mengakses piksel dan metadatanya, dan implementasi kami yang sederhana, elegan, dan efisien membuat hal ini mudah bagi pengembang ImageMagick. Kami membahas implementasi cache piksel serta cara mendapatkan dan menetapkan properti dan profil gambar pada beberapa bagian berikutnya. Selanjutnya, kami membahas penggunaan ImageMagick di dalam thread eksekusi. Pada bagian-bagian terakhir, kami membahas coder gambar untuk membaca atau menulis format gambar tertentu, diikuti dengan sedikit penjelasan tentang membuat filter untuk mengakses atau memperbarui piksel sesuai kebutuhan kustom Anda.
Cache piksel
Cache piksel ImageMagick adalah tempat penyimpanan piksel gambar dengan hingga 64 kanal. Kanal-kanal disimpan secara berurutan (contiguous) pada kedalaman yang ditentukan saat ImageMagick dibangun (build). Kedalaman kanal adalah 8 bit per komponen piksel untuk versi Q8 ImageMagick, 16 bit per komponen piksel untuk versi Q16, dan 32 bit per komponen piksel untuk versi Q32. Secara default, komponen piksel adalah besaran high-dynamic-range floating-bit 32-bit. Kanal dapat menampung nilai apa pun, tetapi biasanya berisi intensitas merah, hijau, biru, dan alpha, atau intensitas cyan, magenta, kuning, hitam, dan alpha. Kanal dapat berisi indeks colormap untuk gambar colormapped atau kanal hitam untuk gambar CMYK. Penyimpanan cache piksel dapat berupa memori heap, memory-mapped yang didukung disk, atau di disk. Cache piksel bersifat reference-counted. Hanya properti cache yang disalin ketika cache digandakan (clone). Piksel cache baru disalin setelahnya, yaitu ketika Anda memberi sinyal niat untuk memperbarui salah satu piksel.
Membuat cache piksel
Cache piksel dikaitkan dengan gambar saat gambar dibuat, dan diinisialisasi ketika Anda mencoba mendapatkan atau menempatkan piksel. Berikut tiga metode umum untuk mengaitkan cache piksel dengan gambar:
Membuat kanvas gambar yang diinisialisasi ke warna latar belakang:
image=AllocateImage(image_info);
if (SetImageExtent(image,640,480) == MagickFalse)
{ /* an exception was thrown */ }
(void) QueryMagickColor("red",&image->background_color,&image->exception);
SetImageBackgroundColor(image);
Membuat gambar dari gambar JPEG di disk:
(void) strcpy(image_info->filename,"image.jpg"):
image=ReadImage(image_info,exception);
if (image == (Image *) NULL)
{ /* an exception was thrown */ }
Membuat gambar dari gambar berbasis memori:
image=BlobToImage(blob_info,blob,extent,exception);
if (image == (Image *) NULL)
{ /* an exception was thrown */ }
Dalam pembahasan cache piksel ini, kami menggunakan API MagickCore untuk mengilustrasikan poin-poin kami, tetapi prinsipnya sama untuk antarmuka program lain ke ImageMagick.
Saat cache piksel diinisialisasi, piksel diskalakan dari kedalaman bit aslinya ke kedalaman yang dibutuhkan oleh cache piksel. Misalnya, gambar PBM monokrom 1-kanal 1-bit diskalakan menjadi gambar gray 8-bit jika Anda menggunakan versi Q8 ImageMagick, dan RGBA 16-bit untuk versi Q16. Anda dapat menentukan versi yang Anda miliki dengan opsi -version:
$ identify -version
Version: ImageMagick 7.1.2-25 2026-06-04 Q16-HDRI https://imagemagick.org
Seperti yang Anda lihat, kemudahan cache piksel terkadang disertai trade-off dalam hal penyimpanan (mis. menyimpan gambar monokrom 1-bit sebagai 16-bit adalah pemborosan) dan kecepatan (yaitu menyimpan seluruh gambar di memori umumnya lebih lambat daripada mengakses satu scanline piksel dalam satu waktu). Pada sebagian besar kasus, manfaat cache piksel biasanya lebih besar daripada kekurangannya.
Mengakses cache piksel
Setelah cache piksel dikaitkan dengan gambar, Anda biasanya ingin mendapatkan, memperbarui, atau menempatkan piksel ke dalamnya. Kami menyebut piksel di dalam wilayah gambar sebagai piksel autentik dan piksel di luar wilayah sebagai piksel virtual. Gunakan metode berikut untuk mengakses piksel di dalam cache:
- GetVirtualPixels(): mendapatkan piksel yang tidak Anda maksudkan untuk diubah, atau piksel yang berada di luar wilayah gambar (mis. piksel @ -1,-3)
- GetAuthenticPixels(): mendapatkan piksel yang Anda maksudkan untuk diubah
- QueueAuthenticPixels(): mengantrekan piksel yang Anda maksudkan untuk ditetapkan
- SyncAuthenticPixels(): memperbarui cache piksel dengan piksel yang telah diubah
Berikut cuplikan kode MagickCore yang umum untuk memanipulasi piksel di dalam cache piksel. Pada contoh ini, kami menyalin piksel dari gambar input ke gambar output dan menurunkan intensitasnya sebesar 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 */ }
Saat pertama kali kita membuat gambar tujuan dengan meng-clone gambar sumber, piksel cache piksel tidak disalin. Piksel baru disalin ketika Anda memberi sinyal niat untuk mengubah atau menetapkan cache piksel dengan memanggil GetAuthenticPixels() atau QueueAuthenticPixels(). Gunakan QueueAuthenticPixels() jika Anda ingin menetapkan nilai piksel baru, bukan memperbarui nilai yang sudah ada. Anda bisa menggunakan GetAuthenticPixels() untuk menetapkan nilai piksel, tetapi menggunakan QueueAuthenticPixels() sedikit lebih efisien. Terakhir, gunakan SyncAuthenticPixels() untuk memastikan piksel yang diperbarui didorong (push) ke cache piksel.
Anda dapat mengaitkan konten sembarang dengan setiap piksel, yang disebut konten meta. Gunakan GetVirtualMetacontent()(untuk membaca konten) atau GetAuthenticMetacontent() (untuk memperbarui konten) untuk mendapatkan akses ke konten ini. Misalnya, untuk mencetak metacontent, gunakan:
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 */
Manajer cache piksel memutuskan apakah akan memberi Anda akses langsung atau tidak langsung ke piksel gambar. Dalam beberapa kasus, piksel disimpan sementara (staged) di buffer perantara -- dan itulah sebabnya Anda harus memanggil SyncAuthenticPixels() untuk memastikan buffer ini didorong ke cache piksel guna menjamin piksel terkait di dalam cache diperbarui. Karena itu, kami menyarankan agar Anda hanya membaca atau memperbarui satu atau beberapa scanline piksel dalam satu waktu. Meski begitu, Anda dapat mendapatkan wilayah piksel persegi panjang apa pun yang Anda inginkan. GetAuthenticPixels() mengharuskan wilayah yang Anda minta berada di dalam batas area gambar. Untuk gambar berukuran 640 kali 480, Anda dapat mendapatkan scanline 640 piksel pada baris 479, tetapi jika Anda meminta scanline pada baris 480, sebuah exception dikembalikan (baris diberi nomor mulai dari 0). GetVirtualPixels() tidak memiliki batasan ini. Sebagai contoh,
p=GetVirtualPixels(source,-3,-3,source->columns+3,6,exception);
memberi Anda piksel yang Anda minta tanpa keluhan, meskipun sebagian di antaranya berada di luar batas wilayah gambar.
Piksel virtual
Ada sangat banyak algoritma pemrosesan gambar yang memerlukan sekumpulan piksel tetangga di sekitar piksel yang menjadi perhatian. Algoritma tersebut biasanya menyertakan catatan khusus tentang cara menangani piksel di sekitar batas gambar, yang dikenal sebagai piksel tepi (edge). Dengan piksel virtual, Anda tidak perlu memikirkan pemrosesan tepi khusus, selain memilih metode piksel virtual mana yang paling sesuai untuk algoritma Anda.
Akses ke piksel virtual dikendalikan oleh metode SetImageVirtualPixelMethod() dari API MagickCore atau opsi -virtual-pixel dari baris perintah. Metode yang tersedia meliputi:
| background | area di sekitar gambar adalah warna latar belakang |
|---|---|
| black | area di sekitar gambar berwarna hitam |
| checker-tile | kotak-kotak berselang-seling dengan warna gambar dan warna latar belakang |
| dither | pola dithered 32x32 yang tidak acak |
| edge | memperluas piksel tepi menuju tak hingga (default) |
| gray | area di sekitar gambar berwarna abu-abu |
| horizontal-tile | menyusun gambar secara horizontal (tile), warna latar belakang di atas/bawah |
| horizontal-tile-edge | menyusun gambar secara horizontal (tile) dan menduplikasi piksel tepi samping |
| mirror | menyusun gambar secara cermin (mirror tile) |
| random | memilih piksel acak dari gambar |
| tile | menyusun gambar (tile) |
| transparent | area di sekitar gambar adalah hitam transparan |
| vertical-tile | menyusun gambar secara vertikal (tile), sisi-sisinya berwarna latar belakang |
| vertical-tile-edge | menyusun gambar secara vertikal (tile) dan menduplikasi piksel tepi samping |
| white | area di sekitar gambar berwarna putih |
Penyimpanan cache dan persyaratan sumber daya
Perlu diingat bahwa desain cache piksel ImageMagick yang sederhana dan elegan ini memiliki biaya dari segi penyimpanan dan kecepatan pemrosesan. Kebutuhan penyimpanan cache piksel berskala mengikuti luas gambar dan kedalaman bit komponen piksel. Misalnya, jika kita memiliki gambar 640 kali 480 dan menggunakan versi Q16 non-HDRI ImageMagick, cache piksel mengonsumsi lebar gambar * tinggi * kedalaman-bit / 8 * jumlah kanal byte, atau kurang lebih 2.3 mebibyte (yaitu 640 * 480 * 2 * 4). Tidak terlalu buruk, tetapi bagaimana jika gambar Anda berukuran 25000 kali 25000 piksel? Cache piksel memerlukan sekitar 4.7 gibibyte ruang penyimpanan. Cukup menyakitkan. ImageMagick mengantisipasi kemungkinan kebutuhan penyimpanan yang sangat besar dengan meng-cache gambar besar ke disk, bukan ke memori. Biasanya cache piksel disimpan di memori menggunakan memori heap. Jika memori heap habis, kami membuat cache piksel di disk dan mencoba melakukan memory-map padanya. Jika memori memory-map juga habis, kami cukup menggunakan I/O disk standar. Ruang disk berlimpah dan murah, tetapi juga sangat lambat -- bisa lebih dari 1000 kali lebih lambat dibandingkan mengakses piksel di memori. Kita bisa mendapatkan sedikit peningkatan kecepatan, hingga 5 kali, jika kita melakukan memory-map pada cache berbasis disk. Keputusan tentang penyimpanan ini dibuat secara otomatis oleh manajer cache piksel yang bernegosiasi dengan sistem operasi. Namun, Anda dapat memengaruhi cara manajer cache piksel mengalokasikan cache piksel dengan batas sumber daya cache. Batas-batas tersebut meliputi:
| width | lebar maksimum sebuah gambar. Jika batas ini terlampaui, sebuah exception dilempar dan operasi dihentikan. |
|---|---|
| height | tinggi maksimum sebuah gambar. Jika batas ini terlampaui, sebuah exception dilempar dan operasi dihentikan. |
| area | luas maksimum dalam byte untuk satu gambar mana pun yang dapat berada di memori cache piksel. Jika batas ini terlampaui, gambar secara otomatis di-cache ke disk dan secara opsional di-memory-map. |
| memory | jumlah maksimum memori dalam byte yang dialokasikan untuk cache piksel dari heap. |
| map | jumlah maksimum memory map dalam byte yang dialokasikan untuk cache piksel. |
| disk | jumlah maksimum ruang disk dalam byte yang diizinkan untuk digunakan oleh cache piksel. Jika batas ini terlampaui, sebuah exception fatal dilempar, dan seluruh pemrosesan berhenti. |
| files | jumlah maksimum file cache piksel yang terbuka. Jika batas ini terlampaui, piksel berikutnya yang di-cache ke disk akan ditutup dan dibuka kembali sesuai permintaan. Perilaku ini memungkinkan sejumlah besar gambar diakses secara bersamaan di disk tanpa penalti kecepatan, dengan mengurangi jumlah system call open/close cache piksel. |
| thread | jumlah maksimum thread yang diizinkan berjalan secara paralel. Sistem Anda mungkin memilih jumlah thread yang lebih kecil dari nilai ini. Secara default ImageMagick memilih jumlah thread yang optimal, biasanya sebanyak jumlah core pada host Anda. Tetapkan nilai ini ke 1 agar semua wilayah paralel dieksekusi oleh satu thread. |
| time | jumlah maksimum detik yang diizinkan bagi proses untuk berjalan. Jika batas ini terlampaui, sebuah exception dilempar dan pemrosesan berhenti. |
Perlu diperhatikan, batas-batas ini berlaku untuk cache piksel ImageMagick. Algoritma tertentu di dalam ImageMagick tidak mematuhi batas-batas ini, begitu pula pustaka delegate eksternal mana pun (mis. JPEG, TIFF, dll.).
Untuk mengetahui pengaturan batas-batas ini saat ini, gunakan perintah berikut:
-> 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
Anda dapat menetapkan batas-batas ini baik sebagai kebijakan keamanan (lihat policy.xml), dengan variabel lingkungan, dengan opsi baris perintah -limit, atau dengan metode API MagickCore SetMagickResourceLimit(). Sebagai contoh, antarmuka web online kami untuk ImageMagick, MagickStudio, menyertakan batas kebijakan ini untuk membantu mencegah denial-of-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>
Karena kita memproses banyak sesi secara bersamaan, kita tidak ingin satu sesi mana pun mengonsumsi seluruh memori yang tersedia. Dengan kebijakan ini, gambar besar di-cache ke disk. Jika gambar terlalu besar dan melampaui batas disk cache piksel, program keluar. Selain itu, kami menetapkan batas waktu untuk mencegah tugas pemrosesan yang lepas kendali (run-away). Jika lebar atau tinggi gambar mana pun melebihi 8192 piksel, sebuah exception dilempar dan pemrosesan berhenti. Sejak ImageMagick 7.0.1-8, Anda dapat mencegah penggunaan delegate tertentu atau semua delegate (tetapkan pattern ke "*"). Perlu dicatat, sebelum rilis ini, gunakan domain "coder" untuk mencegah penggunaan delegate (mis. domain="coder" rights="none" pattern="HTTPS"). Kebijakan ini juga mencegah pembacaan tidak langsung. Jika Anda, misalnya, ingin membaca teks dari sebuah file (mis. caption:@myCaption.txt), Anda perlu menghapus kebijakan ini.
Perlu dicatat, batas cache bersifat global untuk setiap invokasi ImageMagick, artinya jika Anda membuat beberapa gambar, kebutuhan sumber daya gabungannya dibandingkan dengan batas untuk menentukan penempatan penyimpanan cache piksel.
Untuk mengetahui jenis dan berapa banyak sumber daya yang dikonsumsi oleh cache piksel, tambahkan opsi -debug cache ke baris perintah:
$ 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]
Perintah ini memanfaatkan cache piksel di memori. Logo mengonsumsi 4.688MiB, dan setelah dipertajam, 3.516MiB.
Cache piksel terdistribusi
Cache piksel terdistribusi adalah perluasan dari cache piksel tradisional yang tersedia pada satu host. Cache piksel terdistribusi dapat mencakup beberapa server sehingga dapat tumbuh dalam ukuran dan kapasitas transaksional untuk mendukung gambar yang sangat besar. Jalankan server cache piksel pada satu atau beberapa mesin. Saat Anda membaca atau mengoperasikan sebuah gambar dan sumber daya cache piksel lokal habis, ImageMagick menghubungi satu atau beberapa server piksel jarak jauh ini untuk menyimpan atau mengambil piksel. Cache piksel terdistribusi bergantung pada bandwidth jaringan untuk mengirimkan piksel ke dan dari server jarak jauh. Karena itu, kinerjanya kemungkinan besar jauh lebih lambat dibandingkan cache piksel yang memanfaatkan penyimpanan lokal (mis. memori, disk, dll.).
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
Cache view
GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels(), dan SyncAuthenticPixels() dari API MagickCore hanya dapat menangani satu area cache piksel per gambar dalam satu waktu. Bagaimana jika Anda ingin mengakses scanline pertama dan terakhir dari gambar yang sama secara bersamaan? Solusinya adalah menggunakan cache view. Cache view memungkinkan Anda mengakses sebanyak mungkin area secara bersamaan di dalam cache piksel sesuai kebutuhan. Metode cache view serupa dengan metode sebelumnya, hanya saja Anda harus membuka view tersebut terlebih dahulu dan menutupnya setelah selesai menggunakannya. Berikut cuplikan kode MagickCore yang memungkinkan kita mengakses baris piksel pertama dan terakhir dari gambar secara bersamaan:
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 cache piksel Magick
Perlu diingat bahwa setiap format gambar di-decode oleh ImageMagick dan piksel-piksel disimpan ke dalam cache piksel. Jika Anda menulis gambar, piksel dibaca dari cache piksel dan di-encode sesuai kebutuhan format yang Anda tulis (mis. GIF, PNG, dll.). Format Magick Pixel Cache (MPC) dirancang untuk menghilangkan overhead dari proses decode dan encode piksel ke dan dari suatu format gambar. MPC menulis dua file. File pertama, dengan ekstensi .mpc, menyimpan semua properti yang terkait dengan gambar atau urutan gambar (mis. lebar, tinggi, ruang warna, dll.), dan file kedua, dengan ekstensi .cache, adalah cache piksel dalam format raw asli. Saat membaca file gambar MPC, ImageMagick membaca properti gambar dan melakukan memory-map pada cache piksel di disk, sehingga menghilangkan kebutuhan untuk men-decode piksel gambar. Trade-off-nya ada pada ruang disk. MPC umumnya berukuran file lebih besar dibandingkan sebagian besar format gambar lainnya.
Penggunaan file gambar MPC yang paling efisien adalah pola tulis-sekali, baca-berkali-kali. Misalnya, alur kerja Anda memerlukan pengambilan blok piksel acak dari gambar sumber. Daripada membaca ulang dan mungkin melakukan dekompresi gambar sumber setiap kali, kita menggunakan MPC dan memetakan gambar langsung ke memori.
Praktik cache piksel yang direkomendasikan
Meskipun Anda dapat meminta piksel mana pun dari cache piksel, blok piksel mana pun, scanline mana pun, beberapa scanline, baris mana pun, atau beberapa baris dengan metode GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels, GetCacheViewVirtualPixels(), GetCacheViewAuthenticPixels(), dan QueueCacheViewAuthenticPixels(), ImageMagick dioptimalkan untuk mengembalikan beberapa piksel atau beberapa baris piksel dalam satu waktu. Ada optimisasi tambahan jika Anda meminta satu scanline atau beberapa scanline sekaligus. Metode-metode ini juga mengizinkan akses acak ke cache piksel, tetapi ImageMagick dioptimalkan untuk akses sekuensial. Meskipun Anda dapat mengakses scanline piksel secara berurutan dari baris terakhir gambar ke baris pertama, Anda mungkin mendapatkan peningkatan performa jika mengakses scanline dari baris pertama gambar ke baris terakhir, dalam urutan sekuensial.
Anda dapat mendapatkan, mengubah, atau menetapkan piksel dalam urutan baris atau kolom. Namun, mengakses piksel per baris lebih efisien dibandingkan per kolom.
Jika Anda memperbarui piksel yang dikembalikan oleh GetAuthenticPixels() atau GetCacheViewAuthenticPixels(), jangan lupa memanggil SyncAuthenticPixels() atau SyncCacheViewAuthenticPixels() secara berurutan untuk memastikan perubahan Anda disinkronkan dengan cache piksel.
Gunakan QueueAuthenticPixels() atau QueueCacheViewAuthenticPixels() jika Anda menetapkan nilai piksel awal. Metode GetAuthenticPixels() atau GetCacheViewAuthenticPixels() membaca piksel dari cache, dan jika Anda menetapkan nilai piksel awal, pembacaan ini tidak diperlukan. Jangan lupa memanggil SyncAuthenticPixels() atau SyncCacheViewAuthenticPixels() secara berurutan untuk mendorong (push) setiap perubahan piksel ke cache piksel.
GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels(), dan SyncAuthenticPixels() sedikit lebih efisien dibandingkan versi cache view-nya. Namun, cache view diperlukan jika Anda memerlukan akses ke lebih dari satu wilayah gambar secara bersamaan, atau jika lebih dari satu thread eksekusi mengakses gambar tersebut.
Anda dapat meminta piksel di luar batas gambar dengan GetVirtualPixels() atau GetCacheViewVirtualPixels(), tetapi lebih efisien untuk meminta piksel yang berada di dalam batas wilayah gambar.
Meskipun Anda dapat memaksa cache piksel ke disk dengan menggunakan batas sumber daya yang sesuai, akses disk bisa lebih dari 1000 kali lebih lambat dibandingkan akses memori. Untuk akses cache piksel yang cepat dan efisien, usahakan agar cache piksel tetap berada di memori heap.
Versi Q16 ImageMagick mengizinkan Anda membaca dan menulis gambar 16-bit tanpa penskalaan, tetapi cache piksel mengonsumsi sumber daya dua kali lipat dibandingkan versi Q8. Jika sistem Anda memiliki sumber daya memori atau disk yang terbatas, pertimbangkan versi Q8 ImageMagick. Selain itu, versi Q8 biasanya berjalan lebih cepat dibandingkan versi Q16.
Sebagian besar format gambar dan algoritma membatasi diri pada rentang nilai piksel tetap dari 0 hingga suatu nilai maksimum, misalnya versi Q16 ImageMagick mengizinkan intensitas dari 0 hingga 65535. Namun, high dynamic-range imaging (HDRI) mengizinkan rentang dinamis eksposur yang jauh lebih besar (yaitu selisih besar antara area terang dan gelap) dibandingkan teknik pencitraan digital standar. HDRI merepresentasikan secara akurat rentang luas tingkat intensitas yang ditemukan pada pemandangan nyata, mulai dari sinar matahari langsung yang paling terang hingga bayangan tergelap. Aktifkan HDRI saat build ImageMagick untuk menangani gambar high-dynamic-range, tetapi perhatikan bahwa setiap komponen piksel adalah nilai floating point 32-bit. Selain itu, nilai piksel secara default tidak di-clamp, sehingga sebagian algoritma dapat menghasilkan hasil yang tidak terduga akibat nilai piksel di luar rentang dibandingkan versi non-HDRI.
Jika Anda menangani gambar besar, pastikan cache piksel ditulis ke area disk dengan banyak ruang kosong. Pada Linux, area ini biasanya /tmp, dan pada Windows, c:/temp. Anda dapat memerintahkan ImageMagick untuk menulis cache piksel ke lokasi alternatif dan menghemat memori dengan opsi berikut:
magick -limit memory 2GB -limit map 4GB -define registry:temporary-path=/data/tmp ...
Tetapkan batas sumber daya global untuk lingkungan Anda di file konfigurasi policy.xml.
Jika Anda berencana memproses gambar yang sama berkali-kali, pertimbangkan format MPC. Membaca gambar MPC memiliki overhead yang mendekati nol karena formatnya sudah berupa format cache piksel asli, sehingga menghilangkan kebutuhan untuk men-decode piksel gambar. Berikut contohnya:
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 ideal untuk situs web. Format ini mengurangi overhead membaca dan menulis gambar. Kami menggunakannya secara eksklusif di studio gambar online kami.
Properti dan profil gambar
Gambar memiliki metadata yang terkait dengannya dalam bentuk properti (mis. lebar, tinggi, deskripsi, dll.) dan profil (mis. EXIF, IPTC, manajemen warna). ImageMagick menyediakan metode praktis untuk mendapatkan, menetapkan, atau memperbarui properti gambar, serta mendapatkan, menetapkan, memperbarui, atau menerapkan profil. Beberapa properti gambar yang populer terkait dengan struktur Image pada API MagickCore. Misalnya,
(void) printf("image width: %lu, height: %lu\n",image->columns,image->rows);
Untuk sebagian besar properti gambar, seperti komentar atau deskripsi gambar, kita menggunakan metode GetImageProperty() dan SetImageProperty(). Di sini kita menetapkan sebuah properti dan langsung mengambilnya kembali:
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 mendukung artifact dengan metode GetImageArtifact() dan SetImageArtifact(). Artifact adalah properti tersembunyi (stealth) yang tidak diekspor ke format gambar (mis. PNG).
Profil gambar ditangani dengan metode GetImageProfile(), SetImageProfile(), dan ProfileImage(). Di sini kita menetapkan sebuah profil dan langsung mengambilnya kembali:
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);
Gambar multispektral
ImageMagick mendukung gambar multispektral di mana semua kanal memiliki dimensi dan jumlah piksel yang sama dengan gambar aslinya. Namun, tidak semua format gambar mendukung gambar multispektral. PSD, TIFF, MIFF, MPC, dan FTXT memiliki dukungan penuh untuk gambar multispektral hingga 31 band, 21 di antaranya meta kanal. Perlu dicatat, jika Anda membangun ImageMagick dengan opsi configure script --enable-64bit-channel-masks, Anda dapat memproses gambar multispektral 62 band dengan hingga 52 meta kanal.
Jika Anda memiliki kasus penggunaan yang belum didukung oleh suatu format gambar, kirimkan ke forum diskusi. Ada kemungkinan besar kami dapat mendukung kasus penggunaan Anda pada rilis ImageMagick di masa mendatang.
Streaming piksel
ImageMagick menyediakan streaming piksel saat piksel tersebut dibaca dari atau ditulis ke sebuah gambar. Ini memiliki beberapa keunggulan dibandingkan cache piksel. Waktu dan sumber daya yang dikonsumsi oleh cache piksel berskala mengikuti luas gambar, sedangkan sumber daya pixel stream berskala mengikuti lebar gambar. Kekurangannya adalah piksel harus dikonsumsi saat di-stream sehingga tidak ada persistensi.
Gunakan ReadStream() atau WriteStream() dengan metode callback yang sesuai pada program MagickCore Anda untuk mengonsumsi piksel saat di-stream. Berikut contoh singkat penggunaan 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);
Kami juga menyediakan alat ringan, stream, untuk men-stream satu atau beberapa komponen piksel dari gambar atau bagian gambar ke format penyimpanan pilihan Anda. Alat ini menulis komponen piksel saat dibaca dari gambar input, satu baris dalam satu waktu, sehingga stream diinginkan ketika bekerja dengan gambar besar atau ketika Anda memerlukan komponen piksel raw. Sebagian besar format gambar men-stream piksel (merah, hijau, dan biru) dari kiri ke kanan dan atas ke bawah. Namun, beberapa format tidak mendukung urutan umum ini (mis. format PSD).
Dukungan gambar besar
ImageMagick memiliki kemampuan untuk menangani ukuran gambar mulai dari mega- hingga tera-piksel, mencakup operasi membaca, memproses, dan menulis. Secara teori, dimensi gambar dapat mencapai hingga 31 juta baris/kolom pada sistem operasi 32-bit dan hingga 31 triliun yang luar biasa besar pada OS 64-bit. Namun, dimensi yang benar-benar dapat dicapai jauh lebih kecil, tergantung pada sumber daya yang tersedia pada komputer host Anda. Penting untuk diketahui bahwa format gambar tertentu memberlakukan batasan pada ukuran gambar. Misalnya, gambar Photoshop dibatasi hingga maksimum 300,000 piksel pada lebar atau tinggi. Di sini kita mengubah ukuran (resize) gambar menjadi seperempat juta piksel persegi:
magick logo: -resize 250000x250000 logo.miff
Untuk gambar besar, sumber daya memori kemungkinan besar akan habis dan ImageMagick akan membuat cache piksel di disk sebagai gantinya. Pastikan Anda memiliki cukup banyak ruang disk sementara. Jika partisi disk sementara default Anda terlalu kecil, perintahkan ImageMagick untuk menggunakan partisi lain dengan banyak ruang kosong. Misalnya:
magick -define registry:temporary-path=/data/tmp logo: \
-resize 250000x250000 logo.miff
Untuk memastikan gambar besar tidak mengonsumsi seluruh memori pada sistem Anda, paksa piksel gambar ke disk yang di-memory-map dengan batas sumber daya:
magick -define registry:temporary-path=/data/tmp -limit memory 16mb \
logo: -resize 250000x250000 logo.miff
Di sini kita memaksa semua piksel gambar ke disk:
magick -define registry:temporary-path=/data/tmp -limit area 0 \
logo: -resize 250000x250000 logo.miff
Meng-cache piksel ke disk sekitar 1000 kali lebih lambat dibandingkan memori. Perkirakan waktu eksekusi yang lama saat memproses gambar besar di disk dengan ImageMagick. Anda dapat memantau progres dengan perintah berikut:
magick -monitor -limit memory 2GiB -limit map 4GiB -define registry:temporary-path=/data/tmp \
logo: -resize 250000x250000 logo.miff
Untuk gambar yang benar-benar besar, atau jika sumber daya pada host Anda terbatas, Anda dapat memanfaatkan cache piksel terdistribusi pada satu atau beberapa host jarak jauh:
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
Karena latensi jaringan, perkirakan perlambatan yang signifikan dalam pemrosesan alur kerja Anda.
Thread eksekusi
Banyak algoritma internal ImageMagick di-thread untuk memanfaatkan percepatan yang ditawarkan oleh chip prosesor multicore. Namun, Anda tetap dapat menggunakan algoritma ImageMagick pada thread eksekusi Anda sendiri, kecuali metode cache piksel MagickCore GetVirtualPixels(), GetAuthenticPixels(), QueueAuthenticPixels(), atau SyncAuthenticPixels(). Metode-metode ini dimaksudkan hanya untuk satu thread eksekusi, kecuali di dalam bagian paralel OpenMP. Untuk mengakses cache piksel dengan lebih dari satu thread eksekusi, gunakan cache view. Kami melakukan ini, misalnya, untuk metode CompositeImage(). Misalkan kita ingin menyusun (composite) satu gambar sumber ke atas gambar tujuan yang berbeda pada setiap thread eksekusi. Jika kita menggunakan GetVirtualPixels(), hasilnya tidak dapat diprediksi karena beberapa thread kemungkinan besar akan meminta area cache piksel yang berbeda secara bersamaan. Sebagai gantinya, kita menggunakan GetCacheViewVirtualPixels() yang membuat view unik untuk setiap thread eksekusi, memastikan program kita berperilaku dengan benar berapa pun jumlah thread yang dijalankan. Antarmuka program lain, seperti API MagickWand, sepenuhnya thread-safe sehingga tidak ada tindakan pencegahan khusus untuk thread eksekusi.
Berikut cuplikan kode MagickCore yang memanfaatkan thread eksekusi dengan paradigma pemrograman 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");
Cuplikan kode ini mengonversi bitmap Windows tanpa kompresi menjadi gambar 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;
}
Jika Anda memanggil API ImageMagick dari aplikasi Anda yang mengaktifkan OpenMP dan bermaksud menambah jumlah thread yang tersedia secara dinamis pada wilayah paralel berikutnya, pastikan penambahan tersebut dilakukan sebelum Anda memanggil API, jika tidak ImageMagick dapat mengalami fault.
MagickWand mendukung wand view. View melakukan iterasi atas seluruh, atau sebagian, gambar secara paralel, dan untuk setiap baris piksel, view tersebut memanggil metode callback yang Anda sediakan. Ini membatasi sebagian besar aktivitas pemrograman paralel Anda hanya pada satu modul tersebut. Ada metode serupa di MagickCore. Sebagai contoh, lihat algoritma kontras sigmoidal yang sama, yang diimplementasikan baik di MagickWand maupun MagickCore.
Pada sebagian besar situasi, jumlah default thread ditetapkan sebanyak jumlah core prosesor pada sistem Anda untuk performa optimal. Namun, jika sistem Anda hyperthreaded atau Anda berjalan pada host virtual dan hanya sebagian prosesor yang tersedia untuk instance server Anda, Anda mungkin mendapatkan peningkatan performa dengan menetapkan kebijakan thread atau variabel lingkungan MAGICK_THREAD_LIMIT. Misalnya, host virtual Anda memiliki 8 prosesor tetapi hanya 2 yang dialokasikan untuk instance server Anda. Default 8 thread dapat menyebabkan masalah performa yang serius. Salah satu solusinya adalah membatasi jumlah thread sesuai prosesor yang tersedia di file konfigurasi policy.xml Anda:
<policy domain="resource" name="thread" value="2"/>
Atau misalkan komputer hyperthreaded 12-core Anda secara default menggunakan 24 thread. Tetapkan variabel lingkungan MAGICK_THREAD_LIMIT dan Anda kemungkinan besar akan mendapatkan performa yang lebih baik:
export MAGICK_THREAD_LIMIT=12
Komite OpenMP belum mendefinisikan perilaku pencampuran OpenMP dengan model threading lain seperti Posix thread. Namun, pada rilis Linux modern, OpenMP dan Posix thread tampaknya dapat berinteroperasi tanpa masalah. Jika Anda ingin menggunakan Posix thread dari modul program yang memanggil salah satu antarmuka pemrograman aplikasi ImageMagick (mis. MagickCore, MagickWand, Magick++, dll.) dari Mac OS X atau rilis Linux yang lebih lama, Anda mungkin perlu menonaktifkan dukungan OpenMP di dalam ImageMagick. Tambahkan opsi --disable-openmp ke baris perintah configure script, lalu bangun ulang dan instal ulang ImageMagick.
Anda dapat lebih meningkatkan performa dengan mengurangi lock contention menggunakan pustaka alokasi memori tcmalloc. Untuk mengaktifkannya, tambahkan --with-tcmalloc ke baris perintah configure saat Anda membangun ImageMagick.
Performa threading
Sulit untuk memprediksi perilaku pada lingkungan paralel. Performa mungkin bergantung pada sejumlah faktor, termasuk kompiler, versi pustaka OpenMP, jenis prosesor, jumlah core, jumlah memori, apakah hyperthreading diaktifkan, kombinasi aplikasi yang berjalan bersamaan dengan ImageMagick, atau algoritma pemrosesan gambar tertentu yang Anda gunakan. Satu-satunya cara untuk memastikan performa optimal, dari segi jumlah thread, adalah dengan melakukan benchmark. ImageMagick menyertakan progressive threading saat melakukan benchmark sebuah perintah dan mengembalikan waktu yang berlalu serta efisiensi untuk satu atau beberapa thread. Ini dapat membantu Anda mengidentifikasi berapa banyak thread yang paling efisien pada lingkungan Anda. Untuk benchmark ini, kita menajamkan (sharpen) gambar model berukuran 1920x1080 sebanyak 10 kali dengan 1 hingga 12 thread:
$ 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
Titik optimal (sweet spot) untuk contoh ini adalah 6 thread. Ini masuk akal karena ada 6 core fisik. 6 lainnya adalah hyperthread. Tampaknya penajaman tidak mendapat manfaat dari hyperthreading.
Dalam kasus tertentu, mungkin lebih optimal untuk menetapkan jumlah thread ke 1 atau menonaktifkan OpenMP sepenuhnya dengan variabel lingkungan MAGICK_THREAD_LIMIT, opsi baris perintah -limit, atau file konfigurasi policy.xml.
Mengaktifkan throttling dinamis untuk performa yang dioptimalkan
Opsi efektif lainnya adalah mengaktifkan throttling dinamis di ImageMagick. Saat fitur ini diaktifkan, ImageMagick secara otomatis menyisipkan sedikit jeda di antara pemrosesan scanline gambar berdasarkan load average sistem Anda saat itu. Ini membantu mencegah kehabisan sumber daya dan menjaga performa tetap lancar, terutama di bawah beban kerja yang berat.
Untuk mengaktifkan throttling dinamis, tambahkan konfigurasi kebijakan berikut:
<policy domain="resource" name="dynamic-throttle" value="true"/>
Pemrosesan terdistribusi heterogen
ImageMagick menyertakan dukungan untuk pemrosesan terdistribusi heterogen dengan framework OpenCL. Kernel OpenCL di dalam ImageMagick memungkinkan algoritma pemrosesan gambar dieksekusi pada platform heterogen yang terdiri atas CPU, GPU, dan prosesor lain. Bergantung pada platform Anda, percepatannya bisa mencapai satu orde besaran lebih cepat dibandingkan CPU tunggal tradisional.
Pertama, pastikan versi ImageMagick Anda menyertakan dukungan untuk fitur OpenCL:
magick identify -version
Features: DPC Cipher Modules OpenCL OpenMP(4.5)
Jika ya, jalankan perintah berikut untuk mendapatkan percepatan signifikan pada konvolusi gambar:
magick image.png -convolve '-1, -1, -1, -1, 9, -1, -1, -1, -1' convolve.png
Jika akselerator tidak tersedia atau gagal merespons, ImageMagick akan kembali menggunakan algoritma konvolusi tanpa akselerasi.
Berikut contoh kernel OpenCL yang melakukan konvolusi pada gambar:
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);
};
Lihat MagickCore/accelerate.c untuk implementasi lengkap konvolusi gambar dengan kernel OpenCL.
Perlu dicatat, pada Windows Anda mungkin mengalami masalah dengan TDR (Timeout Detection and Recovery of GPUs). Tujuannya adalah mendeteksi tugas yang lepas kendali (runaway) yang membuat GPU hang, menggunakan ambang batas (threshold) waktu eksekusi. Untuk sebagian GPU low-end yang lebih lama yang menjalankan filter OpenCL di ImageMagick, waktu eksekusi yang lebih lama dapat memicu mekanisme TDR dan menghentikan (preempt) filter gambar GPU. Ketika ini terjadi, ImageMagick secara otomatis kembali ke jalur kode CPU dan mengembalikan hasil yang diharapkan. Untuk menghindari preemption, tingkatkan registry key TdrDelay.
Coder gambar kustom
Coder gambar (yaitu encoder / decoder) bertanggung jawab untuk mendaftarkan, secara opsional mengklasifikasikan, secara opsional membaca, secara opsional menulis, dan membatalkan pendaftaran satu format gambar (mis. PNG, GIF, JPEG, dll.). Mendaftarkan coder gambar memberi tahu ImageMagick bahwa format tertentu tersedia untuk dibaca atau ditulis. Sementara pembatalan pendaftaran memberi tahu ImageMagick bahwa format tersebut tidak lagi tersedia. Metode klasifikasi melihat beberapa byte pertama sebuah gambar dan menentukan apakah gambar tersebut dalam format yang diharapkan. Pembaca (reader) menetapkan ukuran gambar, ruang warna, dan properti lainnya, serta memuat cache piksel dengan piksel-piksel tersebut. Pembaca mengembalikan satu gambar atau urutan gambar (jika format mendukung banyak gambar per file), atau jika terjadi error, sebuah exception dan gambar null. Penulis (writer) melakukan hal sebaliknya. Penulis mengambil properti gambar, membongkar (unload) cache piksel, dan menulisnya sesuai kebutuhan format gambar.
Berikut listing dari contoh coder kustom. Coder ini membaca dan menulis gambar dalam format gambar MGK, yang sederhananya berupa ID diikuti lebar dan tinggi gambar, diikuti nilai piksel 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);
}
Untuk memanggil coder kustom dari baris perintah, gunakan perintah berikut:
magick logo: logo.mgk
display logo.mgk
Kami menyediakan Magick Coder Kit untuk membantu Anda memulai menulis coder kustom Anda sendiri.
Sebelum Anda membangun, atur variabel lingkungan PKG_CONFIG_PATH jika ImageMagick tidak berada di path sistem default Anda:
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
Filter gambar kustom
ImageMagick menyediakan mekanisme yang mudah untuk menambahkan algoritma pemrosesan gambar kustom Anda sendiri. Kami menyebutnya filter gambar, dan filter ini dipanggil dari baris perintah dengan opsi -process atau dari metode API MagickCore ExecuteModuleProcess().
Berikut adalah contoh filter gambar kustom. Filter ini menghitung beberapa statistik, seperti rata-rata dan deviasi standar kecerahan serta saturasi piksel.
#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);
}
Untuk memanggil filter kustom dari baris perintah, gunakan perintah berikut:
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
Kami menyediakan Magick Filter Kit untuk membantu Anda memulai menulis filter gambar kustom Anda sendiri.
Sebelum Anda membangun, atur variabel lingkungan PKG_CONFIG_PATH jika ImageMagick tidak berada di path sistem default Anda:
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig