FabricJs: Sesi Melukis

FabricJs: Sesi Melukis

Tutorial Information

Programfabricjs
Version0.8.32
DifficultyStandar
Estimated Time30 Menit
DownloadClick this link

Pada seri fabricjs sebelumnya, semua objek ketika pertama kali diciptakan sudah memiliki ukuran. Ukuran ini ditentukan melalui properti yang dilberikan kepada konstruktor masing-masing objek….

Pada seri fabricjs sebelumnya, semua objek ketika pertama kali diciptakan sudah memiliki ukuran. Ukuran ini ditentukan melalui properti yang dilberikan kepada konstruktor masing-masing objek. Namun, bila kita membuat objek dengan cara tersebut, sebenarnya kita belum benar-benar menggambar.

Ketika melukis, seharusnya tidak ada yang tergambarkan di atas canvas selama kuas (mouse) tidak bersentuhan dengan canvas (kecuali kalau disemprot, tapi itu kasus lain). Dan, bentuk dari objek yang kita buat, tidak tiba-tiba memiliki suatu ukuran tertentu. Tetapi ukuran itu sesuai dengan seberapa lebar objek tersebut kita gambarkan.

Ilustrasinya adalah seperti pada gambar dibawah ini.

Ilustrasi Melukis

Ilustrasi Melukis

Untuk mengimplementasikan contoh pada gambar di atas, penulis menawarkan langkah sebagai berikut:

  1. Ketika mouse ditekan pada suatu titik di dalam canvas, maka titik tersebut kita simpan sebagai nilai x1, y1.
  2. Kita juga memerlukan suatu variabel khusus untuk memberitahukan kepada kita, bahwa kita sedang melukis.
  3. Ketika mouse kita bergerak, maka kita perlu merubah ukuran dari objek yang hendak kita buat sesuai dengan titik dimana mouse sekarang berada.
  4. Proses nomor 3 akan berhenti ketika user tidak lagi menekan tombol mouse. Pada titik ini, objek kita sudah berbentuk dan berukuran sesuai dengan yang kita harapkan. Dengan kata lain, kita baru saja selesai melukis suatu objek.

Penulis sudah menyiapkan demo yang menerapkan kurang lebih langkah-langkah yang penulis jelaskan di atas. Demo dapat pembaca lihat pada halaman berikut:

http://jsfiddle.net/keripix/kkQxG/1/

Persiapan

function create(e) {
        var id = e.target.id;

        activateDrawing({
            type: id
        });
    }

    function activateDrawing(params) {
        canvas.defaultCursor = 'crosshair';

        if (params.type === 'rect') {
            drawing = new Rectangle();
        } else if (params.type === 'circle') {
            drawing = new Circle();
        } else if (params.type === 'image') {
            drawing = new FImage();
        } else if (params.type === 'sketch') {
            drawing = new Sketch();
        }

        if (drawing) {
            canvas.observe({
                'mouse:move': drawing.update,
                'mouse:down': drawing.create,
                'mouse:up': stopDrawing
            });
        }
    }

Pada demo di atas, ketika tombol kotak, lingkaran, dan garis ditekan, maka fungsi create akan dijalankan. Ketika fungsi ini dijalankan, maka id dari tombol yang ditekan akan disimpan dan dijadikan sebagai penentu objek yang hendak digambar. Itulah mengapa penulis memilih nama id yang merepresentasikan objek yang didukung oleh fabricjs.

Fungsi create akan memanggil fungsi activateDrawing. Parameter yang dilemparkan akan mengandung id dari objek yang hendak dibuat. Bila id bernilai rect, maka objek Rectangle yang akan dibuat. Bilai bernilai circle, maka objek Circle yang akan dibuat. Dan begitu pula seterusnya.

Instanta dari objek yang dibuat tadi akan disimpan ke dalam variabel drawing. Kita akan menggunakan variabel ini untuk mengakses instanta tersebut untuk tahapan melukis selanjutnya.

Yang perlu disadari adalah, nilai dari instanta akan tergantung dari objek yang hendak dibuat. Namun, meskipun objeknya berbeda, kita dapat menseragamkan metode-metode yang dimiliki oleh objek-objek tersebut. Metode yang seragam tersebut adalah metode create dan update.

Kita perlu memasang event-listener untuk mengetahui status dari proses pelukisan. Event yang perlu didengarkan adalah pada saat mouse ditekan (mouse:down), mouse digerakkan (mouse:move) dan mouse diangkat (mouse:up).

Proses pelukisan berakhir dengan dijalankannya metode stopDrawing. Kode untuk fungsi stopDrawing adalah sebagai berikut:

