Nguyên nhân
Một số hình ảnh rất khó lấy mẫu và nội suy, chẳng hạn như hình này có các đường cong khi bạn muốn chuyển từ kích thước lớn sang kích thước nhỏ.
Các trình duyệt dường như thường sử dụng phép nội suy hai tuyến tính (lấy mẫu 2x2) với phần tử canvas thay vì hai khối (lấy mẫu 4x4) vì lý do hiệu suất (có thể xảy ra).
Nếu bước quá lớn thì đơn giản là không có đủ pixel để lấy mẫu mà từ đó được phản ánh trong kết quả.
Từ góc độ tín hiệu / DSP, bạn có thể xem đây là giá trị ngưỡng của bộ lọc thông thấp được đặt quá cao, điều này có thể dẫn đến hiện tượng răng cưa nếu có nhiều tần số cao (chi tiết) trong tín hiệu.
Giải pháp
Cập nhật 2018:
Đây là một mẹo nhỏ mà bạn có thể sử dụng cho các trình duyệt hỗ trợ thuộc filter
tính trên bối cảnh 2D. Điều này làm mờ trước hình ảnh về bản chất giống như lấy mẫu lại, sau đó thu nhỏ lại. Điều này cho phép các bước lớn nhưng chỉ cần hai bước và hai lần rút.
Làm mờ trước bằng cách sử dụng số bước (kích thước ban đầu / kích thước đích / 2) làm bán kính (bạn có thể cần điều chỉnh điều này theo kinh nghiệm dựa trên trình duyệt và các bước chẵn / lẻ - ở đây chỉ hiển thị đơn giản):
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
if (typeof ctx.filter === "undefined") {
alert("Sorry, the browser doesn't support Context2D filters.")
}
const img = new Image;
img.onload = function() {
const oc = document.createElement('canvas');
const octx = oc.getContext('2d');
oc.width = this.width;
oc.height = this.height;
const steps = (oc.width / canvas.width)>>1;
octx.filter = `blur(${steps}px)`;
octx.drawImage(this, 0, 0);
ctx.drawImage(oc, 0, 0, oc.width, oc.height, 0, 0, canvas.width, canvas.height);
}
img.src = "//i.stack.imgur.com/cYfuM.jpg";
body{ background-color: ivory; }
canvas{border:1px solid red;}
<br/><p>Original was 1600x1200, reduced to 400x300 canvas</p><br/>
<canvas id="canvas" width=400 height=250></canvas>
Hỗ trợ cho bộ lọc như ogf tháng 10 năm 2018:
CanvasRenderingContext2D.filter
api.CanvasRenderingContext2D.filter
On Standard Track, Experimental
https:
DESKTOP > |Chrome |Edge |Firefox |IE |Opera |Safari
:----------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------
filter ! | 52 | ? | 49 | - | - | -
MOBILE > |Chrome/A |Edge/mob |Firefox/A |Opera/A |Safari/iOS|Webview/A
:----------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------
filter ! | 52 | ? | 49 | - | - | 52
! = Experimental
Data from MDN - "npm i -g mdncomp" (c) epistemex
Cập nhật 2017: Hiện có một thuộc tính mới được xác định trong thông số kỹ thuật để đặt chất lượng lấy mẫu lại:
context.imageSmoothingQuality = "low|medium|high"
Nó hiện chỉ được hỗ trợ trong Chrome. Các phương pháp thực tế được sử dụng cho mỗi cấp độ là do nhà cung cấp quyết định, nhưng sẽ hợp lý khi giả định Lanczos cho "cao" hoặc một cái gì đó tương đương về chất lượng. Điều này có nghĩa là bước xuống có thể bị bỏ qua hoàn toàn hoặc các bước lớn hơn có thể được sử dụng với ít bản vẽ lại hơn, tùy thuộc vào kích thước hình ảnh và
Hỗ trợ cho imageSmoothingQuality
:
CanvasRenderingContext2D.imageSmoothingQuality
api.CanvasRenderingContext2D.imageSmoothingQuality
On Standard Track, Experimental
https:
DESKTOP > |Chrome |Edge |Firefox |IE |Opera |Safari
:----------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:
imageSmoothingQuality !| 54 | ? | - | ? | 41 | Y
MOBILE > |Chrome/A |Edge/mob |Firefox/A |Opera/A |Safari/iOS|Webview/A
:----------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:
imageSmoothingQuality !| 54 | ? | - | 41 | Y | 54
! = Experimental
Data from MDN - "npm i -g mdncomp" (c) epistemex
trình duyệt. Cho đến khi đó ..:
Kết thúc truyền
Giải pháp là sử dụng bước xuống để có được kết quả thích hợp. Bước xuống có nghĩa là bạn giảm kích thước theo từng bước để cho phép phạm vi nội suy giới hạn bao phủ đủ pixel để lấy mẫu.
Điều này sẽ cho phép kết quả tốt cũng với nội suy hai tuyến tính (nó thực sự hoạt động giống như hai khối khi làm điều này) và chi phí là tối thiểu vì có ít pixel hơn để lấy mẫu trong mỗi bước.
Bước lý tưởng là đi đến một nửa độ phân giải trong mỗi bước cho đến khi bạn đặt được kích thước mục tiêu (cảm ơn Joe Mabel đã đề cập đến vấn đề này!).
Fiddle sửa đổi
Sử dụng quy mô trực tiếp như trong câu hỏi ban đầu:
Sử dụng bước xuống như hình dưới đây:
Trong trường hợp này, bạn cần phải từ bỏ trong 3 bước:
Trong bước 1, chúng tôi giảm hình ảnh xuống một nửa bằng cách sử dụng canvas ngoài màn hình:
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = img.width * 0.5;
oc.height = img.height * 0.5;
octx.drawImage(img, 0, 0, oc.width, oc.height);
Bước 2 sử dụng lại canvas ngoài màn hình và lại thu nhỏ hình ảnh xuống một nửa:
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
Và chúng tôi vẽ một lần nữa vào canvas chính, một lần nữa giảm xuống một nửa nhưng đến kích thước cuối cùng:
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
0, 0, canvas.width, canvas.height);
Tiền boa:
Bạn có thể tính toán tổng số bước cần thiết, sử dụng công thức này (nó bao gồm bước cuối cùng để đặt kích thước mục tiêu):
steps = Math.ceil(Math.log(sourceWidth / targetWidth) / Math.log(2))