[英]Resize image with javascript canvas (smoothly)
我正在嘗試用畫布調整一些圖像的大小,但我對如何平滑它們一無所知。 在 photoshop、瀏覽器等上,他們使用了一些算法(例如雙三次、雙線性),但我不知道這些是否內置到畫布中。
這是我的小提琴: http : //jsfiddle.net/EWupT/
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);
第一個是普通調整大小的圖像標簽,第二個是畫布。 請注意畫布是如何不平滑的。 我怎樣才能達到“平滑”?
您可以使用向下步進來獲得更好的結果。 大多數瀏覽器在調整圖像大小時似乎使用線性插值而不是雙三次。
(更新規范中添加了一個質量屬性, imageSmoothingQuality
,目前僅在 Chrome 中可用。)
除非選擇不平滑或最近鄰,否則瀏覽器將始終在縮小圖像后插入圖像,因為此功能作為低通濾波器以避免混疊。
雙線性使用 2x2 像素進行插值,而雙三次使用 4x4,因此通過分步進行,您可以在使用雙線性插值時接近雙三次結果,如結果圖像中所示。
var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); var img = new Image(); img.onload = function () { // set size proportional to image canvas.height = canvas.width * (img.height / img.width); // step 1 - resize to 50% 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); // step 2 octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5); // step 3, resize to final size ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5, 0, 0, canvas.width, canvas.height); } img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234"> <canvas id="canvas" width=300></canvas>
根據您調整大小的劇烈程度,如果差異較小,您可以跳過第 2 步。
在演示中,您可以看到新結果現在與圖像元素非常相似。
由於Trung Le Nguyen Nhat 的小提琴根本不正確(它只是在最后一步使用了原始圖像)
我用性能比較寫了自己的一般小提琴:
基本上是:
img.onload = function() {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext("2d"),
oc = document.createElement('canvas'),
octx = oc.getContext('2d');
canvas.width = width; // destination canvas size
canvas.height = canvas.width * img.height / img.width;
var cur = {
width: Math.floor(img.width * 0.5),
height: Math.floor(img.height * 0.5)
}
oc.width = cur.width;
oc.height = cur.height;
octx.drawImage(img, 0, 0, cur.width, cur.height);
while (cur.width * 0.5 > width) {
cur = {
width: Math.floor(cur.width * 0.5),
height: Math.floor(cur.height * 0.5)
};
octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
}
ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}
我創建了一個可重用的 Angular 服務來為任何感興趣的人處理圖像/畫布的高質量調整: https : //gist.github.com/transitive-bullshit/37bac5e741eaec60e983
該服務包括兩種解決方案,因為它們都有自己的優點/缺點。 lanczos 卷積方法以速度較慢為代價獲得更高質量,而逐步降尺度方法產生合理的抗鋸齒結果並且速度明顯更快。
用法示例:
angular.module('demo').controller('ExampleCtrl', function (imageService) {
// EXAMPLE USAGE
// NOTE: it's bad practice to access the DOM inside a controller,
// but this is just to show the example usage.
// resize by lanczos-sinc filter
imageService.resize($('#myimg')[0], 256, 256)
.then(function (resizedImage) {
// do something with resized image
})
// resize by stepping down image size in increments of 2x
imageService.resizeStep($('#myimg')[0], 256, 256)
.then(function (resizedImage) {
// do something with resized image
})
})
雖然其中一些代碼片段簡短且有效,但遵循和理解它們並非易事。
由於我不喜歡堆棧溢出中的“復制粘貼”,我希望開發人員了解他們推送到軟件中的代碼,希望您會發現以下有用。
演示:使用 JS 和 HTML Canvas Demo fiddler 調整圖像大小。
您可能會找到 3 種不同的方法來調整大小,這將幫助您了解代碼的工作方式以及原因。
https://jsfiddle.net/1b68eLdr/93089/
可以在 GitHub 項目中找到演示的完整代碼以及您可能希望在代碼中使用的 TypeScript 方法。
https://github.com/eyalc4/ts-image-resizer
這是最終的代碼:
export class ImageTools {
base64ResizedImage: string = null;
constructor() {
}
ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
let img = new Image();
img.src = base64image;
img.onload = () => {
// Check if the image require resize at all
if(img.height <= height && img.width <= width) {
this.base64ResizedImage = base64image;
// TODO: Call method to do something with the resize image
}
else {
// Make sure the width and height preserve the original aspect ratio and adjust if needed
if(img.height > img.width) {
width = Math.floor(height * (img.width / img.height));
}
else {
height = Math.floor(width * (img.height / img.width));
}
let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
let resizingCanvasContext = resizingCanvas.getContext("2d");
// Start with original image size
resizingCanvas.width = img.width;
resizingCanvas.height = img.height;
// Draw the original image on the (temp) resizing canvas
resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);
let curImageDimensions = {
width: Math.floor(img.width),
height: Math.floor(img.height)
};
let halfImageDimensions = {
width: null,
height: null
};
// Quickly reduce the dize by 50% each time in few iterations until the size is less then
// 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
// created with direct reduction of very big image to small image
while (curImageDimensions.width * 0.5 > width) {
// Reduce the resizing canvas by half and refresh the image
halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);
resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
0, 0, halfImageDimensions.width, halfImageDimensions.height);
curImageDimensions.width = halfImageDimensions.width;
curImageDimensions.height = halfImageDimensions.height;
}
// Now do final resize for the resizingCanvas to meet the dimension requirments
// directly to the output canvas, that will output the final image
let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
let outputCanvasContext = outputCanvas.getContext("2d");
outputCanvas.width = width;
outputCanvas.height = height;
outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
0, 0, width, height);
// output the canvas pixels as an image. params: format, quality
this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);
// TODO: Call method to do something with the resize image
}
};
}}
我不明白為什么沒有人建議createImageBitmap
。
createImageBitmap(
document.getElementById('image'),
{ resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap =>
document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);
效果很好(假設您為圖像和畫布設置了 id)。
我創建了一個庫,允許您在保留所有顏色數據的同時降低任何百分比。
https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js
您可以在瀏覽器中包含該文件。 結果看起來像photoshop或image magick,保留所有顏色數據,平均像素,而不是取附近的並丟棄其他的。 它不使用公式來猜測平均值,而是采用精確的平均值。
基於 K3N 答案,我通常為任何人重寫代碼
var oc = document.createElement('canvas'), octx = oc.getContext('2d');
oc.width = img.width;
oc.height = img.height;
octx.drawImage(img, 0, 0);
while (oc.width * 0.5 > width) {
oc.width *= 0.5;
oc.height *= 0.5;
octx.drawImage(oc, 0, 0, oc.width, oc.height);
}
oc.width = width;
oc.height = oc.width * img.height / img.width;
octx.drawImage(img, 0, 0, oc.width, oc.height);
更新 JSFIDDLE 演示
這是我的在線演示
我寫了一個小的 js-utility 來裁剪和調整前端圖像的大小。 這是 GitHub 項目的鏈接。 您也可以從最終圖像中獲取 blob 以發送它。
import imageSqResizer from './image-square-resizer.js'
let resizer = new imageSqResizer(
'image-input',
300,
(dataUrl) =>
document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);
//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;
這是我的代碼,我希望它對 SO 社區中的某些人有用:
您可以將目標圖像尺寸作為參數包含在腳本調用中。 這將是圖像寬度或高度的結果值,以較大者為准。 調整較小的尺寸,保持圖像縱橫比不變。 您還可以在腳本中對默認目標大小進行硬編碼。
您可以輕松更改腳本以滿足您的特定需求,例如您想要的圖像類型(默認為“圖像/png”)用於輸出,並決定您想要按百分比調整圖像大小以獲得更好的結果(請參閱代碼中的常量百分比步驟)。
const ResizeImage = ( _ => {
const MAX_LENGTH = 260; // default target size of largest dimension, either witdth or height
const percentStep = .3; // resizing steps until reaching target size in percents (30% default)
const canvas = document.createElement("canvas");
const canvasContext = canvas.getContext("2d");
const image = new Image();
const doResize = (callback, maxLength) => {
// abort with error if image has a dimension equal to zero
if(image.width == 0 || image.height == 0) {
return {blob: null, error: "either image width or height was zero "};
}
// use caller dimension or default length if none provided
const length = maxLength == null ? MAX_LENGTH : maxLength;
canvas.width = image.width;
canvas.height = image.height;
canvasContext.drawImage(image, 0, 0, image.width, image.height);
// if image size already within target size, just copy and return blob
if(image.width <= length && image.height <= length) {
canvas.toBlob( blob => {
callback({ blob: blob, error: null });
}, "image/png", 1);
return;
}
var startDim = Math.max(image.width, image.height);
var startSmallerDim = Math.min(image.width, image.height);
// gap to decrease in size until we reach the target size,
// be it by decreasing the image width or height,
// whichever is largest
const gap = startDim - length;
// step length of each resizing iteration
const step = parseInt(percentStep*gap);
// no. of iterations
var nSteps = 0;
if(step == 0) {
step = 1;
} else {
nSteps = parseInt(gap/step);
}
// length of last additional resizing step, if needed
const lastStep = gap % step;
// aspect ratio = value by which we'll multiply the smaller dimension
// in order to keep the aspect ratio unchanged in each iteration
const ratio = startSmallerDim/startDim;
var newDim; // calculated new length for the bigger dimension of the image, be it image width or height
var smallerDim; // length along the smaller dimension of the image, width or height
for(var i = 0; i < nSteps; i++) {
// decrease longest dimension one step in pixels
newDim = startDim - step;
// decrease shortest dimension proportionally, so as to keep aspect ratio
smallerDim = parseInt(ratio*newDim);
// assign calculated vars to their corresponding canvas dimension, width or height
if(image.width > image.height) {
[canvas.width, canvas.height] = [newDim, smallerDim];
} else {
[canvas.width, canvas.height] = [smallerDim, newDim];
}
// draw image one step smaller
canvasContext.drawImage(canvas, 0, 0, canvas.width, canvas.height);
// cycle var startDim for new loop
startDim = newDim;
}
// do last missing resizing step to finally reach target image size
if(lastStep > 0) {
if(image.width > image.height) {
[canvas.width, canvas.height] = [startDim - lastStep, parseInt(ratio*(startDim - lastStep))];
} else {
[canvas.width, canvas.height] = [parseInt(ratio*(startDim -lastStep)), startDim - lastStep];
}
canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
}
// send blob to caller
canvas.toBlob( blob => {
callback({blob: blob, error: null});
}, "image/png", 1);
};
const resize = async (imgSrc, callback, maxLength) => {
image.src = imgSrc;
image.onload = _ => {
doResize(callback, maxLength);
};
};
return { resize: resize }
})();
用法:
ResizeImage.resize("./path/to/image/or/blob/bytes/to/resize", imageObject => {
if(imageObject.error != null) {
// handle errors here
console.log(imageObject.error);
return;
}
// do whatever you want with the blob, like assinging it to
// an img element, or uploading it to a database
// ...
document.querySelector("#my-image").src = imageObject.blob;
// ...
}, 300);
我通過在畫布上使用比例解決了這個問題,並且在我的情況下圖像質量變得非常好。
所以首先我縮放畫布內的內容:
ctx.scale(2, 2)
然后用 css 擴展 canvas 標簽:
#myCanvas { transform: scale(0.5); }
export const resizeImage = (imageFile, size = 80) => {
let resolver = ()=>{};
let reader = new FileReader();
reader.onload = function (e) {
let img = document.createElement("img");
img.onload = function (event) {
// Dynamically create a canvas element
let canvas = document.createElement("canvas");
canvas.width=size;
canvas.height=size;
// let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
// Actual resizing
ctx.drawImage(img, 0, 0, size, size);
// Show resized image in preview element
let dataurl = canvas.toDataURL(imageFile.type);
resolver(dataurl);
}
img.src = e.target.result;
}
reader.readAsDataURL(imageFile);
return new Promise((resolve, reject) => {
resolver = resolve;
})
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.