Contoh penggunaan ImageMagick -- optimisasi animasi
- Kata pengantar dan indeks contoh penggunaan ImageMagick
- Pengantar optimisasi
- Pengoptimal GIF serba guna dari ImageMagick
- Optimisasi frame
- Optimisasi frame dasar
- Tanpa overlay piksel - gambar yang berulang setiap frame kedua
- Animasi lubang bergerak - animasi yang sulit dioptimisasi frame-nya
- Penggandaan frame
- Layer Optimize Plus
- Menghapus frame duplikat
- Memecah pembaruan frame
- Menghapus frame jeda nol
- Animasi speed - animasi dengan warna terlalu banyak
- Optimisasi frame sebelum optimisasi warna?
- Optimisasi warna fuzzy
- Satu tabel warna global
- Optimisasi LZW
- Optimisasi LZW lossy - (non-IM)
-
Optimisasi LZW dengan ordered dither
Optimisasi minor Sumber informasi lain tentang optimisasi GIF Contoh-contoh ini mulai memanfaatkan penanganan dasar animasi untuk mencoba mengoptimisasi tampilan akhir dan ukuran file sebuah animasi. Ini terutama penting untuk animasi GIF kompleks yang dapat memakai overlay subframe lebih kecil, serta tiga jenis metode disposal yang mengendalikan bagaimana animasi ditangani.
Pengantar optimisasi animasi
Mengoptimisasi animasi tidaklah mudah, apalagi animasi GIF yang punya pembatasan warna, pilihan berbagai teknik disposal frame, serta kemampuan memakai overlay 'subframe' lebih kecil dari satu frame ke frame berikutnya. Saat mengoptimisasi animasi, usahakan mengikuti urutan berikut.
- Optimisasi minor
- Penanganan semitransparansi
- Optimisasi warna
- Optimisasi frame
- Optimisasi kompresi
- Satu tabel warna global
Namun bukan itu urutan kita membahas teknik-teknik optimisasi ini. Untuk animasi GIF, optimisasi frame adalah teknik optimisasi paling mendasar, dan di sanalah keuntungan terbesar bisa diraih. Karena itu ia dibahas lebih dulu. Aspek optimisasi yang mungkin paling menyulitkan pengguna adalah optimisasi warna akibat keterbatasan warna animasi GIF. Salah satu bagiannya, satu tabel warna global, harus dilakukan sebagai langkah terakhir sebelum menyimpan ke GIF, atau efek operator itu bisa hilang pada penyimpanan file GIF akhir.
Pengoptimal GIF serba guna dari ImageMagick
Metode "-layers" '**Optimize**' memakai sejumlah teknik, yang akan kita bahas rinci di bawah, untuk mencoba mengoptimisasi sebuah animasi GIF dalam satu langkah yang masuk akal. Saat ini opsi tersebut setara dengan (berurutan)...
- Coalesce animasinya.
- Optimisasi frame dasar
- dan optimisasi transparansi
Pada titik itu animasi GIF-nya bisa langsung disimpan. Ini langkah-langkah optimisasi yang cukup aman dan bisa diterapkan pada kebanyakan urutan animasi, tetapi tidak ada jaminan hasilnya berupa animasi GIF yang lebih kecil. Ini terutama berlaku untuk urutan video mentah, di mana optimisasi transparansi umumnya justru memperburuk rasio kompresi LZW. Namun untuk kebanyakan animasi GIF yang melibatkan gambar bergaya kartun, operator 'Optimize' semestinya menghasilkan animasi yang teroptimisasi dengan baik. Operator ini masih dalam pengembangan, dan ke depan kemungkinan akan mencakup pula langkah optimisasi standar tambahan, seperti...
- Threshold 50% pada kanal alpha, persis seperti yang biasa dilakukan IM saat menyimpan ke format file GIF, untuk membuang piksel semitransparan. Anda tetap bisa menangani sendiri semitransparansi itu lebih dulu untuk menimpanya, jika mau. Lihat transparansi boolean GIF untuk detailnya.
- Semacam teknik optimisasi warna. Persisnya apa masih akan diputuskan, dan mungkin dipilih tergantung animasi dan jumlah warna yang terlibat. Saran dipersilakan.
- Operasi satu tabel warna global, "
+map".
Dengan kata lain, diharapkan 'Optimize' kelak menjadi pengoptimal animasi GIF generik IM, yang cepat dan mudah dipakai pengguna IM. Sampai saat itu, hati-hatilah memakainya, terutama di dalam skrip, karena ia akan berubah. Tentu banyak langkah optimisasi mungkin tidak sepadan untuk animasi tertentu. Opsi ini kemungkinan juga akan menjadi cukup lambat. Itulah rencananya, dan tujuan yang dituju bagian contoh penggunaan IM ini.
Optimisasi frame
Optimisasi frame didasarkan pada meng-overlay subgambar yang lebih kecil alih-alih menimpa seluruh gambar secara penuh. Ini jelas menghasilkan jumlah piksel lebih sedikit, sehingga file di disk lebih kecil untuk dikirim lewat jaringan. Meng-overlay frame yang lebih kecil juga berarti komputer klien tidak perlu bekerja sekeras itu mengubah piksel di layar. Namun format GIF menyediakan beberapa metode disposal untuk menangani frame yang terakhir ditampilkan, dan itu bisa menghasilkan overlay dengan ukuran berbeda. Tidak hanya itu, overlay juga bisa dipecah menjadi beberapa bagian, atau aksi pembaruan, menghasilkan animasi yang lebih kompleks tetapi lebih teroptimisasi. Karena rumitnya optimisasi frame, optimisasi frame yang sudah ada biasanya selalu dihapus dulu dengan operasi "-coalesce". Lihat contoh coalesce. Otomatis itu berarti optimisasi manual yang mungkin sudah ada juga ikut terhapus, jadi perlu sedikit kewaspadaan.
Optimisasi frame dasar
Metode "-deconstruct" menghasilkan optimisasi frame dasar untuk animasi GIF. Namun seperti ditunjukkan di contoh deconstruct pada bagian sebelumnya, operator ini tidak bekerja untuk semua animasi GIF bila ada piksel transparan yang terlibat. Tepatnya, ketika animasi mengosongkan piksel berwarna menjadi transparan. Artinya, ia hanya bekerja untuk animasi overlay. Metode "-layers" '**OptimizeFrame**' dirancang sebagai pengoptimal frame GIF, yang mencoba menemukan gambar overlay subframe terkecil, memakai metode disposal GIF apa pun. Hasilnya umumnya animasi disposal campuran, meski sering juga ia menghasilkan animasi frame yang dibersihkan atau animasi overlay murni, bila itu dinilai solusi terbaik untuk animasi tersebut. Ingat, animasi masukan harus berupa 'animasi hasil coalesce', yaitu terdiri dari urutan frame gambar lengkap, semuanya berukuran sama, tanpa offset kanvas. Tentu metode dispose apa pun yang ada di animasi hasil coalesce sama sekali tidak relevan, dan akan diabaikan oleh metode 'OptimizeFrame'. Sebagai contoh, mari kita coba pada animasi dispose previous yang dibuat di bagian sebelumnya. | |
magick canvas_prev.gif -coalesce -layers OptimizeFrame optframe.gif
gif_anim_montage optframe.gif optframe_frames.gif
![[IM Output]](../static/img/anim_opt/optframe.gif)
![[IM Output]](../static/img/anim_opt/optframe_frames.gif)
Seperti yang terlihat, "-layers OptimizeFrame' dengan benar mengembalikan animasi kita ke bentuk teroptimisasi frame aslinya, memakai disposal previous. Optimisasi ini bahkan bekerja dengan benar untuk animasi dispose background yang lebih sulit ditangani... | |
magick canvas_bgnd.gif -coalesce -layers OptimizeFrame optframe_bgnd.gif
gif_anim_montage optframe_bgnd.gif optframe_bgnd_frames.gif
![[IM Output]](../static/img/anim_opt/optframe_bgnd.gif)
![[IM Output]](../static/img/anim_opt/optframe_bgnd_frames.gif)
Animasi tersebut teroptimisasi frame secara sempurna memakai disposal background. Operator ini bekerja benar untuk semua animasi GIF, dan umumnya mengembalikan 'optimisasi dispose dan frame' sederhana terbaik yang mungkin.
Sekarang kabar buruk tentang segala jenis optimisasi frame sederhana, seperti yang disediakan IM... Meskipun 'OptimizeFrame' mengembalikan optimisasi frame terbaik yang bisa ditemukan IM untuk suatu animasi, ada sejumlah kasus khusus yang tidak ditanganinya dengan baik. Di antaranya...
- Animasi yang membutuhkan pengosongan piksel (kembali ke transparan), tetapi overlay frame-nya terlalu besar untuk mengosongkan secara efisien area kecil piksel yang perlu dikosongkan (lihat animasi lubang bergerak di bawah).
- Animasi dengan dua atau lebih area perubahan kecil yang saling berjauhan. Ini sebenarnya cukup umum, dan mengerikan untuk dioptimisasi frame-nya. (Lihat memecah pembaruan frame di bawah)
- Animasi dengan latar belakang sangat kompleks yang statis untuk waktu lama (lebih dari 3 frame), lalu berubah sedikit sebelum statis lagi untuk waktu lama, dst., dst., dst... Atau latar belakang statis yang tertutup banyak untuk waktu sangat singkat. Hampir mustahil bagi algoritma komputer mana pun menemukan optimisasi frame 'terbaik' dalam situasi kompleks ini (yaitu: apa yang harus dianggap latar belakang statis?). Hanya manusia, dengan daya tangkap intuitifnya atas apa yang dilihatnya, yang bisa membangkitkan urutan overlay frame yang optimal dalam kasus-kasus ini.
Contoh animasi yang sulit dioptimisasi sangat diharapkan, silakan berkontribusi. Jika Anda menemukan contoh animasi yang gagal dioptimisasi IM dengan baik, tolong kirimkan ke saya untuk dipelajari lebih lanjut. Beginilah teknik baru dan kemungkinan solusi otomatis bisa dikembangkan. Tentu nama Anda akan saya cantumkan sebagai kontributor.
Tanpa overlay piksel - gambar yang berulang setiap frame kedua
Kadang optimisasi terbaik untuk sebuah gambar justru tidak meng-overlay piksel sama sekali! Misalnya di sebelah kanan ada animasi sederhana, kontribusi dari nixscripter. Jika kita lihat frame-frame-nya, tampak ia tidak begitu teroptimisasi. Tapi perhatikan bahwa setiap frame kedua animasi itu sekadar diulang.
gif_anim_montage paddleball.gif paddleball_frames.gif
Setelah dioptimisasi frame, kita mendapat urutan disposal GIF yang sangat istimewa. | |
magick paddleball.gif -coalesce -layers OptimizeFrame paddleball_opt.gif
gif_anim_montage paddleball_opt.gif paddleball_opt_frames.gif
![[IM Output]](../static/img/anim_opt/paddleball_opt.gif)
![[IM Output]](../static/img/anim_opt/paddleball_opt_frames.gif)
Yang terjadi adalah: alih-alih meng-overlay frame aslinya, IM memilih memulihkan gambar pertama memakai disposal GIF 'Previous'. Karena frame yang dipulihkan itu dibiarkan apa adanya, tidak ada piksel yang berubah. Maka overlay subframe menyusut menjadi tidak ada. Sayangnya, baik IM maupun format GIF tidak mengizinkan gambar berukuran nol, jadi dipakailah gambar minimal khusus berisi satu piksel transparan. Gambar ini dikenal sebagai Missed Image, karena juga banyak dipakai ketika "-crop" 'meleset' dari data gambar sebenarnya, menghasilkan hal yang sama. Gambar ini, pada praktiknya, hanya mempertahankan metadata frame, seperti: metode dispose, jeda waktu, dan iterasi loop. Dengan demikian ia bagian esensial dari animasi, meski 'kosong'. Jadi dengan meng-overlay satu piksel transparan minimal saja, IM menghemat sangat banyak ruang (dan waktu) pada animasi ini.
Animasi lubang bergerak - sulit dioptimisasi frame-nya
Berikut satu kasus ekstrem animasi GIF yang tidak teroptimisasi frame dengan baik oleh metode optimisasi normal mana pun. Animasi ini pada dasarnya terdiri dari gambar latar belakang sederhana yang tak berubah, tetapi dengan 'lubang' transparan menembus latar itu yang berpindah posisi dari frame ke frame. Untuk membuatnya, saya perlu membuat urutan gambar hasil coalesce, dengan melubangi gambar latar belakang tetap memakai komposisi alpha layer. Saya juga memakai switch "+antialias" untuk memastikan hanya empat warna yang dipakai: tiga biru dan transparansi. Jadi kita tidak perlu berurusan dengan masalah optimisasi warna. | |
magick +antialias -size 100x100 -delay 100 xc:SkyBlue -loop 0 \
-fill DodgerBlue -draw 'circle 50,50 15,25' \
-fill RoyalBlue -draw 'circle 50,50 30,25' \
null: \( -size 100x100 xc:none -draw 'circle 40,25 27,22' \) \
\( +clone -rotate 90 \) \( +clone -rotate 90 \) \
\( +clone -rotate 90 \) -compose DstOut -layers Composite \
-set dispose background moving_hole.gif
gif_anim_montage moving_hole.gif moving_hole_frames.gif
![[IM Output]](../static/img/anim_opt/moving_hole.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_frames.gif)
Seperti yang terlihat, animasinya bekerja, dengan 'lubang' bundar yang memperlihatkan warna latar halaman ini, menghasilkan file animasi berukuran
byte. Jadi mari kita coba optimisasi frame langsung pada animasi ini. | |
magick moving_hole.gif -layers OptimizeFrame moving_hole_opt.gif
gif_anim_montage moving_hole_opt.gif moving_hole_opt_frames.gif
![[IM Output]](../static/img/anim_opt/moving_hole_opt.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_opt_frames.gif)
Tunggu, tidak terjadi apa-apa! Optimisasi terbaik yang bisa dicapai IM adalah tanpa perubahan sama sekali! Apakah versi coalesce animasi di atas memang bentuk paling optimalnya? Ya, untuk animasi sebagaimana adanya... ini memang optimisasi sederhana terbaik yang bisa dicapai lewat optimisasi disposal frame murni! Tidak bagus. Masalahnya, agar animasi GIF bisa 'mengosongkan' atau 'menghapus' piksel yang digambar frame sebelumnya, ia perlu memakai metode dispose 'Background'. Meski dalam beberapa situasi khusus metode dispose 'Previous' juga bisa dipakai. Namun dispose 'Background' hanya bisa mengosongkan area yang baru saja di-overlay. Karena frame pertama adalah overlay penuh seluruh gambar, seluruh gambar pun dikosongkan. Padahal hanya sebagian kecil animasi yang piksel-pikselnya perlu dikosongkan. Akibatnya seluruh frame kedua harus di-overlay, meski sebagian besar frame itu baru saja ditampilkan! Situasi serba salah yang mengerikan ini berlanjut di sepanjang sisa animasi, sehingga tidak ada optimisasi frame dasar sama sekali. Sudah saya bilang animasi ini bakal sulit dioptimisasi frame-nya.
Penggandaan frame - cara mengoptimisasi frame 'lubang'
Namun belum semuanya hilang. Dengan menambahkan beberapa frame ekstra ke animasi, metode 'OptimizeFrame' diberi ruang untuk memanfaatkan metode disposal GIF yang tersedia dengan lebih baik. Di sini misalnya kita menambah satu frame ekstra dengan menggandakan gambar pertama, tetapi memberinya jeda waktu nol agar pewaktuan keseluruhan animasi tidak berubah. | |
magick moving_hole.gif[0] -set delay 0 moving_hole.gif \
-layers OptimizeFrame moving_hole_dup.gif
gif_anim_montage moving_hole_dup.gif moving_hole_dup_frames.gif
![[IM Output]](../static/img/anim_opt/moving_hole_dup.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_dup_frames.gif)
Dengan menggandakan frame pertama, animasi kini menyusut dari
byte menjadi
byte. Jadi meskipun animasinya kini punya lima frame, ukurannya jauh lebih kecil secara keseluruhan, berkat pengurangan masif ukuran gambar overlay subframe. Penggandaan pada dasarnya memisahkan fungsi pengosongan piksel oleh metode dispose dari fungsi peng-overlay-an piksel oleh frame berikutnya. Baik dispose maupun overlay dijalankan sebagai bagian dari pembaruan frame yang sama oleh program animasi GIF, jadi semestinya tidak ada kehilangan kecepatan atau kualitas yang terasa. Ini teknik yang kompleks dan rumit, yang jarang terlihat atau dipahami oleh desainer animasi GIF maupun program pengoptimal GIF, tetapi manfaatnya sangat sepadan saat dibutuhkan. Namun pengurangan ukuran gambar subframe itu hanya bertahan sebentar, karena frame-frame berikutnya juga harus mengosongkan piksel untuk frame sesudahnya, sehingga frame membesar lagi demi terus mengosongkan piksel berikutnya. Sebab pengosongan piksel selalu menghasilkan frame yang lebih besar, tidak pernah lebih kecil. Jadi mari kita coba menggandakan semua frame (kecuali yang terakhir, yang tak pernah perlu digandakan) untuk melihat pengaruhnya pada gambar akhir... | |
magick moving_hole.gif \( -clone 0--1 -set delay 0 \) \
+delete -insert 2 -insert 1 -insert 0 \
-layers OptimizeFrame moving_hole_double.gif
gif_anim_montage x2 moving_hole_double.gif moving_hole_double_frames.gif
![[IM Output]](../static/img/anim_opt/moving_hole_double.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_double_frames.gif)
Seperti yang terlihat, meski jumlah frame-nya hampir dua kali lipat, semua ukuran gambarnya jauh lebih kecil, menghasilkan animasi berukuran
byte — hasil yang lebih kecil, meski tidak sebesar penghematan dari penggandaan satu frame pertama tadi. Supaya Anda bisa mengikuti apa yang terjadi: frame 'Background' adalah duplikat persis frame sebelumnya, tanpa mengubah apa yang sedang ditampilkan. Namun ia mendefinisikan area animasi yang perlu dikosongkan sebelum gambar frame berikutnya di-overlay. Frame 'None' berikutnya lalu mengisi piksel yang perlu diubah, sekaligus piksel yang ikut dikosongkan oleh disposal frame sebelumnya. Pada animasi di atas itu berarti piksel yang dibutuhkan untuk membentuk lubang baru, sekaligus piksel untuk mengisi 'lubang' sebelumnya. Hasilnya lebih kecil tetapi tidak sebesar itu, karena menambah frame ekstra punya ongkosnya sendiri. Setidaknya tiap frame tambahan itu juga tidak membawa tabel warnanya sendiri, atau animasi ini justru menjadi lebih besar akibat ukuran tabel warna ekstra!
Layer Optimize Plus - optimisasi penggandaan frame otomatis
Dengan senang hati saya sampaikan bahwa sejak versi 6.2.7, IM kini bisa melakukan optimisasi penggandaan frame secara otomatis, sebagai bagian dari penanganan optimisasi frame normalnya. Namun karena menambah frame demi memperkecil animasi adalah langkah yang begitu radikal, ia diberi metode "-layers" tersendiri: '**OptimizePlus**'. Sebagai contoh, mari kita minta IM melakukan optimisasi penggandaan frame... | |
magick moving_hole.gif -layers OptimizePlus moving_hole_oplus.gif
gif_anim_montage x2 moving_hole_oplus.gif moving_hole_oplus_frames.gif
![[IM Output]](../static/img/anim_opt/moving_hole_oplus.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_oplus_frames.gif)
Artinya, IM memberi hasil yang sama dengan contoh penggandaan frame kita sebelumnya. File GIF-nya pun tetap
byte. Namun 'OptimizePlus' hanya akan menggandakan frame bila jumlah piksel pada frame saat ini dan frame berikutnya dari animasi hasilnya (3 frame) berkurang, jadi kita bisa membiarkan IM yang memutuskan menggandakan frame atau tidak. Karena metode "-layers" 'OptimizePlus' menambah frame ekstra saat membuat animasi GIF teroptimisasi frame, ia juga akan menghapus frame yang tidak dibutuhkan atau berlebih yang tidak mengubah animasi akhir (sambil menggabungkan jeda waktunya sebagaimana mestinya). Artinya, ia juga otomatis melakukan 'RemoveDups' (lihat berikutnya). Metode 'OptimizeFrame' tidak melakukan ini.
Menghapus frame duplikat - menggabungkan gambar duplikat beruntun
Sayangnya, jika animasi ini di-coalesce", semua frame ekstra yang ditambahkan tadi ikut terbawa.
magick moving_hole_oplus.gif -coalesce gif:- |\
gif_anim_montage x2 - moving_hole_oplus_cframes.gif
Agar frame duplikat tak berguna semacam itu bisa dihapus dari animasi hasil coalesce, disediakan metode '**RemoveDups**'. Metode ini membandingkan tiap frame dengan frame berikutnya dalam animasi, dan menghapus frame pertama jika keduanya identik (dengan kemiripan warna diatur oleh faktor fuzz saat itu). Dan agar pewaktuan animasi tidak hilang, jeda waktu kedua frame juga digabungkan. Misalnya..
magick moving_hole_oplus.gif -coalesce -layers RemoveDups gif:- |\
gif_anim_montage - moving_hole_oplus_rmdups_frames.gif
Dan kini kita mendapat kembali bentuk coalesce asli animasi itu. Untuk cara lain menghapus frame ekstra, lihat metode 'RemoveZero' di bawah.
Memecah pembaruan frame - memperbarui dua perubahan berjauhan secara terpisah
Seperti sudah terlihat pada penggandaan frame, dengan memisahkan 'pengosongan piksel' dari peng-overlay-an piksel baru, kita bisa mengurangi ukuran keseluruhan satu overlay frame. Namun animasi ini masih menghasilkan overlay yang sangat besar, yang sebagian besar berisi piksel yang sebenarnya tidak berubah dari satu frame ke berikutnya. Artinya, frame overlay utama sebenarnya hanya memperbarui dua area cukup kecil yang saling berjauhan, sehingga menghasilkan satu gambar overlay besar. Alih-alih mencoba memperbarui kedua perubahan itu serentak, yang juga menyertakan semua piksel tak berubah di antara kedua area, kita memperbarui tiap area secara terpisah. Artinya, kita memecah pembaruan frame menjadi dua fase, satu untuk tiap area terpisah yang berubah. Dalam hal ini kita bisa mengisi lubang lama dulu, lalu membuat lubang baru sebagai pembaruan terpisah. Sebenarnya tidak penting (kecuali mungkin terkait disposal) perubahan mana dari keduanya yang terjadi lebih dulu, tetapi usahakan logis. Bisa juga salah satu perubahan lebih mudah dibuat daripada yang lain. Misalnya, di sini saya menyisipkan frame ekstra untuk mengisi lubang lama sebagai pembaruan terpisah dari 'penggalian' lubang baru. Ini frame antara yang lebih mudah dibangkitkan sekaligus urutan aksi yang paling logis. Tentu Anda tidak perlu melakukannya untuk frame terakhir, karena frame itu langsung dibuang sebelum animasi mengulang. | |
magick moving_hole.gif \
\( +antialias -size 100x100 -delay 0 xc:SkyBlue \
-fill DodgerBlue -draw 'circle 50,50 15,25' \
-fill RoyalBlue -draw 'circle 50,50 30,25' \) \
\( +clone \) -insert 1 \( +clone \) -insert 3 +swap \
-set dispose background moving_hole_split.gif
gif_anim_montage x2 moving_hole_split.gif moving_hole_split_frames.gif
![[IM Output]](../static/img/anim_opt/moving_hole_split.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_split_frames.gif)
Ingat, frame antara yang ditambahkan berbeda dari frame di sekitarnya yang ditampilkan ke pengguna (yang jeda waktunya bukan nol). Ini bukan sekadar 'penggandaan frame', melainkan pemisahan dua perubahan kecil yang berjauhan. Penambahan frame antara semacam ini bukan langkah sederhana yang bisa diotomatiskan. Meski mungkin saja heuristik cerdas bisa dikembangkan untuk membangkitkan frame antara ini, tidak selalu jelas apa yang harus dilakukan, apalagi apakah perlu dilakukan. Jika Anda ingin mencoba membangkitkan heuristik semacam itu, silakan surati saya. Jadi mari kita coba optimisasi frame standar setelah menambahkan frame-frame ekstra ini... | |
magick moving_hole_split.gif \
-layers OptimizeFrame moving_hole_split_opt.gif
gif_anim_montage x2 moving_hole_split_opt.gif \
moving_hole_split_opt_frames.gif
![[IM Output]](../static/img/anim_opt/moving_hole_split_opt.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_split_opt_frames.gif)
Penambahan 'frame antara jeda nol' ini membuat animasi ini teroptimisasi frame lebih baik daripada animasi asli yang belum dioptimisasi, menghasilkan animasi
byte. Namun untuk kasus khusus ini hasilnya tidak sebaik memakai teknik penggandaan frame otomatis (lihat metode layers 'OptimizePlus' di atas). Meski begitu, menambahkan 'frame antara jeda nol' tidak menghalangi penerapan teknik 'penggandaan frame' itu juga... | |
magick moving_hole_split.gif \
-layers OptimizePlus moving_hole_split_oplus.gif
gif_anim_montage x2 moving_hole_split_oplus.gif \
moving_hole_split_oplus_frames.gif
![[IM Output]](../static/img/anim_opt/moving_hole_split_oplus.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_split_oplus_frames.gif)
Animasi ini kini punya dua 'frame antara jeda nol' ekstra per pembaruan frame. Yang pertama mengisi lubang lama, yang kedua mengosongkan area yang akan berisi piksel transparan, sebelum akhirnya piksel yang semestinya tidak dikosongkan dipulihkan. Hasilnya adalah optimisasi frame paling optimal yang mungkin untuk animasi bermasalah ini, dengan ukuran file akhir
byte. Artinya, animasi 4 frame kita menjadi lebih kecil dengan menambahkan 6 frame ekstra berjeda nol! Lebih dari dua kali jumlah frame aslinya. Aneh tapi nyata! Tentu akan bagus juga bila program animasi GIF benar-benar mengenali frame antara jeda nol sebagaimana adanya, yaitu pembaruan antara di sela frame-frame sesungguhnya dari animasi. Meski begitu, bila pembaruannya sangat berjauhan dan sangat kecil, jeda singkat akibat frame ekstra itu jarang terlihat.
Tentu, jika kedua bagian animasi yang terpisah itu sebenarnya tidak berkaitan, keduanya tidak perlu disinkronkan waktunya. Alternatif lain, alih-alih menambah frame ekstra, adalah memecah animasi menjadi dua animasi yang sepenuhnya terpisah dan menampilkannya bersama di halaman web. Lihat memecah animasi. Namun animasi yang satu ini tidak bisa dipecah menjadi animasi-animasi yang saling lepas waktunya. Pertama, perubahan yang berjauhan itu perlu disinkronkan waktunya. Kedua, keempat area yang berubah saling tumpang tindih baik arah horizontal maupun vertikal. Artinya 'tabel' HTML sederhana tidak bisa menyatukan kembali subanimasi menjadi satu kesatuan utuh tanpa semacam akrobat CSS. Bisakah Anda membuktikan saya salah? FUTURE: reference to a better example of animating 'two distant objects'. in 'Animation Handling', say involving two separately moving objects.
Menghapus frame jeda nol - membuang pembaruan antara
Tentu kadang Anda tidak tertarik pada frame antara tambahan itu, atau ingin menghapusnya dari animasi, menyisakan hanya frame yang benar-benar ditampilkan ke pengguna selama rentang waktu tertentu. Animasi itu tidak bisa sekadar di-coalesce lalu diproses dengan metode 'RemoveDups', karena tidak semua 'frame antara' mirip dengan frame di sekitarnya, sehingga bukan duplikat. Namun karena frame jenis ini punya jeda waktu nol, tersedia metode "-layers" khusus lain, '**RemoveZero**', yang menghapus frame mana pun berjeda waktu nol. Metode yang sama juga menghapus frame yang ditambahkan lewat teknik penggandaan frame dan 'OptimizePlus'. Misalnya...
magick moving_hole_split_oplus.gif -coalesce -layers RemoveZero gif:- |\
gif_anim_montage - moving_hole_split_rmzero_frames.gif
Yang kembali mengembalikan animasi ke frame-frame yang dilihat pengguna saja, menyederhanakan animasinya. Tentu, setelah frame antara jeda nol dihapus, sangat sulit menambahkannya kembali karena informasi perubahan yang dikandungnya hilang. Akibatnya animasi itu mungkin tidak teroptimisasi frame dengan baik sesudahnya. Optimisasi toh salah satu tujuan utama frame semacam itu.
Hasil dan rangkuman optimisasi frame
Mari kita rangkum optimisasi kita atas animasi lubang bergerak...
Seperti yang terlihat, dengan penanganan frame yang cukup kompleks, dengan bantuan IM dan sedikit campur tangan manusia, kita berhasil mengoptimisasi frame animasi 'lubang bergerak' menjadi hampir separuh ukuran aslinya, meski dengan jumlah frame hampir tiga kali lipat dari aslinya. Tentu hasil bisa sangat bervariasi dari animasi ke animasi, tetapi teknik yang kita pakai untuk optimisasi frame tetap sama. Hanya butuh sedikit kecermatan dan pertimbangan, yang manusia jago melakukannya, dan komputer tidak. | _Ada satu catatan: IM semestinya tidak hanya memperhitungkan jumlah piksel pada set frame yang sedang ditinjau, tetapi juga ukuran keseluruhan frame ekstra yang ditambahkan, dan mungkin hasil kompresi keseluruhan yang diperoleh, saat mengambil keputusan cara mengoptimisasi frame gambar.
Di sisi lain, IM juga tidak melihat penghematan jumlah piksel yang mungkin timbul di luar frame-frame yang terlibat langsung. Artinya, ukuran frame-frame berikutnya juga bisa mengecil sebagai akibat penggandaan frame, atau metode disposal yang dipakai. Ini terutama benar ketika pilihannya adalah memakai metode 'dispose previous image', yang bisa memberi pengurangan jumlah piksel besar di bagian belakang urutan animasi, alih-alih langsung di frame berikutnya. Pilihan yang baik di sini sering butuh masukan manusia.
Karena itu saya tidak bisa menjamin IM akan menghasilkan pilihan optimisasi terbaik untuk animasi tertentu. Namun ia jelas berusaha keras, tanpa memakai rekursi, untuk membuat pilihan itu. Artinya, hanya memakai hitungan piksel langsung untuk keputusannya.
Algoritma rekursif, yang membuat satu pilihan lalu melihat ukuran akhir terbaik animasi yang dihasilkan pilihan itu (termasuk pilihan rekursif selanjutnya), bisa menghasilkan optimisasi terbaik yang terjamin. Namun ia juga bisa menjadi operator yang luar biasa lambat, dan untuk animasi besar bisa butuh bertahun-tahun untuk mengambil keputusan akhir. Ia juga perlu menyertakan pilihan optimisasi kompresi, karena ini bisa memengaruhi hasil akhirnya. Dengan kata lain, meski algoritma semacam itu bisa menjamin optimisasi terbaik, ongkos komputasinya berat.
Tentu, manusia yang mengenal betul apa yang ingin dicapai animasinya umumnya berhasil lebih baik pada animasi kompleks, seperti terlihat di atas pada memecah pembaruan frame.
Jika Anda ingin mencoba membuat operator optimisasi GIF rekursif, silakan. Saya akan membantu sebisa saya. Ia akan mengalahkan hampir semua program optimisasi GIF lain di pasaran. Kebanyakan pengembang animasi GIF mungkin juga akan sangat berterima kasih atas upaya Anda (secara finansial)._
Penanganan semitransparansi
Format file GIF tidak mengizinkan pemakaian piksel semitransparan (lihat transparansi boolean GIF). Ini fakta, dan sebelum bisa mengoptimisasi animasi dengan benar, atau bahkan menyimpannya ke format GIF, piksel semitransparan yang mungkin ada perlu ditangani dengan cara yang cocok untuk animasinya. Secara bawaan, jika Anda tidak menangani piksel-piksel ini, IM memakai threshold 50% untuk mengubahnya menjadi transparan penuh atau buram penuh. Namun itu belum tentu cara terbaik menangani masalahnya, khususnya pada gambar yang berisi area semitransparan luas, seperti efek bayangan. Sebagai contoh, saya ingin membuat animasi teleport Asgard ala Stargate yang bisa menerima hampir subgambar apa pun sebagai objek yang diteleportasi.
magick -channel RGBA -fill white \
\( medical.gif -repage 100x100+34+65 -coalesce -set delay 200 \) \
\( +clone -motion-blur 0x20+90 -blur 0x3 -colorize 100% \
+clone -colorize 30% +swap -composite -set delay 10 \) \
\( +clone -roll +0-20 -blur 0x3 -colorize 30% \
-motion-blur 0x15+90 -motion-blur 0x15-90 -set delay 10 \) \
\( +clone -colorize 30% \
-motion-blur 0x30+90 -blur 0x5 -crop +0+10\! \) \
\( +clone -motion-blur 0x50+90 -blur 0x2 -crop +0+20\! \) \
\( +page -size 100x100 xc:none -set delay 200 \) \
-set dispose background -coalesce -loop 0 teleport.miff
gif_anim_montage teleport.miff teleport_frames.png
Saya sengaja membiarkan animasinya dalam format file internal IM MIFF:, karena ini memastikan gambar asli terjaga tanpa modifikasi, dan memakai format file PNG: untuk menampilkan frame-frame-nya agar Anda bisa melihat semua piksel semitransparan yang dikandungnya! Ini penting bukan hanya untuk animasi dengan piksel semitransparan, tetapi juga yang berwarna banyak. Begitu urutan gambar tersimpan ke GIF, peluang menghasilkan optimisasi warna yang baik berubah dari bagus menjadi sulit. Oke, saya sudah punya urutan animasi. Jika saya coba menyimpannya langsung sebagai GIF, IM begitu saja men-threshold semua piksel semitransparan itu. |
magick teleport.miff teleport_thres.gif
gif_anim_montage teleport_thres.gif teleport_thres_frames.gif
Hasilnya sama sekali tidak mirip dengan yang kita inginkan. Penanganan transparansi 50% bawaan membuat animasinya tampak seperti 'telur' yang menyusut. Jelas bukan yang ingin saya capai dengan animasi ini.. Jika penanganan transparansi jenis ini bisa diterima, beginilah cara menerapkannya, sebelum melanjutkan ke optimisasi lain...
magick teleport.miff -channel A -threshold 50% +channel \
...do further processing now... teleport.gif
Keuntungan ekstra cara DIY di atas: level threshold-nya bisa dikendalikan. Misalnya '10%' untuk membuang hampir semua piksel semitransparan yang ada, sampai '90%' untuk membuat semuanya buram. |
magick teleport.miff -channel A -threshold 90% +channel teleport_thres90.gif
gif_anim_montage teleport_thres90.gif teleport_thres90_frames.gif
Namun menerapkan threshold untuk animasi seperti ini bukan solusi yang baik, karena benar-benar merusak efek transparansi yang ingin saya capai. Solusi keseluruhan terbaik untuk mempertahankan semua efek khusus pada animasi di atas adalah cukup menambahkan latar belakang warna polos. |
magick teleport.miff -bordercolor skyblue \
-coalesce -border 0 teleport_bgnd.gif
gif_anim_montage teleport_bgnd.gif teleport_bgnd_frames.gif
Ini membuang SEMUA transparansi dari animasi, tetapi dengan ongkos animasinya hanya bekerja di atas warna latar tertentu. Namun jika Anda membuat animasi untuk halaman web tertentu, itu bisa jadi cukup bisa diterima. Perhatikan bahwa untuk gambar bergaris tepi tajam, memakai pola dither seperti ini dapat menghasilkan garis tepi 'berbintik' pada tepian tajam itu. Karena itu tidak disarankan untuk kasus umum. Solusi lain adalah mencoba membangkitkan pola piksel transparan dan buram tertentu demi mempertahankan semitransparansi gambar. Dan untuk ini IM menawarkan beragam opsi dithering yang bisa menyelesaikannya. FUTURE: some link to a to be created section on transparency dithering, such asQuantization and Dithering. Perhatikan bahwa solusi pertama yang tampak jelas, yaitu dithering monokrom pada kanal alpha, ternyata tidak sederhana, dan mungkin butuh komposisi multi-gambar tingkat lanjut agar benar. Solusi sederhana adalah memakai teknik ordered dither piksel terdifusi, yang bisa dibatasi hanya pada kanal alpha, untuk membuang piksel semitransparan. |
magick teleport.miff -channel A -ordered-dither o8x8 teleport_od.gif
gif_anim_montage teleport_od.gif teleport_od_frames.gif
Hasilnya lumayan, tetapi tampak seperti objek yang melarut ketimbang berteleportasi. Memakai halftone memberi efek yang jauh lebih bagus karena pola transparansinya lebih tegas. |
magick teleport.miff -channel A -ordered-dither h8x8a teleport_htone.gif
gif_anim_montage teleport_htone.gif teleport_htone_frames.gif
Namun untuk animasi yang satu ini, saya menemukan bahwa memakai peta dither rancangan pengguna untuk menghasilkan garis-garis vertikal (dari pola dither garis horizontal) memberi efek yang justru memperkuat animasi teleport sambil membuang piksel semitransparan. |
magick teleport.miff -rotate 90 \
-channel A -ordered-dither hlines -rotate -90 teleport_lines.gif
gif_anim_montage teleport_lines.gif teleport_lines_frames.gif
Jadi seperti yang terlihat, ada cukup banyak kemungkinan untuk menangani semitransparansi dalam animasi GIF.
Optimisasi warna
Menangani piksel semitransparan baru keterbatasan pertama format file GIF. Berikutnya adalah batas 256 warna untuk tiap tabel warna dalam animasi. Tabel warna terpisah untuk tiap frame diperbolehkan. Artinya satu animasi bisa punya lebih dari 256 warna. Namun itu pun tidak selalu ide bagus. Jika Anda hanya ingin rangkuman cepat opsi optimisasi warna yang tersedia, saya sarankan langsung ke contoh konversi video ke GIF, tempat masalah warna animasi berada di titik terburuknya.
Masalah warna GIF
Animasi GIF khususnya bermasalah dalam penanganan warna: pertama ia tidak mengizinkan warna semitransparan, lalu ada batas 256 warna per frame, atau batas 256 warna global. Terakhir, optimisasi frame terbaik sekalipun tidak akan bekerja baik kecuali warna yang dipakai sebuah piksel di satu frame juga cocok dengan warna yang sama di frame berikutnya, ketika bagian gambar itu TIDAK berubah! Ini mungkin terdengar mudah, tetapi reduksi warna sendiri adalah bidang yang luar biasa kompleks, sampai butuh bagian tersendiri yang lengkap di contoh penggunaan IM. Masalah warna sebenarnya adalah alasan kebanyakan animasi GIF yang ditemukan di World Wide Web bergaya kartun, atau tampak sangat buruk. Apalagi jika diubah ukurannya dari versi animasi yang lebih besar. Pada mengubah ukuran animasi, optimisasi warna kemungkinan malah butuh usaha lebih besar daripada proses pengubahan ukurannya sendiri. Di sini saya berasumsi Anda punya sumber asli animasinya. Tapi itu tidak selalu mungkin; jadi jika Anda mengoptimisasi animasi GIF hasil modifikasi, kewaspadaan ekstra mungkin diperlukan. Namun jika Anda punya animasi dengan warna terlalu banyak, hal pertama yang perlu diingat adalah...
Jangan menyimpan langsung ke format GIF,
pakailah format file MIFF, ATAU gambar-gambar PNG terpisah.
Begitu animasi disimpan ke GIF, kendali atas upaya optimisasi warna GIF itu pun hilang, dan kemungkinan mendapat animasi GIF yang tampak sangat buruk dan tidak akan teroptimisasi baik lewat berbagai teknik optimisasi frame.
Animasi speed - animasi dengan warna terlalu banyak
Pertama kita perlu membangkitkan animasi GIF dengan jumlah warna sangat besar, agar kita benar-benar bisa menguji masalah-masalah dalam optimisasi warna. | |
magick -dispose none -channel RGBA \
\( medical.gif -repage 100x60+5+14 -coalesce -set delay 100 \) \
\( medical.gif -repage 100x44+34+6 -coalesce -set delay 10 \
-motion-blur 0x12+0 -motion-blur 0x12+180 -wave -8x200 \) \
\( medical.gif -repage 100x60+63+14 -coalesce -set delay 100 \) \
\( medical.gif -repage 100x44+34+6 -coalesce -set delay 10 \
-motion-blur 0x12+0 -motion-blur 0x12+180 -wave +8x200 \) \
null: \( +page -size 120x15 xc:SkyBlue xc:RoyalBlue \
-size 120x70 gradient:SkyBlue-RoyalBlue \
+swap -append -blur 0x3 -background white -rotate -25 \
\) -gravity center -compose DstOver -layers Composite \
-loop 0 speed.miff
magick speed.miff speed.gif
gif_anim_montage speed.gif speed_frames.gif
![[IM Output]](../static/img/anim_opt/speed.gif)
![[IM Output]](../static/img/anim_opt/speed_frames.gif)
Perhatikan bahwa saya tidak menyimpan animasinya langsung ke format GIF, melainkan menyimpannya lebih dulu dalam file format MIFF, "speed.miff". Ini menjaga semua aspek animasi yang baru dibuat (atau dimodifikasi), termasuk metadata GIF, jeda waktu, serta semua warna gambar tanpa distorsi. Baru setelah animasi aslinya terjaga, saya mengonversinya langsung ke format GIF. Dengan begitu saya bisa memperlihatkan apa yang ingin dicapai kode di atas, dan mengapa saya menamainya 'speed'. Ini juga untuk menyediakan animasi GIF pembanding dasar untuk dipelajari dan dibandingkan nanti. Jadi mari kita lihat berbagai detail animasi asli kita..
magick identify -format "Number of Frames: %n\n" speed.miff | head -1
magick identify -format "Colors in Frame %p: %k\n" speed.miff
magick speed.miff +append -format "Total Number of Colors: %k" info:
Seperti yang terlihat, tiap gambar dalam animasi punya jumlah warna sangat besar. Bukan hanya tiap frame punya jumlah warna berbeda; frame pertama dan ketiga sangat mirip warnanya, meski tidak persis sama. Namun format file GIF hanya bisa menyimpan maksimum 256 warna per frame, dan ketika ImageMagick menyimpannya ke format GIF, ia melakukannya dengan cara tercepat dan terbodoh yang mungkin... Ia mereduksi jumlah warna tiap frame dalam animasi (proses yang disebut kuantisasi warna)...
magick identify -format "Colors in Frame %p: %k\n" speed.gif
magick speed.gif +append -format "Total Number of Colors: %k" info:
Karena jumlah warna hasil reduksi tiap frame sedikit berbeda, IM juga perlu menyertakan colormap terpisah untuk tiap frame animasi. Artinya file GIF itu punya satu 'tabel warna global' — itu selalu ada — tetapi juga tiga 'tabel warna lokal' terpisah. Perintah "magick identify" tidak bisa memberi tahu berapa banyak tabel warna lokal semacam itu dalam sebuah file GIF, karena informasi itu terlalu spesifik format, dan tidak penting bagi pemrosesan gambar yang biasa dilakukan IM. Namun program yang lebih spesifik "Giftrans" bisa memberi tahu berapa banyak tabel warna lokal level rendah yang dipakai...
giftrans -L speed.gif 2>&1 | grep -c "Local Color Table:"
Seperti yang terlihat, animasi ini punya
tabel warna lokal, satu lebih sedikit dari jumlah frame dalam gambar, persis seperti prediksi saya. Bukan hanya tiap frame punya set warna berbeda, tetapi juga pola warna yang sedikit berbeda (pola dither gambar), seperti dijelaskan di masalah dengan dither koreksi error. Normalnya operasi bawaan kuantisasi warna dan dithering IM ini sangat baik, dan sangat cocok untuk gambar, terutama foto kehidupan nyata. Bahkan frame-frame individual sebuah animasi umumnya akan tampak bagus. Semua masalahnya muncul ketika kita kemudian mencoba merangkai frame-frame yang direduksi warnanya sendiri-sendiri itu menjadi satu urutan animasi.
Optimisasi frame sebelum optimisasi warna?
Seperti terlihat di atas, menyimpan animasi langsung ke format GIF memang bisa, tetapi akan muncul cukup banyak perbedaan warna dari satu frame ke berikutnya, yang buruk bagi optimisasi frame selanjutnya (seperti akan Anda lihat nanti). Untuk mencegah perbedaan warna menimbulkan masalah itu, lakukan optimisasi frame sebelum menyimpan animasi, sehingga terhindar dari perbedaan warna antar-frame yang timbul. Namun berhati-hatilah: melakukan optimisasi frame sebelum reduksi warna mengubah dinamika reduksi warnanya. Sering kali area statis yang tak bergerak lebih sedikit muncul di subframe teroptimisasi, yang berarti kuantisasi warna untuk frame itu bisa memberi warna-warna tersebut bobot lebih rendah, dan karenanya warna lebih sedikit.
Optimisasi warna fuzzy
Namun kadang Anda tidak punya akses ke animasi asli sebelum ia disimpan ke format GIF. Ini terutama benar jika Anda mengunduh animasinya dari WWW. Artinya animasi itu sudah membawa semua distorsi warna GIF di dalamnya, yang menimbulkan masalah bagi optimisasi selanjutnya. Nah, karena set warna yang dipakai sedikit berbeda dari satu frame ke berikutnya, dan pola piksel yang dipakai tiap frame animasi pun berbeda, tiap frame bisa dianggap gambar yang sama sekali berbeda. Sebagai contoh, mari kita bandingkan frame pertama dan ketiga, yang berbagi sebagian besar gambar latar belakang yang sama.... |
magick compare speed.gif'[0,2]' speed_compare.gif
![[IM Output]](../static/img/anim_opt/speed_compare.gif)
Area merah pada contoh di atas memperlihatkan dua area persegi polos yang memang berbeda, persis seperti dugaan Anda. Tetapi ia juga memperlihatkan pita-pita perbedaan warna yang menggarisi latar belakang kedua frame. Ini mewakili pola dither yang 'bergolak' di sepanjang tepi gradien latar, tempat piksel warna berbeda dipakai untuk merepresentasikan latar yang persis sama. Ini pun pasangan frame dengan gangguan latar paling sedikit akibat pemakaian set warna dan pola dither yang berbeda. Perbedaan frame beruntun yang sebenarnya jauh lebih buruk, menghasilkan perbedaan merah yang nyaris polos. | _Perbedaan gambar seperti ini juga jadi masalah jika gambar sumber Anda disimpan dalam format gambar JPEG. Format ini memakai metode kompresi lossy yang (bahkan pada kualitas 100%) menimbulkan perbedaan warna kecil pada gambar. Namun perbedaannya umumnya terbatas pada halo di sekitar area yang benar-benar berbeda, bukan di seluruh gambar.
Yang bisa saya katakan hanyalah: hindari gambar JPEG untuk animasi, kecuali jika satu gambar tunggal dipakai sebagai gambar latar statis untuk SEMUA frame Anda._
Karena begitu banyak piksel animasi yang berbeda dari satu frame ke berikutnya, tidak mengherankan ketika kita mencoba mengoptimisasi frame animasi itu, kita tidak mendapat optimisasi sama sekali...
magick speed.gif -layers OptimizeFrame speed_opt2.gif
gif_anim_montage speed_opt2.gif speed_opt2_frames.gif
Namun sebagian besar perbedaan warna piksel di antara bagian animasi yang tak berubah sebenarnya cukup kecil. Kalau tidak begitu, berarti reduksi warnanya tidak bagus. Artinya, dengan sedikit melonggarkan pembandingan warna IM, perbedaan warna minor bisa diabaikan. Ini dilakukan dengan mengeset faktor fuzz yang sesuai.
magick speed.gif -fuzz 5% -layers OptimizeFrame speed_opt3.gif
gif_anim_montage speed_opt3.gif speed_opt3_frames.gif
Seperti yang terlihat, dengan tambahan faktor fuzz kecil, IM kini mengabaikan piksel yang hanya sedikit berbeda, menghasilkan optimisasi frame yang lumayan. Seberapa besar faktor fuzz yang Anda butuhkan bergantung pada seberapa sulit IM mereduksi warna gambar aslinya. Dalam kasus ini tidak terlalu sulit, jadi hanya butuh faktor sangat kecil. Jika faktor fuzz kecil memberi hasil yang bisa diterima, set saja untuk optimisasi frame dan optimisasi transparansi Anda. Hanya saja, ingat masih ada tabel warna terpisah untuk tiap frame yang harus diurus, dan itulah topik pembahasan berikutnya. Perhatikan pula bahwa optimisasi frame tadi memutuskan memakai 'disposal previous' untuk frame kedua. Artinya, setelah menampilkan frame kedua, kembalikan gambar ke disposal frame sebelumnya (gambar pertama) sebelum meng-overlay. Ini menghasilkan ukuran gambar overlay lebih kecil dibanding tanpa disposal sama sekali. Jika Anda hanya ingin animasi overlay sederhana, memakai disposal none di sepanjang animasi, operator lama deconstruct (dikenal juga sebagai layers CompareAny) bisa dipakai untuk membangkitkannya.
magick speed.gif -fuzz 5% -deconstruct speed_opt4.gif
gif_anim_montage speed_opt4.gif speed_opt4_frames.gif
Membangkitkan satu tabel warna global
Nah, karena tiap frame punya set warna berbeda, IM terpaksa menyimpan gambar dengan tabel warna terpisah untuk tiap frame: satu global untuk frame pertama, dan 3 tabel warna lokal untuk frame-frame berikutnya. Misalnya, di sini saya memakai program sangat sederhana "Giftrans" untuk melaporkan berapa banyak tabel warna frame yang dibuat.
giftrans -L speed.gif 2>&1 | grep -c "Local Color Table:"
Untuk animasi yang sepenuhnya hasil coalesce (atau seperti gulungan film), punya tabel warna terpisah tiap frame sepenuhnya wajar dan masuk akal, dan dalam situasi itu ini bukan masalah. Artinya, untuk slideshow gambar-gambar yang sangat berbeda, tabel warna terpisah memberi hasil tampilan terbaik. Karena itulah ini perilaku kerja normal IM. Namun semua tabel warna ekstra ini sangat mahal, karena tiap tabel warna bisa memakan banyak ruang. Sampai 768 byte (256 warna × 3 byte per warna, atau 3/4 kilobyte) untuk tiap frame dalam gambar. Tidak hanya itu, kompresi GIF tidak mengompres tabel warna ini, hanya data piksel! Jika ruang file sebesar itu untuk tabel warna terpisah jadi masalah, apalagi untuk gambar yang warnanya tidak banyak berubah, seperti kebanyakan animasi GIF, IM bisa dibuat hanya memakai tabel warna global yang memang dibutuhkan, tanpa menambahkan tabel warna lokal apa pun. ---Untuk membuang colormap lokal, semua gambar harus menjadi tipe palette dan semuanya memakai palet yang sama. Di baris perintah Anda bisa melakukannya dengan mengeset "-map image" untuk mendefinisikan palet bersama. Opsi -colors tidak bisa dipakai karena ia bekerja per gambar individual. Solusi baris perintahnya adalah opsi khusus "+map", yang melakukan reduksi warna global ke satu palet bersama yang ditambahkan ke semua gambar. CATATAN: perubahan apa pun pada gambar kemungkinan membatalkan paletnya, jadi meskipun reduksi warna harus dilakukan SEBELUM optimisasi frame dan/atau kompresi GIF, palet bersama harus dipasang terakhir, tepat sebelum menyimpan. Jika "+map" tidak perlu mereduksi jumlah warna dalam sebuah gambar, ia tidak akan melakukannya, juga tidak men-dither warna, melainkan hanya menambahkan palet bersama ke semua gambar. --- IM bisa membangkitkan satu tabel warna global, jika semua frame memakai palet warna yang sama. Di IM, palet warna hanya melekat pada gambar bila dibaca dari format gambar yang memakai palet semacam itu, atau bila diberikan lewat operator reduksi warna "-map". Lihat dither dengan colormap terdefinisi untuk detailnya. Satu cara membangkitkan tabel warna tunggal ini adalah cukup meng-"-append" semua frame jadi satu, lalu memakai perintah "-colors" untuk mereduksi jumlah warna ke set minimal (kurang dari 256, atau lebih kecil jika Anda ingin tabel warna lebih kecil lagi). Tabel warna hasilnya lalu diterapkan ke gambar asli memakai "-map". Misalnya, di sini gambar direduksi ke satu set 64 warna. Ini memakai register MPR dalam memori khusus untuk memberikan peta warna yang dibangkitkan ke perintah "-map". |
magick speed.gif \
\( -clone 0--1 -background none +append \
-quantize transparent -colors 63 -unique-colors \
-write mpr:cmap +delete \) \
-map mpr:cmap speed_cmap.gif
![[IM Output]](../static/img/anim_opt/speed_cmap.gif)
Sekarang jika Anda memeriksa animasi hasilnya memakai "Giftrans", akan terlihat gambar itu kini memakai satu tabel warna 'global', bukan tabel warna terpisah untuk tiap frame. | _Saya memakai warna "-background" 'None' sebelum menyatukan gambar-gambar dengan append, sehingga cara ini bisa dipakai pada animasi yang belum di-coalesce, tanpa kemungkinan menambah warna ekstra yang tak dibutuhkan.
Pengaturan "-quantize" khusus dengan ruang warna 'transparent' dipakai untuk memastikan IM tidak mencoba membangkitkan warna semitransparan di colormap-nya. Hal yang sia-sia, karena kita menyimpan hasilnya ke GIF yang tidak bisa menangani semitransparansi.
Terakhir, saya mereduksi warna ke 63 warna, untuk menyisakan tempat bagi satu warna transparan. Sebagian animasi butuh transparansi, sementara yang lain (seperti yang ini) mungkin masih membutuhkannya nanti untuk optimisasi kompresi._
Untuk mempermudah, IM juga menyediakan opsi khusus "+map" yang membangkitkan satu peta warna bersama (256 warna) atas semua frame dan menerapkannya secara global. Ini jauh lebih sederhana daripada cara DIY di atas. |
magick speed.miff -alpha off +map speed_map.gif
![[IM Output]](../static/img/anim_opt/speed_map.gif)
Hasilnya
tabel warna 'lokal' (alias ekstra yang tak diinginkan) pada gambar hasil. Saya akan memakai versi animasi bertabel warna tunggal ini untuk bagian-bagian optimisasi berikutnya, meski sebenarnya ini bisa dilakukan kapan pun dalam optimisasi animasi Anda, dan terutama sebelum penyimpanan akhir. Sebagai hasil optimisasi tabel warna, animasi yang tadinya
byte pada GIF hasil konversi langsung kini menjadi
byte setelah memakai operator "+map". Makin banyak frame (dan 'tabel warna lokal') sebuah animasi, makin besar penghematannya. Nah, karena modifikasi apa pun pada animasi umumnya membuang palet tersimpan tiap gambar, penting agar operator "+map" menjadi operasi terakhir sebelum animasi disimpan ke GIF. Ingat
Pembuangan colormap lokal harus menjadi optimisasi terakhir, sebelum menyimpan ke format GIF.
Ordered dither, membuang 'semut' statis
Sedang dikerjakan
Perhatikan bahwa pada semua teknik yang kita lihat sejauh ini, pola dither bisa berubah dari satu overlay ke overlay berikutnya. Golakan piksel yang bisa tampak seperti 'semut' statis di TV.
... small number of colors ...
Dengan optimisasi frame pada area kecil yang tak bergerak, bahkan bisa muncul area 'semut' berbentuk persegi yang tampak lebih buruk lagi. ... Ordered Dither ... Untuk sementara, lihat video ke GIF, rangkuman optimisasi yang lebih praktis dan tidak sedetail ini.
Optimisasi kompresi
Begitu animasi Anda tersimpan dalam format GIF, dengan semitransparansi tertangani dan optimisasi warna serta frame diterapkan, masih bisa diraih pengurangan ukuran file lebih kecil dengan menyesuaikan diri pada algoritma kompresi GIF. Kompresi LZW atau kompresi run-length yang bisa dipakai format file GIF akan mengompres lebih baik jika menemukan area warna konstan yang lebih luas, atau urutan piksel yang berulang-ulang.
Optimisasi transparansi
| Seperti terlihat pada optimisasi frame, gambar yang di-overlay sering hanya mengulang apa yang sudah ditampilkan. Yaitu meng-overlay piksel berwarna sama dengan yang sudah ada setelah metode disposal GIF diterapkan. Tapi buat apa repot mengulang piksel-piksel itu. Jika Anda sudah memakai transparansi dalam gambar, warna piksel transparan sudah tersedia. Dengan mengonversi area-area itu menjadi transparan, diperoleh area piksel transparan seragam yang lebih luas. Itu bisa terkompres lebih baik daripada memakai campuran warna berbeda yang dibutuhkan untuk mencocokkan area sama yang di-overlay. Misalnya, ini animasi overlay sederhana yang sudah dioptimisasi frame... | ![]() |
![]() |
|---|---|---|
Sekarang mari kita pakai metode "-layers" '**OptimizeTransparency**' (ditambahkan di IM v6.3.4-4) untuk mengganti piksel apa pun yang tidak mengubah hasil tampilan dengan transparansi. |
magick bunny_bgnd.gif -layers OptimizeTransparency \
+map bunny_bgnd_opttrans.gif
gif_anim_montage bunny_bgnd_opttrans.gif bunny_bgnd_opttrans_frames.gif
![[IM Output]](../static/img/anim_opt/bunny_bgnd_opttrans.gif)
![[IM Output]](../static/img/anim_opt/bunny_bgnd_opttrans_frames.gif)
Seperti yang terlihat, subframe-nya kini punya area transparan luas, yang tidak memengaruhi hasil akhir animasi. Area yang pikselnya perlu diubah tetap di-overlay, tetapi area yang tidak berubah dijadikan transparan. Termasuk juga bagian dalam objek yang dianimasikan, meninggalkan 'lubang-lubang' yang tampak cukup mengerikan. Karena area transparan konstan yang lebih luas itu (secara teori) terkompres lebih baik, animasi 'berantakan' hasilnya jauh lebih kecil: ukuran file turun dari hasil optimisasi frame
byte menjadi
byte. Penghematan cukup besar untuk usaha yang sangat kecil. Perhatikan bahwa metode optimisasi ini tidak mensyaratkan animasi hasil coalesce, dan ukuran subframe dibiarkan tak berubah, demi menjaga kebutuhan disposal frame ini dan frame berikutnya. Jadi penghematannya murni dari rasio kompresi yang membaik untuk jumlah piksel yang sama dalam animasi, bukan dari berkurangnya jumlah piksel yang tersimpan di file. Karena itu ia sebaiknya dilakukan setelah semua optimisasi frame yang dibutuhkan selesai, sebagai salah satu langkah optimisasi terakhir Anda.
FUTURE: link to a 'remove background' from animation
Tentu, seperti kebanyakan metode "-layers" lain (pembandingan maupun optimisasi), faktor fuzz bisa ditentukan untuk mengatur 'seberapa mirip' warna dianggap sama. Dengan begitu animasi yang di-dither warnanya dengan buruk pun bisa ditangani, meski jika Anda sudah mempelajari optimisasi warna di atas, masalah itu semestinya tidak muncul. Alat GIF animasi gratis "**InterGIF**" juga menyediakan jenis optimisasi kompresi transparansi yang sama seperti di atas, tetapi tanpa kemampuan mendukung 'faktor fuzz' untuk ikut menjadikan perubahan warna yang 'mirip' transparan. Saya tidak merekomendasikannya, kecuali sebagai alternatif saat IM tidak tersedia.
Optimisasi LZW - (non-IM)
Sebagian aplikasi bisa mengoptimisasi lebih jauh rasio kompresi gambar-gambar dalam animasi supaya lebih kecil lagi. Namun untuk itu dibutuhkan pengetahuan khusus tentang kompresi LZW yang biasa dipakai format file gambar GIF. Pada dasarnya, jika suatu urutan piksel tertentu sudah pernah ditangani algoritma kompresi LZW, ia tidak akan repot-repot mengubahnya menjadi piksel transparan, karena melakukannya tidak memperbaiki kompresi gambar. Kedengarannya aneh, tapi berhasil. Sayangnya ImageMagick tidak melakukan ini, karena prosesnya begitu kompleks sehingga butuh keterampilan dan sumber daya besar untuk mendapatkan heuristik yang cukup baik yang memberi hasil bagus untuk kasus umum. Namun saya bisa memberi contoh praktis teknik ini memakai aplikasi "Gifsicle" pada level optimisasi tertingginya '-O2'. |
gifsicle -O2 bunny_bgnd.gif -o bunny_bgnd_lzw_gifsicle.gif
gif_anim_montage bunny_bgnd_lzw_gifsicle.gif bunny_bgnd_lzw_frames.gif
Optimisasi kompresi LZW mengecilkan gambar dari
byte dengan optimisasi transparansi sederhana, menjadi
byte untuk "Gifsicle". Bukan perbaikan besar. Namun aspek yang lebih penting: meskipun optimisasi LZW mengubah piksel yang tak berubah menjadi transparan (seperti yang kita lakukan lewat optimisasi transparansi di atas), ia tidak mengubah urutan piksel yang sudah pernah terlihat. Artinya, hanya kelompok piksel yang belum berulang dalam animasi yang diubah, karena piksel yang sudah berulang itu (diasumsikan) sudah terkompres baik lewat pola kompresi LZW. Perhatikan bahwa pemilihan piksel mana yang harus dijadikan transparan, demi membangkitkan pola piksel berulang, sangat kompleks dan sulit, bahkan bisa bergantung pada implementasi LZW persisnya pula. Ia heuristik, bukan algoritma yang bisa diprediksi sempurna. Karena itu program yang berbeda umumnya memberi hasil berbeda tergantung gambar yang dikompres. Satu program bisa memberi rasio kompresi lebih baik untuk satu gambar, dan program lain bisa lebih baik untuk gambar lain.
Optimisasi LZW lossy - (non-IM)
Metode perbaikan kompresi lain melibatkan modifikasi kecil warna piksel itu sendiri ke 'padanan warna terdekat' demi meningkatkan pengulangan rujukan warna dalam gambar. Pola berulang tentu terkompres lebih baik, sehingga bisa memberi rasio kompresi lebih tinggi. Fork dari aplikasi "Gifsicle" sebelumnya, dikenal sebagai giflossy, juga menghasilkan program 'gifsicle', tetapi dengan opsi memodifikasi gambar secara kecil-kecilan (ia 'lossy') untuk mengecilkan ukuran gambar GIF, terutama animasi, jauh lebih lanjut. | |
gifsicle -O3 --lossy=80 bunny_bgnd.gif -o bunny_bgnd_giflossy.gif
gif_anim_montage bunny_bgnd_giflossy.gif bunny_bgnd_lossy_frames.gif
Dibuat: 22 Maret 2007 ((subdivisi dari "animation")
Diperbarui: 23 April 2007
Penulis: Anthony Thyssen, Anthony.Thyssen@gmail.com
Contoh dibangkitkan dengan: ![[version image]](../static/img/anim_opt/version.gif)
URL: https://usage.imagemagick.org/anim_opt/
Ini menghasilkan ukuran
, pengurangan ukuran sepertiga yang menakjubkan. Sayangnya metode ini melibatkan pengubahan gambar hasilnya, sehingga optimisasinya lossy, karena bisa kehilangan informasi warna yang halus. Sisi baiknya, frame-frame individual bisa dikompres sendiri-sendiri, alih-alih lewat optimisasi antar-frame. Di sini misalnya saya membangkitkan gambar selisih antara kompresi LWZ non-lossy sebelumnya dan kompresi LWZ lossy. |
magick compare bunny_bgnd_lossy_frames.gif bunny_bgnd_lzw_frames.gif \
bunny_bgnd_diff_frames.gif
| Seperti yang terlihat, hampir semua modifikasi warna terjadi pada gambar latar 'rumput' asli, bukan pada kelinci kartunnya. Pada dasarnya ia memodifikasi sedikit saja, cukup untuk membuat perbedaan besar, dan untuk gambar ini tanpa perbedaan tampilan yang benar-benar kentara. Tentu kuantisasi warna dan dithering sendiri adalah operasi lossy dan biasanya toh dibutuhkan, jadi memakai metode kompresi lossy untuk gambar GIF dan animasi GIF tidak dianggap buruk-buruk amat. | Contoh lain algoritma semacam ini yang dipatenkan untuk dipakai Photoshop: lihat US Patent 7031540 - Transformation to increase the lempel-ziv compressibility of images with minimal visual distortion. Bacaannya berat, tetapi merinci metode yang dipakainya untuk mencapai kompresi lebih baik. |
|---|---|
Optimisasi LZW dengan ordered dither
Karena proses dithering biasanya lebih lossy daripada optimisasi LZW, solusi yang lebih baik mungkin adalah mencoba memasukkan pola-pola berulang itu sebagai bagian dari proses dithering. Itu bisa dicapai dengan memakai ordered dithering untuk menghasilkan pola semacam itu, dan dengan demikian penghematan kompresi LZW yang jauh lebih kuat daripada semua metode optimisasi LZW sebelumnya. Sebagai bonus, ia juga bisa memperbaiki optimisasi frame animasi kehidupan nyata berlatar statis. Itu terutama benar bila latarnya dibersihkan secara artifisial sehingga benar-benar menjadi latar statis yang tak berubah. Tentu optimisasi kompresi ordered dither hanya bekerja untuk gambar yang belum pernah di-dither atau dioptimisasi warnanya. Jadi ia hanya bekerja untuk animasi yang belum dioptimisasi untuk format gambar GIF. Selain itu, saat ini ordered dither IM hanya bekerja untuk palet warna seragam. IM belum punya implementasi ordered dither untuk palet 'warna terbaik' atau 'palet buatan pengguna', meski saya pernah melihat program yang memakai algoritma semacam itu untuk palet warna yang sangat terbatas (dan tetap). Tahukah Anda algoritma semacam itu? Untuk contoh praktis pemakaian ordered dither demi optimisasi kompresi LZW yang lebih baik, lihat video dengan ordered dither.
Optimisasi LZW lainnya
Perbaikan lain dalam optimisasi LZW juga bisa dicapai lewat penataan ulang 'pola dither' lain pada gambar. Dan sebagian alat GIF bisa melakukan persis itu. Namun optimisasi semacam itu harus selalu diperiksa mata manusia sebelum disetujui, karena kadang bisa timbul perubahan warna yang halus tetapi buruk.
Rangkuman optimisasi kompresi
Berikut rangkuman lengkap ukuran file akhir yang dicapai dengan optimisasi kompresi.
Seperti yang terlihat, hanya perbaikan kecil pada ukuran animasi akhir yang dicapai lewat optimisasi LZW yang sangat kompleks itu, dibanding optimisasi transparansi bawaan. Namun hasilnya juga sangat bervariasi di antara sekian banyak program aplikasi optimisasi GIF yang tersedia, dan animasi spesifik yang dioptimisasi. Jika Anda benar-benar perlu memeras byte terakhir dari ukuran file, optimisasi LZW mungkin memang yang Anda butuhkan. Dan jika Anda benar-benar butuh hasil terbaik, cobalah sejumlah program berbeda (alias implementasi heuristik berbeda) untuk melihat mana yang mengompres animasi Anda lebih baik, termasuk fitur optimisasi lain yang mereka sediakan. Umumnya optimisasi transparansi sudah cukup untuk kebanyakan keperluan. Optimisasi LZW hanya memberi hasil sedikit lebih baik, penghematan sangat kecil untuk ukuran transmisi jaringan, alih-alih ukuran penyimpanan disk, karena yang terakhir memakai 'chunk' atau 'blok' penyimpanan yang lebih besar. Karena itu saya merasa optimisasi LZW berlebihan, dan menurut saya tidak sepadan dengan usahanya, atau uangnya (kebanyakan alat ini dijual komersial). | _Sayangnya saya mendapati pengoptimal GIF ini tidak menangani SEMUA jenis animasi yang sudah dioptimisasi sebelumnya dengan baik.
Misalnya, pengujian saya menunjukkan "Gifsicle" gagal menangani animasi yang sudah dioptimisasi memakai teknik 'disposal background'.
Di sisi lain saya mendapati "InterGIF" tidak mau menangani animasi yang sudah dioptimisasi memakai kanvas awal dan teknik 'disposal previous'. Ia juga terbatas pada optimisasi transparansi, yang kini disediakan IM.
Karena itu saya sarankan jangan mencampur utilitas optimisasi GIF dengan menyalurkan keluaran satu utilitas ke utilitas lain. Setidaknya tidak tanpa lebih dulu meng-coalesce animasinya untuk membuang optimisasi frame sebelumnya.
IM, Gifsicle, dan InterGIF semuanya menyediakan opsi coalesce untuk membuang optimisasi mereka sendiri, meski saya tidak bisa menjamin aplikasi non-IM meng-coalesce SEMUA animasi dengan benar. IM bisa.
_
Karena program-program ini tidak bisa dipakai secara andal bersama teknik optimisasi frame tingkat lanjut IM (yang memilih dan berganti-ganti teknik disposal GIF secara otomatis), saya sering mendapati IM justru memberi hasil keseluruhan lebih baik daripada sekadar memakai pengoptimal kompresi LZW itu. Saya juga menyarankan Anda meng-coalesce lagi hasilnya sesudahnya dan membandingkan frame-frame-nya dengan animasi asli yang belum dioptimisasi, untuk memastikan program non-IM itu tidak entah bagaimana mengacaukan animasinya sama sekali (lihat catatan di atas). Percayalah, saya pernah melihatnya terjadi, dan skrip semestinya memeriksa ulang bahwa animasi tetap valid.
Tutorial lain (memakai alat windows) tentang optimisasi jenis ini adalah WebReference Frame Optimation. Perhatikan bahwa situsnya salah nama, karena isinya tentang optimisasi kompresi.
Optimisasi minor
Ada beberapa teknik optimisasi lain untuk animasi GIF yang sering begitu jelas sampai-sampai terlewatkan.
- Buang komentar GIF.
Banyak animasi GIF berisi komentar teks panjang. Sering kali komentar itu ditambahkan otomatis oleh editor grafis sebagai bentuk iklan. Misalnya "Gimp" secara bawaan menambahkan "Created with The GIMP" ke gambar. Jika komentarnya tidak dibutuhkan, ia pemborosan ruang. Buang dengan menambahkan operator "+set comment" ke perintah "magick" IM sebelum GIF disimpan. Namun perlu dicatat: jika komentarnya berupa pemberitahuan hak cipta, membuangnya mungkin bukan ide baik karena alasan hukum. - Kurangi jumlah warna.
Jika animasi masih tampak oke dengan warna lebih sedikit, pakai tabel warna lebih kecil. Ukuran tabel warna selalu berupa pangkat dua, jadi jika kurang dari 32 warna sudah cukup, itu jauh lebih kecil daripada memakai 256 warna. Ini sangat penting karena tabel warna tidak dikompres oleh kompresi LZW yang dipakai untuk data gambar GIF. Memakai warna lebih sedikit juga umumnya menghasilkan kompresi LZW lebih baik, karena lebih banyak urutan piksel yang sama ditemukan. Namun tidak selalu begitu, karena dithering warna (akibat reduksi warna) juga bisa memperburuk kompresinya. Mematikan dithering, atau memakai ordered dither, bisa sangat penting di sini. - Separuhkan jumlah frame yang dilihat pengguna.
Jika Anda bisa menerima animasi yang kurang mulus, memangkas total frame menjadi separuhnya bisa memberi perbaikan bagus pada ukuran file akhir. Tentu file-nya tidak lantas berukuran separuh, dan kualitas animasinya berkurang. Tapi ia bisa memberi pengurangan ukuran file yang sangat besar. - Pangkas/ubah ukuran animasinya.
Ukuran gambar lebih kecil berarti ukuran file lebih kecil. Jadi kalau tidak butuh animasi besar, jangan pakai animasi besar. Thumbnail kecil yang mewakili animasi atau video yang lebih besar sering lebih disukai dalam sebuah daftar daripada barang aslinya. - Kompresi alternatif.
Jika Anda tidak berencana memakai animasinya sebagai animasi, alias hanya ingin menyimpannya, matikan kompresi LZW-nya, lalu kompres SELURUH file dengan "gzip" atau "bzip2" untuk penyimpanan! Hasilnya jauh lebih kecil, meski butuh server web memberi petunjuk 'content' dan 'compression' yang benar ke browser agar bisa langsung dipakai browser klien. Server web "Apache" tidak melakukannya secara bawaan, tetapi bisa diatur agar begitu. Lebih baik lagi, arsipkan seluruh direktori animasi tak terkompresi menjadi satu file, untuk kompresi penyimpanan yang lebih baik lagi.
Jika Anda punya ide optimisasi lain, tolong beri tahu saya.
Sumber informasi lain tentang optimisasi GIF
Uraian di atas melengkapi berbagai metode dan teknik dasar penanganan animasi. Namun agar gambarannya utuh, sebaiknya lanjutkan ke halaman contoh IM berikutnya, yang merinci teknik menangani masalah nyata pada animasi gambar. Banyak pula teknik di atas yang didemonstrasikan dalam contoh praktis optimisasi video ke GIF. Saya juga menyarankan Anda membaca tuntas tentang kuantisasi warna jika benar-benar serius menangani animasi GIF, karena reduksi warna sering menjadi kunci penanganan animasi GIF yang baik. Sumber lain yang berguna tentang teknik optimisasi animasi GIF yang saya temukan di WWW antara lain...
Surati saya jika Anda merasa punya halaman yang layak saya daftar di sini. Saya hanya akan menambahkan halaman berisi konten yang berguna, jadi tidak ada jaminan tautan Anda ditambahkan.
![[IM Output]](../static/img/anim_opt/paddleball_frames.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_oplus_cframes.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_oplus_rmdups_frames.gif)
![[IM Output]](../static/img/anim_opt/moving_hole_split_rmzero_frames.gif)
![[IM Text]](../static/img/anim_opt/moving_hole_sizes.txt.gif)
![[IM Output]](../static/img/anim_opt/teleport_frames.png)
![[IM Output]](../static/img/anim_opt/teleport_thres.gif)
![[IM Output]](../static/img/anim_opt/teleport_thres_frames.gif)
![[IM Output]](../static/img/anim_opt/teleport_thres90.gif)
![[IM Output]](../static/img/anim_opt/teleport_thres90_frames.gif)
![[IM Output]](../static/img/anim_opt/teleport_bgnd.gif)
![[IM Output]](../static/img/anim_opt/teleport_bgnd_frames.gif)
![[IM Output]](../static/img/anim_opt/teleport_od.gif)
![[IM Output]](../static/img/anim_opt/teleport_od_frames.gif)
![[IM Output]](../static/img/anim_opt/teleport_htone.gif)
![[IM Output]](../static/img/anim_opt/teleport_htone_frames.gif)
![[IM Output]](../static/img/anim_opt/teleport_lines.gif)
![[IM Output]](../static/img/anim_opt/teleport_lines_frames.gif)
![[IM Text]](../static/img/anim_opt/speed_nframes.txt.gif)
![[IM Text]](../static/img/anim_opt/speed_ncf.txt.gif)
![[IM Text]](../static/img/anim_opt/speed_ncolors.txt.gif)
![[IM Text]](../static/img/anim_opt/speed_ncolors2.txt.gif)
![[IM Output]](../static/img/anim_opt/speed_opt2_frames.gif)
![[IM Output]](../static/img/anim_opt/speed_opt3_frames.gif)
![[IM Output]](../static/img/anim_opt/speed_opt4_frames.gif)
![[IM Output]](../static/img/anim_basics/bunny_bgnd_frames.gif)
![[IM Output]](../static/img/anim_basics/bunny_bgnd.gif)
![[IM Output]](../static/img/anim_opt/bunny_bgnd_lzw_gifsicle.gif)
![[IM Output]](../static/img/anim_opt/bunny_bgnd_lzw_frames.gif)
![[IM Output]](../static/img/anim_opt/bunny_bgnd_giflossy.gif)
![[IM Output]](../static/img/anim_opt/bunny_bgnd_lossy_frames.gif)
![[IM Output]](../static/img/anim_opt/bunny_bgnd_diff_frames.gif)
![[IM Text]](../static/img/anim_opt/bunny_bgnd_compress_sizes.txt.gif)