Pengenalan Game Design

Bermain dengan Gambar

Dalam membuat animasi, tentu saja kita akan beberapa kali bekerja dengan menggunakan gambar, baik gambar diam maupun bergerak. Dalam bab ini, kita akan mulai mempelajari bagaimana gambar bisa diolah di dalam Processing. Agar pembahasan lebih fokus, maka kita awali dulu dengan menggunakan gambar diam.

Pengetahuan Dasar Gambar dalam Processing

Sebelumnya kita telah melihat beberapa tipe data yang digunakan Processing untuk menyimpan angka maupun huruf. Lalu apakah tipe data ini juga yang digunakan untuk menyimpan data gambar? Tentu saja tidak. Processing memiliki tipe data khusus untuk gambar, yakni PImage dan PGraphics. Dengan menggunakan salah satunya, maka kita bisa membaca berbagai tipe file gambar seperti JPG, PNG atau GIF, memanipulasinya, serta membuatnya menjadi file baru.

Selanjutnya, data gambar yang sudah kita simpan ini, bisa kita akses dalam bentuk array, sehingga teknik-teknik yang berkaitan dengan array yang telah kita pelajari sebelumnya, bisa diterapkan di sini. Karena gambar yang kita pelajari sekarang masih bersifat statis, menggunakannya sebagai latar belakang dari animasi yang akan kita buat, bisa menjadi ide bagus.

Mari kita mulai.

Menampilkan Gambar di Processing

Kita akan belajar menampilkan gambar yang kita miliki di Processing. Semua gambar di dalam Processing akan kita simpan seperti halnya menyimpan variabel biasa. Oleh karena itu, alur kerjanya pun akan nampak familiar. Berikut ini format dasar memuat gambar di Processing:

// Deklarasi variabel gambar dengan tipe data PImage
PImage gambar;

// Memindahkan isi gambar dari file ke variabel
gambar = loadImage ("nama_file_gambar");

// Menampilkan gambar pada koordinat x=0, y=0
image (gambar, 0, 0)

Kita bisa melihat bahwa cara kerjanya mirip dengan bagaimana kita membuat dan menginisialisasi variabel. Ini dilakukan oleh 2 kode pertama yang kita tulis. Hanya kali ini, kita pastikan bahwa gambar juga ditampilkan di layar, yang dilakukan oleh fungsi image().

Khusus untuk nama file gambar, ada 2 pilihan yang bisa kita lakukan untuk menuliskan argumen bagi fungsi loadImage() yang akan membaca file gambar:

  1. Menuliskan direktori serta nama file gambar kita.
  2. Menyimpan sketch Processing kita, lalu di folder sketch tersebut, kita buat folder bernama data, dan letakkan file gambar di folder tersebut. Untuk ini, kita cukup menuliskan saja nama file gambar sebagai argumen loadImage().

Contoh berikut ini akan menunjukkan bagaimana menampilkan gambar dalam program Processing

Contoh 7-1 Menampilkan Gambar

PImage gambar;

void setup() {
    size (640, 480);
    gambar = loadImage ("contoh.jpg");
}

void draw() {
    image (gambar, 0, 0);
}

Agar sketch tadi bisa berjalan, silakan save sketch dan simpan gambar yang kita inginkan di folder data, atau ubah saja parameter loadImage() mengikuti direktori file gambar yang ingin ditampilkan.

Perlu dibahas juga bahwa membaca file gambar kita lakukan di dalam setup(), karena proses ini cukup berlangsung sekali saja. Setelah loadImage() dilakukan, maka Processing sudah selesai membaca isi file gambar dan datanya sudah berada di dalam variabel gambar. Kita hanya perlu menampilkan isi dari variabel tersebut agar gambar bisa muncul di layar. Agar gambar bisa ditampilkan terus, maka kita bisa melakukannya di dalam draw(). Dengan demikian, maka kita jalankan fungsi image() di dalam draw() agar bisa berjalan berulang kali. Tentu saja, ini bisa jadi dilakukan di dalam setup(), namun dengan demikian maka yang kita lakukan nanti di dalam draw() bisa jadi menutupi si gambar.