function create(e) {
        var id = e.target.id;

        activateDrawing({
            type: id
        });
    }

    function activateDrawing(params) {
        canvas.defaultCursor = 'crosshair';

        if (params.type === 'rect') {
            drawing = new Rectangle();
        } else if (params.type === 'circle') {
            drawing = new Circle();
        } else if (params.type === 'image') {
            drawing = new FImage();
        } else if (params.type === 'sketch') {
            drawing = new Sketch();
        }

        if (drawing) {
            canvas.observe({
                'mouse:move': drawing.update,
                'mouse:down': drawing.create,
                'mouse:up': stopDrawing
            });
        }
    }

Untuk saat ini, kita hanya akan membahas secara umum kode di atas. Intinya, ketika user selesai melukis, maka kita perlu melepas event-listener dari mendengarkan event mouse:move, mouse:up dan mouse:down yang sudah dipasang sebelumnya.

Variabel instance juga perlu untuk di-null-kan. Sebab variabel inilah yang membantu untuk menunjukkan apakah user sedang melukis atau tidak. Sebab ia akan bernilai null bila user sedang tidak melukis.

Kotak

Pertama yang harus kita lakukan adalah menginisiasi objek yang mengatur pelukisan objek kotak.

function Rectangle() {
}

Fungsi kosong di atas adalah sebuah constructor untuk class Rectangle. Untuk keperluan demo ini, penulis tidak menjalankan apa-apa di dalam constructor tersebut. Jadi, bila user hendak melukis kotak, maka codenya adalah sebagai berikut:

drawing = new Rectangle();

Dengan menjalankan seperti pada contoh kode di atas, maka drawing menyimpan instanta dari class Rectangle. Instanta inilah yang akan dimanfaatkan untuk membuat kotak dan melukisnya.

Untuk membuat kotak pertama, metode create-lah yang bertanggung-jawab. Untuk mengupdate kotak berdasarkan pergerakan mouse, metode update yang akan dijalankan.

    Rectangle.prototype.create = function(e) {
        var point = canvas.getPointer(e.e);
        instance = new fabric.Rect({
            fill: 'red',
            width: 2,
            height: 2,
            top: point.y,
            left: point.x
        });

        canvas.add(instance);
        canvas.renderAll();
    }
Rectangle.prototype.update = function(e) {
        if (instance instanceof fabric.Rect) {
            var topLeft = instance.get('oCoords').tl,
                point = canvas.getPointer(e.e),
                width = point.x - topLeft.x,
                height = point.y - topLeft.y,
                top = height / 2 + topLeft.y,
                left = width / 2 + topLeft.x;

            instance.set({
                top: top,
                left: left,
                width: Math.abs(width),
                height: Math.abs(height)
            });

            canvas.renderAll();
        }
    }

Pada metode create dan update di atas, terdapat kode berikut:

var point = canvas.getPointer(e.e);

Kode tersebut berguna untuk membaca titik koordinat dimana mouse berada di dalam canvas. Tetapi apa itu parameter e.e yang diberikan kepada metode getPointer?

e yang pertama adalah objek event yang dilemparkan saat event sedang dipublikasikan. Pada objek e yang pertama, terdapat sebuah properti yaitu e, dimana properti e ini menyimpan informasi yang kita butuhkan, yaitu koordinat dimana mouse berada. Dengan memberikan objek e.e kepada metode getPointer, kita memperoleh sebuah objek berisikan properti berupa kordinat x dan y.

Setelah mengetahui dimana kita harus membuat kotak pertama kita, maka tahapan selanjutnya adalah membuat instanta dari objek fabric.Rect. Instanta dari fabric.Rect kita simpan pada variabel instance.

Perlu diperhatikan bahwa variabel drawing dan instance merujuk pada dua instanta yang berbeda. Variabel drawing merujuk kepada instanta dari objek yang akan mengatur proses pelukisan. Sementara itu, variabel instance merujuk pada instanta dari objek fabric yang sedang dilukus.

Bila pembaca mengingat kembali tahapan melukis no 2, kita membutuhkan sebuah variabel yang membantu kita mengetahui apakah user sedang melukis atau tidak. Kita dapat menggunakan variabel instance untuk tujuan tersebut.

Inilah mengapa di proses update, kita memeriksa terlebih dahulu apakah instance adalah instanta dari fabric.Rect. Bila ia, maka kita mengetahui bahwa kita memang sedang melukis, dan secara khusus sedang melukis kotak.

Dan dengan alasan di atas pula, mengapa kita perlu me-null-lan variabel instance ketika metode stopDrawing dijalankan:

instance = null;

Pada metode update, ada bagian yang menarik, yaitu:

instance.get('oCoords').tl;

Metode di atas akan mengembalikan sekumpulan objek yang menyimpan koordinat titik-titik resize. Titik-titik resize ini adalah objek berbentuk kotak yang memungkinkan user mengubah ukuran dari sebuah objek fabric.

properti dari oCoords

Properti dari oCoords

Mengapa properti oCoords sangat penting? Silahkan pembaca melihat gambar berikut:

kotak

Ilustrasi Melukis Kotak

