Vấn đề cốt lõi: không được phân tích ảnh gốc
Ảnh người dùng upload thường có độ phân giải rất lớn: 3000px, 4000px mỗi chiều là chuyện bình thường. Nếu bạn đọc toàn bộ pixel ảnh gốc để phân tích màu, số pixel có thể lên đến hàng chục triệu. Với JavaScript chạy trên main thread, đó là công thức chắc chắn gây lag.
Giải pháp đầu tiên và quan trọng nhất là: downscale ảnh trước khi làm bất kỳ phép tính nào. Không có ngoại lệ.
// Giảm kích thước ảnh để tránh xử lý quá nhiều pixel
// maxEdge: giới hạn cạnh lớn nhất của ảnh
function downscaleImage(img, maxEdge, cb) {
maxEdge = maxEdge || 800;
const biggestSide = Math.max(img.width, img.height);
// Nếu ảnh đã nhỏ, dùng trực tiếp
if (biggestSide <= maxEdge) {
cb(img);
return;
}
// Tính tỷ lệ scale để giữ nguyên aspect ratio
const ratio = maxEdge / biggestSide;
const w = Math.round(img.width * ratio);
const h = Math.round(img.height * ratio);
// Vẽ lại ảnh đã scale lên canvas
const canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";
ctx.drawImage(img, 0, 0, w, h);
// Xuất ảnh mới để dùng cho các bước phân tích tiếp theo
const scaled = new Image();
scaled.onload = () => cb(scaled);
scaled.src = canvas.toDataURL("image/png");
}
Việc giới hạn cạnh lớn nhất của ảnh (ví dụ 800px hoặc 1500px) giúp giảm số pixel xuống hàng chục lần, trong khi sự khác biệt về màu sắc gần như không đáng kể đối với mục đích trích xuất palette.
Sampling pixel: không cần đọc tất cả
Ngay cả sau khi downscale, ảnh vẫn có thể chứa hàng trăm nghìn pixel. Đọc toàn bộ vẫn là lãng phí. Thay vì vậy, ta chỉ cần lấy mẫu đại diện.
Chiến lược ở đây rất đơn giản: chia đều ảnh thành một lưới và chỉ lấy pixel tại các điểm giao. Số lượng mẫu được giới hạn cứng, ví dụ 4000 pixel.
// Lấy mẫu màu từ ảnh thay vì quét toàn bộ pixel
// maxSamples: số lượng mẫu tối đa mong muốn
function sampleImageColors(img, maxSamples) {
const canvas = document.createElement("canvas");
const maxEdge = 400;
let w = img.width;
let h = img.height;
const biggest = Math.max(w, h);
// Downscale thêm một lần để sampling nhanh hơn
if (biggest > maxEdge) {
const ratio = maxEdge / biggest;
w = Math.round(w * ratio);
h = Math.round(h * ratio);
}
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, w, h);
const data = ctx.getImageData(0, 0, w, h).data;
const totalPixels = w * h;
maxSamples = maxSamples || 4000;
// Tính step để phân bố mẫu đều toàn ảnh
const step = Math.max(1, Math.floor(Math.sqrt(totalPixels / maxSamples)));
const samples = [];
for (let y = 0; y < h; y += step) {
for (let x = 0; x < w; x += step) {
const idx = (y * w + x) * 4;
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
const a = data[idx + 3];
// Bỏ pixel trong suốt để tránh nhiễu màu
if (a < 32) continue;
samples.push({ r, g, b });
}
}
return samples;
}
Điểm mấu chốt nằm ở công thức tính step. Thay vì chọn ngẫu nhiên, bước nhảy được tính dựa trên tổng số pixel và số mẫu mong muốn, giúp các mẫu trải đều trên toàn bộ ảnh.
Vì sao “đủ chuẩn” quan trọng hơn “chính xác tuyệt đối”
Một tool trích xuất màu phục vụ con người, không phải máy đo quang phổ. Mục tiêu là tạo ra bảng màu nhìn hợp lý, phản ánh tổng thể ảnh, chứ không phải thống kê chính xác từng pixel.
Với downscale và sampling hợp lý, bạn có thể giảm thời gian xử lý từ vài trăm mili giây xuống vài chục mili giây, tránh block main thread và giữ trải nghiệm mượt ngay cả trên những máy cấu hình yếu.
Đổi lại, bạn chấp nhận mất đi một lượng rất nhỏ độ chính xác, thứ mà phần lớn người dùng thậm chí không thể nhận ra bằng mắt thường.
Kết luận
Trích xuất màu từ ảnh trong trình duyệt không phải là bài toán phức tạp, nhưng rất dễ làm sai nếu xử lý ảnh một cách ngây thơ. Nguyên tắc sống còn là luôn downscale ảnh trước khi phân tích, chỉ lấy mẫu đại diện thay vì quét toàn bộ pixel, và ưu tiên hiệu năng cùng UX hơn độ chính xác tuyệt đối. Làm đúng ba điều này, bạn đã có nền tảng vững chắc cho mọi tool xử lý màu client-side, từ color palette đơn giản cho đến các công cụ phân tích hình ảnh phức tạp hơn.
Bình luận