Secara default, Processing akan menampilkan gambar dalam bentuk kotak. Apabila kita ingin menampilkan gambar dengan transparansi, misal gambar berbentuk lingkaran, maka kita bisa menyimpan gambar tersebut dalam format GIFa tau PNG dan tetap mempertahankan transparansinya. Nantinya nilai transparansi akan tetap disimpan dalam Processing, hingga gambarnya bisa tidak berbentuk kotak.

Simak juga bahwa ukuran layar yang diatur dengan size() perlu dibuat sama dengan ukuran gambar, agar gambar yang ditampilkan tidak terpotong. Lalu bagaimana kalau hal tersebut tidak bisa dilakukan karena satu dan lain hal? Tentu saja kita bisa memanipulasi gambar ketika ditampilkan. Mari kita simak sub bab berikutnya.

Memanipulasi Gambar

Sebagaimana variabel lain, karena data dari gambar sudah dibaca oleh Processing melalui loadImage(), maka gambar pun bisa dimanipulasi hingga bisa dipakai sesuai keinginan kita. Ada beberapa hal yang akan kita bahas, jadi kita mulai dari yang paling mudah terlebih dahulu, memanipulasi ukuran gambar.

Memanipulasi Ukuran dan Posisi Gambar

Cara paling mudah untuk melakukan ini adalah dengan menambahkan argumen pada fungsi image(). Sebenarnya fungsi ini memiliki 2 argumen opsional yang bisa tidak kita gunakan. Namun, kalau ingin mengubah ukuran gambar secara langsung, maka inilah caranya:

image (variabel_gambar, posisi_ujung_kiri_gambar, posisi_ujung_atas_gambar, lebar_gambar, tinggi_gambar)

Dengan cara ini, maka apabila ukuran gambar kita tidak sesuai dengan ukuran layar, maka ukuran gambar bisa kita ganti dengan kode, tanpa perlu mengedit ulang gambar.

Selain itu, apabila kita ingin menampilkan gambar tidak pada koordinat (0,0), maka kita bisa mengubah argumen ke-2 dan ke-3 pada fungsi image(). Dengan demikian, posisi gambar bisa kita atur, atau bahkan diubah secara dinamis, seperti misalnya mengikuti koordinat mouse.

Dengan demikian, maka potongan kode berikut akan menampilkan gambar mengikuti koordinat mouse dengan ukuran gambar 200x400 pixel.

image (gambar, mouseX, mouseY, 200, 400)

Memanipulasi Warna Gambar

Hal lain yang juga kita ubah langsung adalah warna gambar. Yang sebenarnya terjadi ketika Processing selesai membaca data dari sebuah file gambar, adalah ia telah mendapatkan data warna di setiap pixel pada gambar. Dengan demikian, maka kita bisa langsung memanipulasi warna ini, karena kita sudah bisa mengakses setiap pixel pada gambar. Contoh berikut ini menunjukkan bagaimana caranya

Contoh 7-2 Manipulasi Warna Gambar per Pixel

PImage gambar;

void setup() {
  size(600, 450);
  gambar = loadImage("product.jpg");
  int dimension = gambar.width * gambar.height;
  gambar.loadPixels();
  for (int i = 0; i < dimension; i+=10) {
    gambar.pixels[int(random(i))] = color(random(100), random(100,200), 0);
  }
  gambar.updatePixels();
}

void draw() {
    image(gambar, 0, 0);
}

Kode di atas menunjukkan bagaimana Processing bisa mengakses data warna di setiap pixel dalam sebuah gambar, lalu mengatur warna pixel tersebut. Hasil dari kode akan nampak seperti gambar dengan bintik-bintik hijau, karena sebenarnya inilah yang kita lakukan. Jadi kita telah menetapkan pixel acak di dalam gambar untuk menjadi berwarna hijau. Maka kita lihat dengan cara ini kita bisa mewarnai ulang gambar.

