Kita coba bernostalgia dengan pemrograman jaman dahulu yang sebenarnya konsep dasar yang masih digunakan sampai saat ini, dimana untuk menampilkan sesuatu di layar tidak semudah membuat markup HTML, dimana tag img dengan mudah menampilkan gambar, dan tag div dengan properti css background dan color dapat dengan mudah merubah tampilan warna latar dan teks. Yang akan di bahas sekarang adalah bagaimana suatu data binary dapat menentukan suatu pixel yang tampil di layar.

Konsep display dari dulu tidak pernah berubah, baik itu ketika masa display card primitif dengan interface VGA atau masa modern sekarang dengan interface beragam dari HDMI dan Display Port dan dengan akselerator 3D yang bermacam fitur, ketika tampilan siap disajikan ke display, semua display card tersebut akan menyimpannya di dalam framebuffer untuk selanjutnya dapat diproses oleh interface VGA, HDMI, Display Port dengan cara yang berbeda-beda, tapi pada dasarnya adalah melakukan multiplexing baik secara serial atau paralel.

Framebuffer

Framebuffer merupakan media penyimpanan biasanya berupa RAM. Semua proses pen-generate-an framebuffer biasanya berada di luar framebuffer itu sendiri, apalagi pada kartu grafis modern yang hanya mentransfer hasil proses yang siap tampil ke dalam framebuffer ini. Pada aktikel ini, Kita hanya akan membahas konsep dasar dari framebuffer tersebut tanpa membahas tentang 3D processing dan akselerasi dari kartu grafis.

Struktur framebuffer bermacam-macam, tapi pada umumnya framebuffer merupakan kumpulan pixel, dimana dalam satu pixel bisa (saja) berisi dari beberapa subpixel. Setiap pixel tersusun dari bit/byte atau bytes, yang biasanya dimulai dari kiri atas layar sampai kanan bawah layar. Terdapat bermacam implementasi framebuffer, pada artikel ini Kita hanya akan membahas framebuffer yang umum dari kiri atas ke kanan bawah.

Pixel dimulai dari kiri atas ke kanan atas yang biasa disebut satu scanline (1 baris pixel), dan akan dilanjutkan ke baris ke 2 dari kiri ke kanan, sampai pada baris terakhir paling kanan. Pada framebuffer jumlah pixel horizontal dan vertical tidak selalu sama dengan jumlah pixel yang tampil di layar, misalnya resolusi layar 800×600, tapi framebuffer bisa saja memiliki width bukan 800px tapi lebih besar, misalnya 820px.

Berikut contoh konfigurasi suatu framebuffer di linux:

xres           : 1280
yres           : 1024
bits_per_pixel : 16
smem_len       : 2621440
line_length    : 2560

Pada contoh di atas, resolusi layar x=1280 dan y=1024, dengan 1 pixel berjumlah 16bit (2 bytes), ukuran memory pada satu baris sebesar 2560 bytes dimana bila 1 pixel berukuran 2 byte, maka 1280 pixel = 1280x2bytes = 2560 bytes, dengan ukuran tinggi 1024 pixel, maka jumlah total memory pada framebuffer ini adalah 2560 x 1024 = 2621440 bytes. Dan kebetulan pada konfigurasi di atas jumlah line_length sama persis dengan ukuran resolusi horizontal (width).

Maka bila Kita memiliki alamat awal framebuffer (kiri atas pixel), Kita bisa mendapatkan posisi pixel yang dimaksud dengan membuat sebuah fungsi berikut:

uint8_t * framebuffer_address;
uint16_t getPixel(int x, int y){
	uint8_t * pixel_address = framebuffer_address + (y * line_length + x * 2);
	return (uint16_t) ((uint16_t *) pixel_address)[0];
}

Implementasi bisa bermacam-macam, pada contoh di atas framebuffer_address disimpan dalam uint8_t * (1 byte address), dimana bisa melakukan incremental maka akan dilakukan step per 1 byte. Disini Kita memiliki niai line_length yaitu ukuran memory per baris, maka bila kita kalikan line_length dengan y, Kita akan mendapatkan alamat pixel pada baris y dipaling kiri, dan kita tambahkan x * 2 untuk mendapatkan koordinat pixel x nya, kenapa dikali 2? Karena disini ukuran 1 pixel = 2 byte.

Untuk merubah isi pixel, Kita juga dapat membuat sebuah fungsi seperti berikut:

uint8_t * framebuffer_address;
void setPixel(int x, int y, uint16_t warna){
	uint8_t * pixel_address = framebuffer_address + (y * line_length + x * 2);
	((uint16_t *) pixel_address)[0] = warna;
}

Pada fungsi di atas, Kita tidak memerlukan return, tapi justu memerlukan argumen warna untuk mengganti isi dari pixel yang dimaksud. Cara perhitungan lokasi dari pixel sama dengan getPixel, tapi pada fungsi kali ini, isi dari alamat pixel yang didapat akan di ubah menjadi variabel warna.

Contoh di atas bukan fungsi praktikal, karena ukuran satu pixel bisa saja berbeda, bisa saja 1 pixel itu berupa pallete dengan ukuran 1 byte atau justru full rgb sebesar 32bit (4 bytes) atau 24bit (3 bytes). Semua modus tersebut memiliki cara yang berbeda dalam penanganannya.

Pixel dan Subpixel

