I'm trying to resize some images with canvas but I'm clueless on how to smoothen them. On photoshop, browsers etc.. there are a few algorithms they use (eg bicubic, bilinear) but I don't know if these are built into canvas or not.
Here's my fiddle: 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);
The first one is a normal resized image tag, and the second one is canvas. Notice how the canvas one is not as smooth. How can I achieve 'smoothness'?
You can use down-stepping to achieve better results. Most browsers seem to use linear interpolation rather than bi-cubic when resizing images.
( Update There has been added a quality property to the specs, imageSmoothingQuality
which is currently available in Chrome only.)
Unless one chooses no smoothing or nearest neighbor the browser will always interpolate the image after down-scaling it as this function as a low-pass filter to avoid aliasing.
Bi-linear uses 2x2 pixels to do the interpolation while bi-cubic uses 4x4 so by doing it in steps you can get close to bi-cubic result while using bi-linear interpolation as seen in the resulting images.
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>
Depending on how drastic your resize is you can might skip step 2 if the difference is less.
In the demo you can see the new result is now much similar to the image element.
Since Trung Le Nguyen Nhat's fiddle isn't correct at all (it just uses the original image in the last step)
I wrote my own general fiddle with performance comparison:
Basically it's:
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);
}
I created a reusable Angular service to handle high quality resizing of images / canvases for anyone who's interested: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983
The service includes two solutions because they both have their own pros / cons. The lanczos convolution approach is higher quality at the cost of being slower, whereas the step-wise downscaling approach produces reasonably antialiased results and is significantly faster.
Example usage:
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
})
})
While some of those code-snippets are short and working, they aren't trivial to follow and understand.
As i am not a fan of "copy-paste" from stack-overflow, i would like developers to understand the code they are push into they software, hope you'll find the below useful.
DEMO : Resizing images with JS and HTML Canvas Demo fiddler.
You may find 3 different methods to do this resize, that will help you understand how the code is working and why.
https://jsfiddle.net/1b68eLdr/93089/
Full code of both demo, and TypeScript method that you may want to use in your code, can be found in the GitHub project.
https://github.com/eyalc4/ts-image-resizer
This is the final code:
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
}
};
}}
I don't understand why nobody is suggesting createImageBitmap
.
createImageBitmap(
document.getElementById('image'),
{ resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap =>
document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);
works beautifully (assuming you set ids for image and canvas).
I created a library that allows you to downstep any percentage while keeping all the color data.
https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js
That file you can include in the browser. The results will look like photoshop or image magick, preserving all the color data, averaging pixels, rather than taking nearby ones and dropping others. It doesn't use a formula to guess the averages, it takes the exact average.
Based on K3N answer, I rewrite code generally for anyone wants
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);
UPDATE JSFIDDLE DEMO
Here is my ONLINE DEMO
I wrote small js-utility to crop and resize image on front-end. Here is link on GitHub project. Also you can get blob from final image to send it.
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;
Here is my code, which I hope may be useful for someone out there in the SO community:
You can include your target image dimension as a param in your script call. That will be the result value of your image width or height, whichever is bigger. The smaller dimension is resized keeping your image aspect ratio unchanged. You can also hard-code your default target size in the script.
You can easily change the script to suit your specific needs, such as the image type you want (default is "image/png") for an output and decide in how many steps percentwise you want to resize your image for a finer result (see const percentStep in code).
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 }
})();
Usage:
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);
I solved this by using scale for canvas and the image quality in my case becomes really good.
So first I scale the content inside of canvas:
ctx.scale(2, 2)
And then scale out the canvas tag with css:
#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;
})
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.