Catatan: blog ini membutuhkan pengetahuan dasar tentang konsep dan model gambar dalam domain komputer.
Pendahuluan
Paket image/draw mendefinisikan hanya satu operasi: menulis gambar sumber ke gambar tujuan, lewat sebuah gambar mask yang opsional. Operasi ini banyak gunanya dan dapat melakukan sejumlah pekerjaan manipulasi gambar yang umum secara elegan dan efisien.
Komposisi gambar dilakukan per piksel dengan gaya pustaka grafik Plan 9 dan
ekstensi X Render.
Model dari paket image/draw
berdasarkan makalah klasik "Compositing Digital
Images" oleh Porter dan Duff, dengan parameter tambahan mask:
dst = (src IN mask) OP dst
.
Untuk mask yang sepenuhnya transparan, ia sama dengan formula asli
Porter-Duff: dst = src OP dst
.
Dalam Go, gambar mask yang bernilai nil
sama dengan gambar mask yang
ukurannya tak terbatas dan sepenuhnya transparan.
Makalah Porter-Duff menjabarkan
12 operator komposisi,
namun dengan mask yang eksplisit, hanya 2 darinya yang dibutuhkan:
sumber-terhadap-tujuan dan sumber.
Dalam Go, operator-operator tersebut direpresentasikan oleh konstan Over
dan
Src
.
Operator Over
melakukan pelapisan dari gambar sumber terhadap gambar tujuan:
perubahan terhadap gambar tujuan lebih sedikit bilamana sumber (setelah
masking) lebih transparan (yaitu, memiliki nilai alpha yang kecil).
Operator Src
menyalin sumber (setelah masking) tanpa memperhatikan isi
asli dari gambar tujuan.
Untuk gambar sumber dan mask yang sepenuhnya transparan, kedua operator
tersebut menghasilkan keluaran yang sama, namun menggunakan operator Src
biasanya lebih cepat.
Penyelarasan Geometris
Komposisi membutuhkan pengasosiasian piksel tujuan dengan piksel sumber dan
mask.
Hal ini membutuhkan gambar tujuan, sumber, dan mask, dan sebuah operator
komposisi, namun ia juga harus menspesifikasikan petak mana dari setiap gambar
yang akan digunakan.
Tidak setiap operasi gambar harus menulis ke seluruh petak tujuan: saat
memperbarui gambar animasi, akan lebih efisien bila hanya menulis bagian dari
gambar yang telah berubah.
Tidak setiap operasi gambar harus membaca seluruh petak sumber: saat
menggunakan sebuah sprite yang menggabungkan banyak gambar-gambar kecil
menjadi satu gambar besar, hanya sebuah bagian gambar yang diperlukan.
Tidak setiap operasi gambar harus membaca seluruh mask: gambar mask
yang berupa kumpulan glyph dari fon (font) mirip dengan sebuah sprite.
Maka, operasi gambar membutuhkan tiga petak, satu untuk setiap gambar.
Secara setiap petak memiliki panjang dan lebar yang sama, maka cukup
mengirim petak tujuan r
dan dua titik sp
(source point) dan mp
(mask point): petak sumber sama dengan r
yang di-"terjemahkan" sehingga
r.Min
pada gambar tujuan sejajaj dengan sp
pada gambar sumber, dan hal
yang sama berlaku untuk mp
.
Petak tersebut juga dipotong untuk setiap gambar yang dibatasi oleh ruang
koordinat masing-masing.
Fungsi DrawMask() menerima tujuh argumen, namun argumen mask dan mask-point biasanya tidak diperlukan, sehingga fungsi Draw() menerima hanya lima:
// Draw calls DrawMask with a nil mask. func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op) func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op)
Gambar tujuan haruslah mutable (bisa diubah), sehingga paket image/draw
mendefinisikan sebuah interface
draw.Image
yang memiliki sebuah method Set()
.
type Image interface { image.Image Set(x, y int, c color.Color) }
Mengisi sebuah Petak
Untuk mengisi sebuah petak dengan warna tunggal, gunakan sumber
image.Uniform
.
Tipe ColorImage
menerjemahkan sebuah Color
sebagai sebuah Image
berukuran tak terbatas dari warna tersebut.
Bagi yang terbiasa dengan rancangan pustaka gambar pada sistem operasi Plan 9,
tidak diperlukan secara eksplisit "pengulangan bit" dalam tipe gambar Go yang
berbasis slice;
konsepnya dilebur ke dalam Uniform
.
// image.ZP adalah titik nol -- seluruh gambar asli. draw.Draw(dst, r, &image.Uniform{c}, image.ZP, draw.Src)
Untuk menginisiasi sebuah gambar baru yang semuanya biru:
m := image.NewRGBA(image.Rect(0, 0, 640, 480)) blue := color.RGBA{0, 0, 255, 255} draw.Draw(m, m.Bounds(), &image.Uniform{blue}, image.ZP, draw.Src)
Untuk mereset sebuah gambar menjadi transparan (atau hitam, jika model warna
pada gambar tujuan tidak dapat merepresentasikan transparansi), gunakan
image.Transparent
, yang mana merupakan sebuah image.Uniform
:
draw.Draw(m, m.Bounds(), image.Transparent, image.ZP, draw.Src)
Menyalin sebuah Gambar
Untuk menyalin dari sebuah petak sr
dari gambar sumber ke sebuah petak yang
diawali dengan sebuah titik dp
pada tujuan, ubah petak sumber
menjadi ruang koordinat pada gambar tujuan:
r := image.Rectangle{dp, dp.Add(sr.Size())} draw.Draw(dst, r, src, sr.Min, draw.Src)
Atau:
r := sr.Sub(sr.Min).Add(dp) draw.Draw(dst, r, src, sr.Min, draw.Src)
Untuk menyalin semua gambar sumber, gunakan sr = src.Bounds()
.
Memindahkan sebuah Gambar
Memindahkan sebuah gambar yaitu menyalin petak gambar ke dirinya sendiri,
dengan petak tujuan dan sumber yang berbeda.
Gambar tujuan dan sumber yang saling menimpa adalah valid, seperti pada fungsi
bawaan copy
pada Go yang menangani penyalinan slice sumber dan tujuan yang
saling timpa.
Untuk menggeser sebuah gambar m
dengan 20 piksel:
b := m.Bounds() p := image.Pt(0, 20) // Ingatlah bahwa walaupun argumen kedua adalah b, // petak yang efektif lebih kecil disebabkan karena pemotongan. draw.Draw(m, b, m, b.Min.Add(p), draw.Src) dirtyRect := b.Intersect(image.Rect(b.Min.X, b.Max.Y-20, b.Max.X, b.Max.Y))
Mengonversi Gambar ke RGBA
Hasil dari menerjemahkan sebuah format gambar belum tentu image.RGBA
:
men-decode sebuah GIF menghasilkan image.Paletted
, men-decode sebuah
JPEG menghasilkan ycbcr.YCbCr
, dan hasil dari men-decode sebuah PNG
bergantung pada data gambar.
Untuk mengonversi gambar apa pun menjadi sebuah image.RGBA
:
b := src.Bounds() m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy())) draw.Draw(m, m.Bounds(), src, b.Min, draw.Src)
Menggambar lewat sebuah Mask
Untuk menulis sebuah gambar lewat sebuah mask bundar dengan pusat p
dan
radius r
:
type circle struct { p image.Point r int } func (c *circle) ColorModel() color.Model { return color.AlphaModel } func (c *circle) Bounds() image.Rectangle { return image.Rect(c.p.X-c.r, c.p.Y-c.r, c.p.X+c.r, c.p.Y+c.r) } func (c *circle) At(x, y int) color.Color { xx, yy, rr := float64(x-c.p.X)+0.5, float64(y-c.p.Y)+0.5, float64(c.r) if xx*xx+yy*yy < rr*rr { return color.Alpha{255} } return color.Alpha{0} } draw.DrawMask(dst, dst.Bounds(), src, image.ZP, &circle{p, r}, image.ZP, draw.Over)
Menggambar Font Glyphs
Untuk menggambar sebuah font glyph dalam warna biru dari titik p
,
gambarlah dengan sumber image.ColorImage
dan sebuah mask image.Alpha
.
Demi kemudahan, kita tidak melakukan perbaikan posisi atau rendering
sub-piksel apa pun, atau memperbaiki tinggi fon yang melewati garis.
src := &image.Uniform{color.RGBA{0, 0, 255, 255}} mask := theGlyphImageForAFont() mr := theBoundsFor(glyphIndex) draw.DrawMask(dst, mr.Sub(mr.Min).Add(p), src, image.ZP, mask, mr.Min, draw.Over)
Kinerja
Implementasi paket image/draw memperlihatkan bagaimana menyediakan sebuah
fungsi manipulasi gambar untuk semua kebutuhan, namun tetap efisien untuk
kasus-kasus umum.
Fungsi DrawMask
menerima argumen bertipe interface, namun langsung melakukan
tipe assertion untuk memastikan argumennya adalah suatu tipe struct
tertentu, yang berkorespondensi pada operasi-operasi umum seperti menulis
sebuah image.RGBA
ke gambar lainnya, atau menulis sebuah mask
image.Alpha
(seperti sebuah font glyph) menjadi gambar image.RGBA
.
Jika tipe assertion sukses, maka informasi tipe tersebut digunakan untuk
menjalankan sebuah implementasi (operasi gambar) yang khusus.
Jika assertion gagal, maka alur kode kembali menggunakan method At
dan
Set
.
Jalur-cepat yang berupa implementasi khusus adalah murni optimasi kinerja saja;
gambar hasilnya akan sama saja (baik dengan jalur cepat atau biasa).
Dalam praktiknya, hanya sejumlah kecil kasus-kasus spesial yang dibutuhkan
untuk mendukung aplikasi-aplikasi tertentu.