Photo Editing với Canvas

Giới thiệu

Canvas là một tính năng mới được thêm vào HTML5, được dùng để lập trình đồ họa hai chiều. Bằng việc sử dụng JavaScript API để thao tác <canvas>, chúng ta có thể tạo đồ họa hoặc animation có tương tác với người dùng. Hoặc vẽ đồ thị, trực quan hóa dữ liệu, thậm chí là xử lý video thời gian thực.

Cơ bản về canvas

Sử dụng thẻ <canvas> rất đơn giản như sau:

<canvas id="myCanvas" width="300" height="300" style="border: 1px solid #000;">
    <!-- Insert content here -->
</canvas>

Đoạn code trên chỉ tạo một canvas trống, với kích thước là 300x300, ngoài ra không còn gì khác và bạn sẽ không thể thấy bất cứ thứ gì trên trình duyệt, đơn giản vì chúng ta vẫn chưa làm gì với ngữ cảnh render 2D. Các thuộc tính widthheight nhằm xác định kích cỡ của phần tử canvas, nếu không có 2 thuộc tính này thì sẽ tự mặc định là width 300 và height 150.

Mục đích của thẻ <canvas> chỉ đơn giản là tạo một vùng bao để ta có thể xử lý đồ họa 2 chiều ở trong vùng bao đó. Tưởng tượng bạn tạo một tài liệu mới trong photoshop, khi đó tất cả thao tác xử lý của bạn chỉ có tác dụng trong tài liệu đó, <canvas> cũng có thể hiểu là một tài liệu như thế, khác biệt là nó dùng ở trên trình duyệt. Chúng ta vẽ trên ngữ cảnh kết xuất đồ họa 2 chiều, chứ không vẽ trên <canvas>. <canvas> được dùng để truy cập và hiển thị đồ họa mà thôi.

Ngữ cảnh đồ họa 2 chiều sử dụng hệ tọa độ Cartesian

he toa do.PNG

Để truy cập vào nội dung thẻ <canvas> trong ví dụ trên, ta có thể làm như sau:

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");

Đầu tiên, ta gán phần tử <canvas> đến biến canvas, sau đó lại gán ngữ cảnh kết xuất đồ họa 2 chiều đến biến context bằng phương thức getContext. Nếu bạn muốn sử dụng Jquery thay vì JavaScript thuần, ta có thể làm như sau:

var canvas = $("#myCanvas");
var context = canvas.get(0).getContext("2d");

canvas là một Jquery Object, nên bạn cần gọi đến phương thức get để trả về một DOM Object, sau đó dùng getContext để truy cập ngữ cảnh đồ họa 2 chiều tương tự như khi dùng Vanilla JavaScript.

Bây giờ, bạn có thể dùng biến context này để vẽ lên ngữ cảnh đồ họa 2 chiều. Trên Viblo đã có một bài giới thiệu về Canvas, về việc vẽ các hình cơ bản, bạn có thể xem thêm tại đây. https://viblo.asia/trungnn142/posts/PjxMeV6gG4YL

Trong bài viết này, mình sẽ tiếp cận <canvas> theo hướng thao tác với hình ảnh.

Photo Editing với canvas

Nói đến xử lý ảnh, chính xác hơn ở đây là image manipulation, mình muốn đề cập đến việc cắt ghép, chỉnh sửa ảnh (photo editing), tạo hiệu ứng và các bộ lọc thường thấy trong phần mềm Adobe Photoshop (một trong những phần mềm chỉnh sửa ảnh tốt nhất hiện nay), chứ không phải tiếp cận xử lý ảnh theo hướng học thuật như kiểu Digital Image Processing.

Thao tác và làm việc với image là một trong những tính năng thú vị của <canvas>. Chúng ta có thể dùng nó để tạo chuyển động với ảnh, đồ thị, sprite trong game. Nó hỗ trợ nhiều định dạng ảnh như PNG, GIF, JPEG. Thậm chí ta có thể dùng ảnh được kết xuất bởi các phần từ <canvas> khác (trên cùng một trang) để làm đầu vào xử lý.

Để import ảnh vào canvas cần 2 bước:

  • Tạo tham chiếu đến ảnh
  • Vẽ ảnh lên canvas sử dụng drawImage
var img = new Image();
img.onload = function() {
  context.drawImage(img, 0, 0);
}
img.src = 'image.jpg';

Ảnh với kích thước 200x200 anh goc.PNG

Lưu ý là nếu bạn gọi drawImage trước khi ảnh được tải lên hoàn toàn thì nó sẽ không làm bất cứ điều gì, đó là lý do cần event onload. Tiếp nữa là load event nên đặt trước phần tạo src cho image để tránh trường hợp không bắt sự kiện do ảnh đã được load hoặc cache.

Phương thức drawImage cung cấp nhiều cách để bạn vẽ ảnh lên canvas drawImage.PNG