Berdasarkan gambar di atas, titik A tidak berubah. Semetara itu titik top dan left berubah sesuai denga pergerakan mouse. Bagaimana caranya agar kita mengetahui koordinat titik A? Disinilah properti oCoords hadir untuk membantu. Titik A sebenarnya sama dengan oCoords.tl.

Jadi, kita sudah dapat mengetahui koordinat titik A dan koordinat mouse. Sehingga, yang perlu kita lakukan adalah menentukan panjang, lebar, koordinat top dan koordinat left. Pembaca dapat membaca kembali kode pada Rectangle.prototype.update untuk mengetahui bagaimana caranya kita dapat memperoleh empat properti tersebut.

Lingkaran

Tahapan melukis lingkaran secara umum sama dengan tahapan melukis kotak. Bedanya kita hanya perlu mengupdate ukuran radius dari kotak dimana ukuran tersebut disesuaikan dengan posisi mouse berada. Silahkan pembaca melihat gambar berikut.

Lingkaran

Ilustrasi Melukis Lingkaran

Untuk mengimplementasikan konsep yang diilustrasikan oleh gambar di atas, metodenya adalah sebagai berikut:

function Circle() {
}

Circle.prototype.create = function(e) {
var point = canvas.getPointer(e.e);

instance = new fabric.Circle({
fill: 'blue',
radius: 3,
top: point.y,
left: point.x
});

canvas.add(instance);
canvas.renderAll();
}

Circle.prototype.update = function(e) {
if (instance instanceof fabric.Circle) {
var point = canvas.getPointer(e.e),
top = instance.get('top'),
left = instance.get('left'),

// Rumus menghitung jarak antara dua titik
radius = Math.sqrt(Math.pow(point.x - left, 2) + Math.pow(point.y - top, 2));

instance.setRadius(radius);
canvas.renderAll();
}
}

Pena (Sketch)

Bagian ini adalah yang paling mudah, karena pembaca tinggal mengubah properti isDrawingMode milik canvas menjadi true. Dengan perubahan ini, canvas secara internal akan membaca titik perpindahan mouse dan membuat path baru dari titik lama ke titik baru tersebut.

Gambar

Melukis objek gambar (instanta dari fabric.Image) tidaklah jauh berbeda dengan kotak. Namun ada satu hal yang berbeda secara signifikan dengan kotak, yaitu mekanisme update terkait dengan penentuan ukuran dan kordinat baru objek gambar.

FImage.prototype.update = function(e) {
        if (instance instanceof fabric.Image) {
            var point = canvas.getPointer(e.e),
                topLeft = instance.get('oCoords').tl,
                width = Math.abs(point.x - topLeft.x),
                height = Math.abs(point.y - topLeft.y),
                top, left;

            if (point.x < topLeft.x && point.y < topLeft.y) {
                top = height / 2 + point.y;
                left = width / 2 + point.x;
            } else if (point.x < topLeft.x) {
                top = height / 2 + topLeft.y;
                left = width / 2 + point.x;
            } else if (point.y < topLeft.y) {
                top = height / 2 + point.y;
                left = width / 2 + topLeft.x;
            } else {
                top = height / 2 + topLeft.y;
                left = width / 2 + topLeft.x;
            }

            instance.set({
                top: top,
                left: left,
                width: width,
                height: height
            });

            canvas.renderAll();
        }
    }

Silahkan pembaca melihat ilustrasi berikut untuk memahami pendekatan yang berbeda tersebut:

Posisi point. dan point.y

Posisi point.x dan point.y Harus Diperhatikan Karena Menentukan Nilai Top dan Left

Berdasarkan gambar tersebut, dapatkah pembaca memahami mengapa kita perlu memeriksa posisi mouse terbaru dengan titik A sebelumnya? Mungkin untuk memahami masalah ini, pembaca dapat mengganti metode update milik Fimage dengan metode update miliki Rectange. Pembaca akan menemukan bahwa akan terjadi kesalahan dalam proses update bila hal tersebut dilakukan.

Penulis yakin bahwa solusi untuk mengatasi masalah di atas tidaklah satu solusi saja. Jadi, bagi pembaca yang memiliki pendekatan yang berbeda, penulis sangat tertarik untuk mengetahui pendekatan tersebut. Jangan malu untuk berbagi kode pada komen di bawah ya.

Penutup

Bila pembaca mengikuti seri fabricjs ini dari awal, maka pada titik ini, pembaca sudah mengetahui tentang cara membuat objek, cara mengaturnya, bermain dengan event, dan metode untuk menjadikan pembuatan objek tadi lebih hidup. Nah, bagaimana kalau kita hendak membuat permainan?

Pada kesempatan berikutnya, penulis akan berusaha untuk menghadirkan contoh sederhana dalam membuat game. Sampai pada seri berikutnya. Dan selamat mengutak-atik canvas menggunakan fabricjs.

Tag: , , ,

Write Comment

Your email will not be published. The marked label is required.