“Nén tấm ảnh này” là bốn thao tác khác nhau nấp sau một cái nút. Biết được thao tác nào lấy đi chất lượng của bạn sẽ khiến các tùy chọn bớt bí ẩn đi rất nhiều.
Bước một: giải mã
Tệp đã nén được dựng ngược lại thành một lưới điểm ảnh. Trình duyệt làm việc này bằng mã máy gốc, có tăng tốc phần cứng ở nơi nền tảng cho phép, và xử lý được JPEG, PNG, WebP lẫn AVIF — thêm HEIC trên các nền tảng của Apple, vì hệ điều hành ở đó đã sẵn có bộ giải mã.
So với tệp, việc giải mã không làm mất gì: bạn nhận đúng những điểm ảnh mà tệp mô tả. Mất mát đã xảy ra từ trước, lúc thứ tạo ra tệp mã hóa nó.
Bước hai: hướng ảnh
Camera điện thoại lưu hướng thô của cảm biến kèm một thẻ EXIF riêng cho biết cần xoay thế nào. Phần mềm bỏ qua thẻ đó sẽ cho ra ảnh nằm ngang. Áp thẻ đó nghĩa là xoay chính lưới điểm ảnh trước khi bất cứ thứ gì khác động vào — sau đó thì thẻ không còn cần nữa, và đây là một phần lý do vì sao việc mã hóa lại làm mất siêu dữ liệu.
Bước ba: lấy mẫu lại
Nếu không thể đạt dung lượng đích ở độ phân giải gốc, ảnh sẽ được thu nhỏ. Đây là chỗ rất nhiều chất lượng cảm nhận được lặng lẽ giữ lại hoặc mất đi. Một bộ lọc box rẻ tiền chỉ lấy trung bình các điểm ảnh kề nhau và làm mọi thứ mềm nhũn; bộ lọc Lanczos cân nhắc một vùng lân cận rộng hơn và giữ cho đường biên sắc nét.
Lấy mẫu lại đúng là thao tác phá hủy — những điểm ảnh bị bỏ đi không quay lại — nhưng nó lại thường là cách ít phá hủy nhất để về được một mức đích nhỏ, vì nó dọn đi phần chi tiết mà nếu giữ, bộ mã hóa sẽ tiêu hết ngân sách byte để mô tả rồi vẫn thất bại.
Bước bốn: mã hóa
Phần có mất dữ liệu. JPEG chuyển ảnh sang cách biểu diễn tách độ sáng và màu, bỏ bớt độ phân giải màu (mắt gần như không nhận ra), chia ảnh thành các khối 8×8, biến đổi từng khối thành các thành phần tần số, rồi lượng tử hóa — làm tròn để loại bỏ chi tiết tần số cao. Tham số chất lượng chính là độ thô của phép làm tròn đó. Mọi thứ sau bước ấy là mã hóa entropy không mất dữ liệu, và đó là lý do một bảng Huffman được tối ưu hay một lần quét lũy tiến giúp tiết kiệm byte mà không mất gì.
WebAssembly xen vào chỗ nào
Bộ mã hóa có sẵn của trình duyệt là một hộp đen: bạn chỉ được đưa vào một con số chất lượng, hết. Không có bảng entropy tối ưu, không được chọn baseline hay progressive, không kiểm soát được bộ lọc lấy mẫu lại, và không có cách nào biết một thiết lập sẽ cho ra bao nhiêu byte nếu không thử.
Một codec biên dịch sang WebAssembly trả lại cho bạn tất cả những thứ đó, đổi lại là số byte bạn tốn để tải nó về. ToolZool chạy toàn bộ chuỗi xử lý theo cách này — giải mã, xoay hướng, lấy mẫu lại và mã hóa đều là Rust biên dịch sang WebAssembly, chạy trong một worker nền — thay vì giao khâu giải mã cho trình duyệt. Có ba lý do, đều rất thực tế:
- Phép dò cần mã hóa nhiều lần. Muốn về đúng một dung lượng thì phải mã hóa cùng tấm ảnh năm sáu lượt. Tự nắm bộ mã hóa mới khiến việc đó rẻ và lặp lại được.
- Bộ giải mã phải biết nói không. Một tệp khai báo 60.000 × 60.000 pixel phải bị từ chối trước khi nó kịp cấp phát bộ nhớ, còn một tệp bị cắt cụt phải báo lỗi chứ không được trả về nửa tấm ảnh. Bộ giải mã của trình duyệt tự quyết định những chuyện đó; bộ của chúng tôi nêu rõ giới hạn và thực thi đúng giới hạn ấy.
- Cùng một đầu vào phải cho cùng một đầu ra. Các bộ giải mã của trình duyệt khác nhau ở cách xử lý màu và ở cách đối xử với tệp hỏng. Một chuỗi xử lý nắm cả hai đầu sẽ cho ra cùng những byte đó trên mọi thiết bị, và đó mới là thứ tạo nên một kết quả bạn có thể kiểm chứng.
Cái giá thì nói thẳng: engine ấy là một lượt tải về. Nó chỉ được tải khi bạn thực sự chọn một tệp, nên đọc một trang như trang này không tốn gì cho nó cả.
Vì sao phép dò quan trọng hơn codec
Với một dung lượng đích cho trước, khoảng cách giữa một bộ mã hóa tốt và một bộ xuất sắc chỉ là vài phần trăm. Khoảng cách giữa việc đoán mò thanh trượt chất lượng và việc dò ra đúng mức thì lớn hơn nhiều, vì một cú đoán chỉ dùng hết 60% hạn mức cho phép là đã vứt đi một phần ba chất lượng mà bạn đáng được hưởng. Đó là toàn bộ ý tưởng đằng sau việc nén về đúng dung lượng, còn bài đưa ảnh về dưới giới hạn 50 KB cho thấy phép dò mang lại điều gì ở đầu khắc nghiệt nhất của thang đo.

