Pendahuluan
Paket
image
dan
image/color
berisi sejumlah tipe: color.Color
dan color.Model
yang mendeskripsikan
warna, image.Point
dan image.Rectangle
yang mendeskripsikan geometri 2-D,
dan image.Image
yang menggabungkan kedua konsep tersebut untuk
representasi sebuah kotak persegi panjang berisi warna-warna.
Sebuah
artikel terpisah
membahas cara melakukan komposisi gambar dengan dengan paket
image/draw.
Catatan: artikel ini membutuhkan pengetahuan tentang konsep warna dan gambar dalam domain komputer.
Warna dan Model Warna
Color adalah interface yang mendefinisikan sekumpulan method untuk tipe bentukan supaya dapat dianggap sebagai sebuah warna: yang bisa dikonversi ke merah, hijau, biru, dan nilai alpha. Hasil konversi warna bisa jadi lossy (menghilangkan atau mengurangi beberapa data gambar), seperti konversi dari ruang warna CMYK atau YCbCr.
type Color interface { // RGBA returns the alpha-premultiplied red, green, blue and alpha values // for the color. Each value ranges within [0, 0xFFFF], but is represented // by a uint32 so that multiplying by a blend factor up to 0xFFFF will not // overflow. RGBA() (r, g, b, a uint32) }
Ada tiga hal penting dari nilai kembalian fungsi RGBA()
di atas.
Pertama, nilai merah r
, hijau g
, dan biru b
dikembalikan dengan nilai
perkalian alpha-nya: sebuah warna merah yang 25% transparan
direpresentasikan dengan RGBA yang mengembalikan 75% r.
Kedua, kanal memiliki rentang 16-bit: warna merah 100% direpresentasikan
dengan RGBA yang mengembalikan nilai r sebagai 65535, bukan 255, sehingga
mengonversi dari CMYK atau YCbCr tidaklah lossy.
Ketiga, tipe yang dikembalikan adalah uint32
, walaupun maksimum nilainya
adalah 65536, untuk menjamin bahwa perkalian dua nilai tersebut tidak
mengakibatkan overflow.
Perkalian tersebut terjadi saat menggabungkan dua warna menurut nilai alpha
dari warna ketiga, dengan cara aljabar klasik
Porter dan Duff:
dstr, dstg, dstb, dsta := dst.RGBA() srcr, srcg, srcb, srca := src.RGBA() _, _, _, m := mask.RGBA() const M = 1<<16 - 1 // Hasil dari warna merah adalah gabungan dari dstr dan srcr, dengan rentang // antara [0, M]. // Perhitungan untuk hijau, biru dan _alpha_ sama. dstr = (dstr*(M-m) + srcr*m) / M
Baris terakhir dari potongan kode tersebut akan menjadi lebih rumit jika kita
bekerja dengan warna tanpa nilai alpha, oleh karena itulah kenapa Color
menggunakan nilai alpha yang telah dihitung sebelumnya.
Paket image/color juga mendefinisikan sejumlah tipe konkret yang
mengimplementasikan interface Color
.
Sebagai contohnya,
RGBA
adalah sebuah struct yang merepresentasikan warna klasik "8 bits per kanal".
type RGBA struct { R, G, B, A uint8 }
Ingatlah bahwa field R
dari sebuah RGBA
adalah warna yang memiliki nilai
perkalian-alpha dengan rentang [0,255].
RGBA
memenuhi interface Color
dengan mengalikan nilai tersebut dengan
0x101 untuk menghasilkan warna perkalian-alpha dalam rentang [0, 65535].
Hal yang sama, tipe struct
NRGBA
merepresentasikan 8 bits warna tanpa perkalian-alpha, seperti yang digunakan
oleh format PNG.
Saat memanipulasi field-field NRGBA, nilainya adalah non perkalian-alpha,
namun saat memanggil method RGBA(), nilai kembaliannya dikalikan dengan nilai
alpha.
Sebuah
Model
yaitu interface yang dapat mengonversi Color
ke Color
lainnya, bisa jadi
secara lossy.
Misalnya, GrayModel
bisa mengonversi Color
apa pun menjadi
Gray.
Sebuah Palette
bisa mengonversi Color
apa pun ke salah satu palette
terbatas.
type Model interface { Convert(c Color) Color } type Palette []Color
Point (titik) dan Rectangle (persegi panjang)
Sebuah
Point
yaitu sebuah koordinat (x,y) pada grid integer, dengan poros ke kanan dan ke
bawah.
Ia bukanlah sebuah pixel dan bukan pula sebuah kotak segi empat.
Sebuah Point
tidak memiliki lebar, tinggi, atau warna, namun visualisasi di
bawah ini menggunakan bentuk kontak berwarna.
type Point struct { X, Y int }
p := image.Point{2, 1}
Sebuah
Rectangle
yaitu persegi panjang dalam grid integer, didefinisikan Point
pada
bagian atas-kiri dan bawah-kanan.
Sebuah Rectangle
juga tidak memiliki warna, namun visualisasi di bawah ini
menggarisi persegi panjang dengan garis berwarna, dan mencetak nilai Max
dan Min
.
type Rectangle struct { Min, Max Point }
Untuk lebih mudah, image.Rect(x0, y0, x1, y1)
sama dengan
image.Rectangle{image.Point{x0, y0}, image.Point{x1, y1}}
,
namun ditulis lebih ringkas.
Sebuah Rectangle
inklusif pada sisi atas-kiri dan eksklusif pada bawah-kanan.
Untuk sebuah Point p
dan sebuah Rectangle r
, p.In(r)
jika dan hanya jika
r.Min.X ⇐ p.X && p.X < r.Max.X
, dan hal yang sama berlaku juga untuk Y
.
Hal ini sama dengan bagaimana sebuah slice s[i0:i1]
adalah
inklusif pada batas bawah dan eksklusif pada batas atas dari slice aslinya s
.
(Tidak seperti array dan slice, sebuah Rectangle
sering kali memiliki nilai
yang bukan nol.)
r := image.Rect(2, 1, 5, 5) // Dx dan Dy mengembalikan lebar dan tinggi persegi panjang. fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // mencetak 3 4 false
Menambahkan sebuah Point
ke Rectangle
memindahkan Rectangle
tersebut.
Point dan Rectangles tidak terbatas hanya pada bagian kuadran bawah-kanan.
r := image.Rect(2, 1, 5, 5).Add(image.Pt(-4, -2)) fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // mencetak 3 4 true
Persilangan dua Rectangle
menghasilkan Rectangle
yang lain, yang bisa saja
kosong.
r := image.Rect(0, 0, 4, 3).Intersect(image.Rect(2, 2, 5, 5)) // Method Size() mengembalikan lebar dan tinggi dari Rectangle, dalam bentuk // Point. fmt.Printf("%#v\n", r.Size()) // mencetak image.Point{X:2, Y:1}
Point dan Rectangle dikirim dan dikembalikan dengan nilai.
Fungsi yang menerima argumen Rectangle
sama efisien dengan menerima dua
argumen Point
, atau empat argumen int
.
Image (Gambar)
Sebuah
Image
memetakan setiap kotak grid dalam sebuah Rectangle
menjadi Color
dari
sebuah Model
.
"Pixel pada (x,y)" mengacu pada warna dari kotak grid yang didefinisikan
oleh titik (x,y), (x+1,y),(x+1,y+1), dan (x,y+1).
type Image interface { // ColorModel returns the Image's color model. ColorModel() color.Model // Bounds returns the domain for which At can return non-zero color. // The bounds do not necessarily contain the point (0, 0). Bounds() Rectangle // At returns the color of the pixel at (x, y). // At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid. // At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one. At(x, y int) color.Color }
Salah satu kesalahan yang umum yaitu mengasumsikan bahwa batas atas Image
dimulai dari (0, 0).
Misalnya, animasi GIF berisi urutan Image
, dan setiap Image
setelah yang
pertama biasanya hanya menyimpan data pixel untuk wilayah yang berubah, dan
wilayah tersebut tidak harus dimulai dari (0, 0).
Cara yang paling benar untuk iterasi pixel pada Image
yaitu seperti:
b := m.Bounds() for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { doStuffWith(m.At(x, y)) } }
Implementasi Image
tidak harus berbasiskan slice dari data pixel.
Contohnya, sebuah
Uniform
yaitu sebuah Image
yang memiliki garis batas yang sangat besar dengan satu
warna, yang representasi di memory hanyalah warna saja.
type Uniform struct { C color.Color }
Biasanya, program menginginkan sebuah gambar yang berbasis slice.
Tipe struct seperti
RGBA
dan
Gray
(yang dipanggil oleh paket lain sebagai image.RGBA
dan image.Gray
)
menyimpan slice dari data pixel dan mengimplementasikan interface Image
.
type RGBA struct { // Pix holds the image's pixels, in R, G, B, A order. The pixel at // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4]. Pix []uint8 // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle }
Tipe tersebut juga menyediakan method Set(x, y int, c color.Color)
untuk
mengubah pixel satu per satu pada gambar.
m := image.NewRGBA(image.Rect(0, 0, 640, 480)) m.Set(5, 5, color.RGBA{255, 0, 0, 255})
Jika kita membaca atau menulis banyak data pixel, akan lebih efisien,
namun lebih kompleks, dengan mengakses field Pix
secara langsung.
Implementasi Image
berbasis slice juga menyediakan method SubImage()
, yang
mengembalikan sebuah Image
yang memiliki dasar array yang sama.
Mengubah pixel pada sub-gambar akan mempengaruhi pixel pada gambar aslinya,
hal yang sama seperti mengubah isi dari sub-slice s[i0:i1]
juga akan
mempengaruhi isi dari slice asli s
.
m0 := image.NewRGBA(image.Rect(0, 0, 8, 5)) m1 := m0.SubImage(image.Rect(1, 2, 5, 5)).(*image.RGBA) fmt.Println(m0.Bounds().Dx(), m1.Bounds().Dx()) // mencetak 8, 4 fmt.Println(m0.Stride == m1.Stride) // mencetak true
Untuk kode tingkat-rendah yang bekerja pada field Pix
pada Image
, ingatlah
bahwa melakukan range
pada Pix
dapat mempengaruhi pixel di luar garis batas
gambar.
Pada contoh di atas, pixel-pixel yang ditutupi oleh m1.Pix
diwarnai dengan
biru.
Untuk kode tingkat-tinggi, seperti method At()
dan Set()
atau
paket image/draw,
akan memotong operasi pixel sesuai dengan garis batas gambar.
Format gambar
Pustaka bawaan mendukung sejumlah format gambar umum, seperti GIF, JPEG, dan PNG. Jika anda mengetahui format dari berkas gambar, anda dapat men- decode langsung dari io.Reader.
import ( "image/jpeg" "image/png" "io" ) // convertJPEGToPNG mengonversi dari JPEG ke PNG. func convertJPEGToPNG(w io.Writer, r io.Reader) error { img, err := jpeg.Decode(r) if err != nil { return err } return png.Encode(w, img) }
Jika anda memiliki gambar dengan format tidak diketahui, fungsi
image.Decode
dapat digunakan untuk mendeteksi formatnya.
Kumpulan format yang dikenali dibentuk pada saat runtime dan tidak terbatas
hanya pada yang ada di pustaka bawaan.
Sebuah paket format gambar biasanya meregistrasi format dalam fungsi init
,
dan paket main
akan menggunakan "import kosong" pada paket tersebut supaya
format teregistrasi.
import ( "image" "image/png" "io" _ "code.google.com/p/vp8-go/webp" _ "image/jpeg" ) // convertToPNG mengonversi dari format apa pun ke PNG. func convertToPNG(w io.Writer, r io.Reader) error { img, _, err := image.Decode(r) if err != nil { return err } return png.Encode(w, img) }