Pada contoh dan pembahasan sebelumnya, banyak dibawah tentang pixel, dan pada fungsi setPixel di atas pixel yang di set memiliki nama argumen variabel warna, karena memang satu pixel itu didefinisikan sebagai satu titik warna, baik itu warna hitam, biru, hijau, kuning, merah atau putih. Ke semua warna tersebut sebenarnya hasil dari percampuran tiga warna utama, yaitu Merah, Hijau dan Biru yang memiliki intensitas tersendiri yang diistilahkan subpixel.

Bagaimana satu pixel dapat memiliki berbagai macam warna dari perpaduan 3 warna?. Karena pada dasarnya semua warna yang dipancarkan (berupa cahaya) berasal dari 3 warna dasar ini, dan mohon untuk diingat bahwa warna primer display berbeda dengan warna primer untuk publisher (cat, tinta, dsb).

Penjelasan yang lebih mudah sebagai berikut: Kita mulai dengan warna Merah, ketika kita tambah Hijau maka warna akan berubah menjadi orange kemudian menjadi kuning, ketika kita hilangkan warna merahnya, warnanya akan berubah menjadi hijau, ketika ditambahkan biru maka akan berubah warna menjadi cyan (telor asin) bila Kita hilangkan warna hijau maka akan menjadi biru, ketika biru tersebut ditambah warna merah akan menjadi ungu kemudian pink, dan seterusnya. Bila ketiga warna ber-intensitas tinggi maka akan semakin terang, bila semua intensitas maksimal rata akan menjadi putih, bila tidak ada intensitasnya sama sekali akan menjadi hitam. Untuk lebih jelasnya sepertinya para pembaca dapat mempelajari tentang hex-color.

Struktur penyimpanan sub-pixel dalam pixel juga bermacam-macam, ada berupa 8 bit pallete, 16 bit RGB565, 24Bit RGB, 32bit RGBA, semuanya memiliki susunan yang berbeda, Pada kesempatan ini Kita hanya akan membahas 2 contoh colorspace saja, yaitu 16bit RGB565 dan 24bit RGB.

16bit RGB565

Color space ini biasanya digunakan pada perangkat embeded yang tidak memiliki banyak RAM untuk menyimpan framebuffer. Satu pixel berukuran 2 bytes atau 16 bit, dimana Red memiliki 5 bit data, Green 6 bit data, dan Blue 5 bit data, dengan format: RRRRRGGG GGGBBBBB, berjumlah 16 bit.

Red dapat memiliki intensitas dari 0-31, Green memiliki intensitas 0-63, dan Blue memiliki intensitas 0-31. Alasan hijau memiliki intensitas yang lebih banyak adalah, karena untuk memanfaatkan 1 bit yang tidak terpakai dan hijau cenderung lebih mudah terdeteksi oleh mata manusia.

Berikut fungsi untuk memparsing subpixel dan meng-generate pixel untuk colorspace rgb565:

uint8_t get_red(uint16_t rgb) {
	/* 0xF800 -> 1111100000000000 */
	return (uint8_t) (rgb & 0xF800) >> 8;
}
uint8_t get_green(uint16_t rgb) {
	/* 0x07E0 -> 0000011111100000 */
	return (uint8_t) (rgb & 0x07E0) >> 3;
}
uint8_t get_blue(uint16_t rgb) {
	/* 0x001F -> 0000000000011111 */
	return (uint8_t) (rgb & 0x001F) << 3;
}

uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) {
  return ((uint16_t)((r >> 3) << 11)|((g >> 2) << 5) | (b >> 3));
}

Sekali lagi implementasi dapat dilakukan dengan cara yang bermacam-macam dengan hasil dan performa yang dapat berbeda-beda.

24bit RGB

Color space 24bit RGB ini sangat mudah di-implementasikan karena alignment sub-pixel berada pada 1 byte yang rata, sehingga dapat diimplementasikan dengan sistem array yang mudah.

uint8_t get_red(uint8_t * rgb) {
	return rgb[0];
}
uint8_t get_green(uint8_t * rgb) {
	return rgb[1];
}
uint8_t get_blue(uint8_t * rgb) {
	return rgb[2];
}

void rgb24(uint8_t * rgb, uint8_t r, uint8_t g, uint8_t b) {
	rgb[0]=r;
	rgb[1]=g;
	rgb[2]=b;
}

Dan kembali mengingatkan, implementasi bisa bermacam-macam caranya.

Contoh Implementasi Framebuffer Sederhana

Kedua contoh ini memperagakan framebuffer pada device yang memiliki resource yang minim, dimana library seperti x-server, gtk, apalagi Windowing system dan web-browser tidak memungkinkan untuk diimplementasikan.

Kesimpulan

Untuk artikel kali ini, rupanya cukup sampai di sini dulu, pembahasan selanjutnya akan dilanjutkan pada Aktikel tentang framebuffer selanjutnya, yang akan membahas tentang drawing primitive seperti menggambar kotak, garis, lingkaran dan sebagainya, dan semoga saja pembahasan ini bisa berlanjut juga pada tahap pemprosesan teks, teknik double buffer dan sebagainya.

Semoga saja artikel singkat ini bermanfaat, dan menjadi bahan penarasaran para programmer untuk mendalami lebih lanjut tentang grafik-primitif ini.

Akhir kata saya ucapkan Terima Kasih.