Selain itu, ada cara lain untuk mewarnai gambar, yakni dengan menggunakan fungsi set(). Kali ini pewarnaan ulang gambar dilakukan tanpa mengakses setiap pixel secara terang-terangan. Ini contoh kodenya:

Contoh 7-3 Manipulasi Warna Gambar dengan set()

PImage gambar;

void setup() {
  size(600, 450);
  gambar = loadImage("product-display-on.jpg");
  for (int i=0; i

Kali ini, kita membuat titik-titik berwarna biru yang muncul setiap 4 pixel. Titik-titik ini bukanlah bentuk yang kita gambar, melainkan titik warna pixel pada gambar yang kita ubah secara manual. Meski hasilnya mirip dengan memanipulasi pixel pada contoh sebelumnya, tapi kita tidak secara literal menulis akses pada pixel. Cara ini penulisannya terasa lebih mudah, meski demikian butuh waktu yang lebih lama dalam operasinya, ketimbang dengan mengakses pixel secara manual.

Meski demikian, secara umum, karena fungsi set() ini mewakili aktivitas mengakses array pixel[], maka pernyataan set(x, y, #000000) ekuivalen dengan pixels[y*width+x] = #000000. Keduanya mengubah warna pixel pada koordinat (x,y) menjadi berwarna #000000 atau hitam.

Selain fungsi set() yang mengatur warna sebuah pixel pada gambar, terdapat juga fungsi get() yang berfungsi mengambil warna dari sebuah gambar atau bisa dikenal dengan istilah sampling. Warna yang diambil ini kemudian akan disimpan ke dalam variabel dengan tipe data color, sehingga bisa langsung digunakan untuk mewarnai gambar dengan set(). Berikut ini contohnya

Contoh 7-4 Manipulasi Warna dengan set() dan get()

PImage gambar;

void setup() {
  size(600, 450);
  gambar = loadImage("product-display-on.jpg");
  color warnaGambar = gambar.get (200,200);

  for (int i=gambar.width/4; i

Kali ini, kita lihat ada sebuah segi empat berwarna di dalam gambar. Bentuk ini tidaklah dibuat dengan menggunakan fungsi rect(), melainkan dengan mewarnai pixel, sama seperti contoh sebelumnya. Warna yang digunakan adalah warna yang disampling dengan fungsi gambar.get(200,200), di mana di sini, kita menggunakan warna yang ada pada gambar pada koordinat pixel (200,200). Warna itu kemudian kita gunakan untuk mewarnai gambar mulai dari titik x=gambar/4 hingga x=gambar/2 dan y=gambar/4 hingga y=gambar/2.

Tentu saja, untuk menggunakan warna dari fungsi get() ini, kita tidak perlu menggunakan set(). Kita bisa mengaplikasikan ini untuk membuat bentuk seperti sebelumnya, dan mewarnai bentuk ini dengan variabel warna dari get(). Ini kemudian akan menguntungkan kita, karena kita bisa membuat bentuk dengan warna yang senada dengan gambar yang jadi latar belakang.

Satu lagi cara mewarnai gambar adalah dengan menggunakan tint(). Kali ini, keseluruhan gambar akan diwarnai dengan menggunakan warna tertentu, atau apabila warna yang digunakan memiliki nilai alpha, maka gambar akan berwarna transparan. Warna yang dituangkan ke dalam gambar tidak akan membuat gambar tidak nampak lagi, namun gambar akan penuh menjadi berwarna sesuai warna yang digunakan.

Argumen untuk fungsi tint() ini adalah warna. Sehingga kita bisa menggunakan warna dengan notasi heksadesimal, format RGB, atau HSB, ataupun variabel warna dengan tipe data color. Tentu saja, kita juga bisa menggunakan nilai alpha untuk transparansi.

Contoh 7-5 Mewarnai dengan tint()

PImage gambar;

void setup() {
  size(600, 450);
  gambar = loadImage("product-display-on.jpg");
  color warnaGambar = color (0, 200, 100);
  color warnaTransparan = color (255, 128);

  //gambar tanpa tint
  image(gambar, 0, 0);

  //gambar dengan tint
  tint (warnaGambar);
  image (gambar, 200, 0);

  //gambar dengan transparansi
  tint (warnaTransparan);
  image (gambar, 400, 0);
}

void draw() {
}

Masking Gambar

Masking adalah sebuah istilah yang digunakan untuk menutupi sebagian gambar dan menampilkan sebagian lainnya. Teknik ini adalah salah satu teknik manipulasi yang lazim digunakan dalam melakukan penyuntingan gambar. Para pengguna Photoshop tentunya sudah lazim mempraktekkannya.

Dalam Processing, masking ini dilakukan dengan menggunakan 2 gambar, 1 gambar yang akan di-masking dan 1 lagi adalah gambar yang datanya akan diambil untuk dijadikan mask. Yang perlu disiapkan adalah kedua gambar ini dan pastikan ukuran mereka sama.

Contoh 7-6 Masking Gambar

PImage img;
PImage imgMask;

void setup() {
  size(640, 360);
  img = loadImage("batik.jpg");
  imgMask = loadImage("mask.jpg");
  img.mask(imgMask);
  imageMode(CENTER);
}

void draw() {
  background(0, 102, 153);
  image(img, width/2, height/2);
}

Yang sebenarnya dilakukan oleh fungsi mask() adalah ia mengambil channel biru dari sebuah gambar yang akan dijadikan mask. Data ini kemudian dijadikan nilai alpha channel dari gambar yang akan dimasking. Ini yang menyebabkan sebagian gambar kemudian tidak ditampilkan.

Dalam praktiknya, akan lebih akurat jika gambar yang dijadikan mask hanya memiliki warna dalam skala grayscale atau hitam putih dan abu-abu.

Filter Gambar

Filter adalah sebuah teknik untuk mengubah beberapa aspek dalam gambar sekaligus. Misalnya mengubah warna, ketajaman, kecerahan sekaligus. Seringkali filter ini memberikan kesan berbeda dari gambar yang sama tanpa perlu melakukan banyak hal sekaligus.

Secara default, Processing memberikan beberapa filter yang langsung bisa digunakan. Sintaks penulisannya sama, hanya perlu diganti mode-nya sesuai yang dibutuhkan. Formatnya adalah sebagai berikut:

variabelGambar.filter(MODE, parameter)

Mode filter yang tersedia adalah:

  • THRESHOLD: mengubah gambar menjadi hitam dan putih. Parameternya antara 0.0 (hitam) dan 0.1 (putih)
  • GRAY: mengubah semua warna menjadi skala grayscale. Tidak ada parameter yang dibutuhkan
  • OPAQUE: mengubah channel alpha menjadi tidak transparan sepenuhnya. Tidak ada parameter yang dibutuhkan
  • INVERT: mengubah setiap pixel menjadi nilai kebalikannya. Tidak ada parameter yang dibutuhkan
  • POSTERIZE: membatasi setiap channel warna pada gambar sesuai dengan parameter yang digunakan. Parameternya bernilai 2 sampai 255.
  • BLUR: melakukan Gaussian Blur dan membuat gambar menjadi buram. Makin besar paramater maka makin tinggi tingkat keburaman yang dihasilkan
  • ERODE: Mengurangi area yang terang. Tidak ada parameter yang digunakan.
  • DILATE: Meningkatkan area yang terang. Tidak ada parameter yang digunakan.

Berikut ini adalah contoh aplikasi filter blur untuk memburamkan gambar.

Contoh 7-7 Filter Blur

PImage img;

void setup() {
  size(396, 396);
  img = loadImage("batik.jpg");
  img.filter(BLUR, 6);
}

void draw() {
  image(img, 0, 0);
}

Filter blur ini bisa kita gunakan untuk membuat latar belakang yang bertumpuk pada animasi yang kita buat nanti. Penggunaan lainnya adalah untuk mensimulasikan efek gerakan.

Latihan 7-1

Dengan menggunakan pengetahuan yang sudah didapat, buatlah program yang menampilkan gambar dan memanipulasi gambar tersebut secara otomatis seiring berjalannya waktu.

Menjadikan Gambar Sebagai Latar Belakang

Pada contoh-contoh sebelumnya, kita selalu membuat program dengan latar belakang warna. Namun sebenarnya, tidak hanya warna, gambar pun bisa diperlakukan oleh Processing sebagai latar belakang. Secara intuitif bisa kita tebak bahwa untuk melakukan hal ini, ada 2 hal yang bisa dipraktekkan:

  1. Menampilkan gambar terlebih dahulu, baru menampilkan bentuk-bentuk lain di atas gambar tersebut
  2. Mengubah parameter untuk fungsi background() dari warna, menjadi gambar.

Dalam prakteknya, keduanya mungkin saja dilakukan. MEski demikian, perlu berhati-hati juga, apabila kita melakukan pilihan pertama di dalam fungsi draw() maka otomatis Processing akan me-render gambar berulang-ulang kali dan kemudian menampilkan bentuk lain di atas gambar tersebut. Ini akan cukup banyak memakan memory, terlebih apabila gambarnya besar dan operasinya cukup rumit.

Lain halnya dengan opsi kedua. Di sini, Processing akan memperlakukan gambar sebagai latar belakang dan akan butuh lebih sedikit sumber daya untuk menjalankan program.

Lalu mengapa tidak menampilkan gambar di setup() dan menggambar bentuk lain yang bergerak di dalam draw()? Konsekuensi dari pilihan ini adalah gambar yang bergerak tidak akan dihapus dari buffer kartu grafis, hingga jejaknya akan nampak. Mirip dengan menggambar di draw() tanpa menjalankan background(). Apabila langkah ini diambil dengan menuliskan background() juga di dalam draw() maka otomatis gambar latar belakang akan tertimpa oleh apapun yang akan ditampilkan background().

Mari kita bandingkan kedua cara menampilkan gambar sebagai latar belakang animasi. Kali ini akan ditampilkan animasi sederhana berupa gerakan bola melingkar dengan latar belakang sebuah gambar.

Contoh 7-8 Gambar sebagai Latar Belakang

PImage img;
float angle = 30;

void setup() {
  size(400, 400);
  img = loadImage("batik.jpg");
  noStroke();
}

void draw() {
  background (0);
  image(img, 0, 0);
  float posX = width/2+(sin(radians(angle)) * random(98,100));
  float posY = height/2+(cos(radians(angle)) * 100);

  fill (0, 200, 100);
  ellipse (posX, posY, 30, 30);

  angle++;
}

Sekarang kita bandingkan cara tadi dengan cara kedua, yakni menuliskan gambar langsung sebagai argumen fungsi background()

Contoh 7-9 Gambar sebagai Argumen Background()

PImage img;
float angle = 30;

void setup() {
  size(400, 400);
  img = loadImage("batik.jpg");
  noStroke();
}

void draw() {
  background (img);
  float posX = width/2+(sin(radians(angle)) * random(98,100));
  float posY = height/2+(cos(radians(angle)) * 100);

  fill (0, 200, 100);
  ellipse (posX, posY, 30, 30);

  angle++;
}

Kedua cara tadi sekilas mirip dan imbasnya untuk gambar berukuran kecil memang tidak terlalu terasa. Perbedaan baru akan terasa sekali apabila kita membuat gambar untuk layar berukuran besar. Perlu dicatat juga, untuk cara kedua ini, dibutuhkan gambar latar belakang yang ukurannya sama dengan ukuran layar program kita.

Untuk mensimulasikan benda yang nampak jauh sekali, kita bisa mengaplikasikan filter blur untuk gambar latar belakang kita. Ini juga akan memberikan fokus kepada benda yang sedang bergerak, sehingga ia lebih nampak sebagai tokoh utama animasi. Trik-trik stereo seperti ini membuat animasi 2 dimensi kita nampak seperti berada dalam ruang 3 dimensi.

Contoh 7-10 Gambar Latar Belakang Buram

PImage img;
float angle = 30;

void setup() {
  size(400, 400);
  img = loadImage("batik.jpg");
  img.filter (BLUR, 2);
  noStroke();
}

void draw() {
  background (img);
  float posX = width/2+(sin(radians(angle)) * random(98,100));
  float posY = height/2+(cos(radians(angle)) * 100);

  fill (0, 200, 100);
  ellipse (posX, posY, 30, 30);

  angle++;
}

Latihan 7-2

Buatlah sebuah animasi sederhana dengan latar belakang beberapa gambar yang berbeda. Coba atur penempatan elemen-elemen latar belakang ini, aplikasikan juga efek blur untuk menimbulkan rasa kedalaman pada animasi.

Menyimpan Gambar

Gambar yang telah kita manipulasi sebelumnya, bisa kita simpan menjadi file sendiri. Ini kemudian berguna apabila kita ingin menggunakan gambar ini untuk keperluan lain. Tidak hanya itu, animasi yang sedang berjalan pun bisa kita simpan sebagai gambar guna keperluan dokumentasi atau lainnya.

Sekarang kita bisa menyimpan gambar yang kita buat di contoh sebelumnya dengan menggunakan fungsi save(). Agar Processing mengetahui dengan pasti di mana gambar akan disimpan, maka ia perlu mememesan ruang di buffer memory sebagai tempat penyimpanan gambar. Ini dilakukan dengan menggunakan fungsi createImage(). Dengan demikian, maka keseluruhan cara menyimpan gambar terangkum dalam contoh berikut ini:

Contoh 7-11 Menyimpan Gambar

PImage img;
PImage newImg;
float angle = 30;

void setup() {
  size(396, 396);
  img = loadImage("batik.jpg");
  newImg = createImage (396, 396, RGB);
  img.filter(BLUR, 2);
  noStroke();
}

void draw() {
  background (0);
  image (img, 0, 0);

  float posX = width/2+(sin(radians(angle)) * random(98, 100));
  float posY = height/2+(cos(radians(angle)) * 100);

  fill (0, 200, 100);
  ellipse (posX, posY, 30, 30);

  angle++;
}

void keyPressed() {
  newImg = img.get();
  newImg.save("cuplikanGambar.jpg");
}

void mousePressed() {
  saveFrame("cuplikanFrame-##.jpg");
}

Di contoh tadi juga ditunjukkan membuat gambar dengan metode saveFrame(). Perbedaan utamanya adalah, perhatikan bahwa fungsi save() hanya berlaku untuk data dengan tipe PImage, seperti variabel img pada contoh di atas. Sebaliknya, fungsi saveFrame() akan menyimpan apapun yang ada di layar, sehingga animasi dan bentuk yang dibuat secara dinamis pun akan ikut tersimpan dalam gambar yang diekspor.

Sintaks untuk menulis fungsi save() adalah

save ("namaFile.ekstensitipeFile");

Sementara fungsi saveFrame() bisa dijalankan tanpa parameter ataupun dengan parameter yang sama dengan save(). Apabila ingin memberi nomor pada hasil ekspor dengan saveFrame() maka kita bisa menulis

saveFrame("namaFile-####.tipeFile");

Di mana nantinya, hasil penyimpanan gambar adalah file dengan nama dan 4 digit angka seperti namaFile-0123.tipe.

Tipe file yang bisa disimpan oleh keduanya adalah TIFF, JPG, PNG dan GIF. Berikan tipe data pada saat memberikan parameter. Apabila tidak diberikan, maka otomatis file akan disimpan dalam format TIFF.