Luma: cách nhìn màu theo độ sáng thực tế
Để quyết định một màu có quá tối hay quá sáng hay không, ta không thể chỉ nhìn vào giá trị RGB lớn nhất hay nhỏ nhất. Mắt người cảm nhận độ sáng theo từng kênh màu khác nhau. Vì vậy, công thức luma được sử dụng để phản ánh cảm nhận thị giác tốt hơn.
// Tính độ sáng (luma) của màu dựa trên cảm nhận thị giác
// Công thức theo chuẩn Rec. 709
function getLuma(r, g, b) {
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
Luma cho kết quả ổn định và đủ chính xác để phục vụ các quyết định UX như lọc màu hoặc sắp xếp palette.
Lọc màu quá tối hoặc quá sáng để tránh palette “bẩn”
Trong ảnh thực tế, đặc biệt là ảnh chụp, luôn tồn tại các vùng gần như đen hoặc gần như trắng. Nếu đưa các màu này vào palette, bảng màu thường trở nên kém hữu ích cho thiết kế. Vì vậy, việc lọc các màu cực đoan là cần thiết.
// Lọc bỏ các màu quá tối hoặc quá sáng dựa trên luma
function filterExtremeColors(colors, opts) {
const ignoreVeryDark = !!opts.ignoreVeryDark;
const ignoreVeryBright = !!opts.ignoreVeryBright;
return colors.filter(c => {
const l = getLuma(c.r, c.g, c.b);
// Ngưỡng tối: gần như đen
if (ignoreVeryDark && l < 25) return false; // Ngưỡng sáng: gần như trắng if (ignoreVeryBright && l > 230) return false;
return true;
});
}
Các ngưỡng như 25 và 230 không mang tính học thuật, mà được chọn dựa trên trải nghiệm thực tế. Quan trọng hơn, nếu sau khi lọc mà palette rỗng, tool nên fallback lại dữ liệu gốc thay vì trả về kết quả trống.
Sắp xếp palette: dominance là mặc định hợp lý nhất
Một bảng màu tốt không chỉ có màu đẹp, mà còn phải có thứ tự hợp lý. Với ảnh đời thực, cách sắp xếp theo mức độ xuất hiện của màu thường cho kết quả dễ hiểu nhất với người dùng.
// Sắp xếp palette theo các tiêu chí khác nhau
function sortPalette(colors, mode) {
const cloned = colors.slice();
switch (mode) {
case "hue":
return cloned.sort((a, b) => {
const ha = rgbToHsl(a.r, a.g, a.b).h;
const hb = rgbToHsl(b.r, b.g, b.b).h;
return ha - hb;
});
case "brightness":
return cloned.sort((a, b) => {
const la = getLuma(a.r, a.g, a.b);
const lb = getLuma(b.r, b.g, b.b);
return la - lb;
});
case "dominance":
default:
// Màu xuất hiện nhiều nhất đứng trước
return cloned.sort((a, b) => (b.count || 0) - (a.count || 0));
}
}
Dominance phù hợp vì nó phản ánh trực tiếp bố cục màu của ảnh. Hue và brightness nên được xem là các tuỳ chọn nâng cao, dành cho người dùng có nhu cầu cụ thể hơn.
Không đoán thay người dùng: background là lựa chọn, không phải phán đoán
Nhiều tool cố gắng tự động nhận diện màu nền và loại bỏ nó. Nghe thì thông minh, nhưng trong thực tế lại rất dễ sai. Thay vì đoán, cách an toàn hơn là cho người dùng quyền quyết định.
// Nếu người dùng không muốn include background,
// loại bỏ màu đầu tiên sau khi đã sort
if (!options.includeBackground && clusters.length > 1) {
clusters = clusters.slice(1);
}
Cách làm này đơn giản, dễ hiểu và nhất quán. Tool không “thông minh” quá mức, nhưng đáng tin cậy.
Kết luận
Lọc và sắp xếp màu không phải là bước trang trí cuối cùng, mà là nơi UX quyết định chất lượng thuật toán. Bằng cách dùng luma để lọc màu cực đoan, sắp xếp theo dominance làm mặc định và trao quyền kiểm soát cho người dùng, palette tạo ra sẽ dễ dùng, dễ hiểu và ổn định hơn rất nhiều. Khi làm tool cho con người, thuật toán tốt nhất là thuật toán biết dừng đúng chỗ.
Bình luận