drawImage(image, dx, dy);
drawImage(image, dx, dy, dWidth, dHeight);
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

image là tham chiếu đến ảnh

dx, dy là tọa độ canvas nơi ảnh sẽ được vẽ lên

dWidth, dHeight là kích thước ảnh bạn muốn vẽ lên canvas, cho phép scale ảnh. Nếu không thêm 2 tham số này thì ảnh sẽ không scale khi vẽ lên canvas.

sx, sy là tọa độ tính từ góc trên trái của phần ảnh sẽ được vẽ lên canvas

sWidth, sHeight là kích thước tính từ điểm (sX, sY) trên ảnh

Resizing và Cropping

Resizing Để resize ảnh rất đơn giản, chỉ cần thêm 2 tham số width và height vào phương thức drawImage, ảnh sẽ resize bằng với 2 tham số này.


context.drawImage(img, 0, 0, 300, 200)

resize.PNG

Cropping Để cropping ta sử dụng phương thức drawImage thứ 3, có thêm các tham số sX, sY, sWidth, sHeight


context.drawImage(image, 50, 50, 100, 100, 50, 50, 100, 100);

Để crop thì các tham số dWidth = sWidth, dHeight = sHeight. Nếu bạn muốn resize ảnh đã crop thì có thể thay đổi 2 tham số dWidth và dHeight.

crop.PNG

Transforming

Translate


context.translate(100, 100)

Dịch gốc hệ tọa độ của canvas đến điểm (x, y) translate.PNG

Rotation


context.rotate(angle)

Tham số angle tính theo chiều kim đồng hồ, đơn vị radian.


context.translate(150, 0);
context.rotate(0.7854);

Đoạn mã trên xoay ảnh 45 độ. rotate.PNG

Pixel Manipulation

Dù resize, crop và transform có thể dùng để tạo những hiệu ứng thú vị, tuy nhiên còn có một tính năng khác của canvas cho phép bạn sáng tạo còn nhiều hơn nữa, đó là pixel manipulation.

Phương thức cho phép bạn truy cập pixel trong canvas là getImageData. Phương thức này nhận 4 tham số: (x, y) là gốc tọa độ vùng pixel bạn muốn truy cập, chiều rộng và chiều cao của vùng pixel. Công thức như sau:


context.getImageData(x, y, width, height)

Nó sẽ trả về một object ImageData. Object này có 3 thuộc tính: width, height là kích thước vùng pixel bạn truy cập, và data, một CanvasPixelArray chứa thông tin về tất cả pixel trong vùng bạn truy cập. (tham khảo thêm ở đây ImageData Object)

Data ở đây là một chuỗi rgba pixel, kiểu như


[
  R1, G1, B1, A1,
  R2, G2, B2, A2,
  ...
]

Bằng việc truy xuất đến từng pixel, bạn có thể lấy được thông tin mã màu RGB của mỗi pixel. Và với việc viết lại mã màu cho từng pixel này, bạn có thể tạo được những hiệu ứng hoặc bộ lọc không khác gì dùng các phần mềm chỉnh sửa ảnh. Dưới đây mình sẽ giới thiệu vài hiệu ứng ảnh cơ bản mà bạn có thể sử dụng.

Grayscale

Giải thuật grayscale các bạn có thể tham khảo ở đây. http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/


var imgData = context.getImageData(0, 0, 200, 200);
var pixel = imgData.data;
for (var i = 0; i < pixel.length; i+=4) {
  var avg = 0.3 * pixel[i] + 0.59 * pixel[i+1] + 0.11 * pixel[i+2];
  pixel[i] = avg;
  pixel[i+1] = avg;
  pixel[i+2] = avg;
}
context.putImageData(imgData, 0, 0);

origin_img.PNG

Threshold

Thresholding (image processing)


var imgData = context.getImageData(0, 0, 200, 200);
var pixel = imgData.data;
for (var i = 0; i < pixel.length; i+=4) {
  var avg = 1 * pixel[i] + 0.89 * pixel[i+1] + 0.11 * pixel[i+2];
  pixel[i] = (avg >= 140) ? 255 : 0;
  pixel[i+1] = (avg >= 140) ? 255 : 0;
  pixel[i+2] = (avg >= 140) ? 255 : 0;
}
context.putImageData(imgData, 0, 0);

threshold.PNG

Tổng kết

Còn rất rất nhiều những bộ lọc hay ho mà bạn có thể tự khám phá, đơn giản là thay đổi các mã màu rgba cho mỗi pixel mà thôi. Mong rằng bài viết này sẽ giới thiệu được những khái niệm cơ bản trong việc sử dụng JavaSript và HTML5 Canvas trong việc chỉnh sửa ảnh.

Thank you for reading.

THAM KHẢO

  1. https://developer.mozilla.org