I want to downscale user input images (jpeg) using HTML5 Canvas / javascript before sending them to server. The goal is to limit image resolution to 1000×1000 pixels and possibly make a thumbnail. But the output images have too much grain / looks too sharp. Looking for a way to produce good quality images.
Below is the JS I was using to downscale images (textarea having base64 string):
function ResizePic(ReqWidth, ReqHeight, ReqQuality=0.9) {
var tmpImg12 = document.createElement('img');
tmpImg12.src = document.getElementById('sometextarea').value;
origWidth = tmpImg12.width;
origHeight = tmpImg12.height;
if (origWidth > origHeight) ReqHeight = ReqWidth * (origHeight / origWidth);
if (origHeight > origWidth) ReqWidth = ReqHeight * (origWidth / origHeight);
var tmpCanvas = document.createElement('canvas');
var tmpctx12 = tmpCanvas.getContext('2d');
console.log('Image Smoothing: ', tmpctx12.imageSmoothingEnabled);
console.log('Image Smoothing Quality: ', tmpctx12.imageSmoothingQuality);
tmpCanvas.width = ReqWidth;
tmpCanvas.height = ReqHeight;
tmpctx12.drawImage(tmpImg12, 0, 0, ReqWidth, ReqHeight);
return tmpCanvas.toDataURL('image/jpeg', ReqQuality);
}
Tried hermite_resize JS by @ViliusL as shown in here . Image quality is much better but still grainy. Seek advice to further reduce grain and produce good quality output as I got absolutely no idea about image manipulation.
function resize_this() {
var rdFile = new FileReader();
var canvas = document.getElementById('cc');
var ctx = canvas.getContext('2d');
var img = new Image();
var tnW = 400;
var tnH = 400;
rdFile.readAsDataURL(document.getElementById('file1').files[0]);
rdFile.onload = function(e1) {
var srcData = e1.target.result;
img.src = srcData;
}
img.onload = function() {
var W = img.width;
var H = img.height;
if (W > H) tnH = Math.round(tnW * (H / W));
if (H > W) tnW = Math.round(tnH * (W / H));
canvas.width = W;
canvas.height= H;
ctx.drawImage(img, 0, 0);
var time1 = Date.now();
document.getElementById('our_image').src=resample_single(canvas, tnW, tnH, true);
document.getElementById('timer').innerHTML = Math.round(Date.now() - time1)/1000;
}
}
function hermite_resize() {
var canvas = document.getElementById("cc");
var ctx = canvas.getContext("2d");
var img = new Image();
img.crossOrigin = "Anonymous"; //cors support
img.onload = function(){
var W = img.width;
var H = img.height;
canvas.width = W;
canvas.height = H;
ctx.drawImage(img, 0, 0); //draw image
var time1 = Date.now();
//resize
document.getElementById('hermite_img').src=resample_single(canvas, 439, 222, true);
document.getElementById('timer').innerHTML = Math.round(Date.now() - time1)/1000;
}
img.src = 'https://i.imgur.com/8VsK7gS.png';
}
/**
* Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!
*
* @param {HtmlElement} canvas
* @param {int} width
* @param {int} height
* @param {boolean} resize_canvas if true, canvas will be resized. Optional.
*/
function resample_single(canvas, width, height, resize_canvas,ReqQuality=0.95) {
var width_source = canvas.width;
var height_source = canvas.height;
width = Math.round(width);
height = Math.round(height);
var ratio_w = width_source / width;
var ratio_h = height_source / height;
var ratio_w_half = Math.ceil(ratio_w / 2);
var ratio_h_half = Math.ceil(ratio_h / 2);
var ctx = canvas.getContext("2d");
var img = ctx.getImageData(0, 0, width_source, height_source);
var img2 = ctx.createImageData(width, height);
var data = img.data;
var data2 = img2.data;
for (var j = 0; j < height; j++) {
for (var i = 0; i < width; i++) {
var x2 = (i + j * width) * 4;
var weight = 0;
var weights = 0;
var weights_alpha = 0;
var gx_r = 0;
var gx_g = 0;
var gx_b = 0;
var gx_a = 0;
var center_y = (j + 0.5) * ratio_h;
var yy_start = Math.floor(j * ratio_h);
var yy_stop = Math.ceil((j + 1) * ratio_h);
for (var yy = yy_start; yy < yy_stop; yy++) {
var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
var center_x = (i + 0.5) * ratio_w;
var w0 = dy * dy; //pre-calc part of w
var xx_start = Math.floor(i * ratio_w);
var xx_stop = Math.ceil((i + 1) * ratio_w);
for (var xx = xx_start; xx < xx_stop; xx++) {
var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
var w = Math.sqrt(w0 + dx * dx);
if (w >= 1) {
//pixel too far
continue;
}
//hermite filter
weight = 2 * w * w * w - 3 * w * w + 1;
var pos_x = 4 * (xx + yy * width_source);
//alpha
gx_a += weight * data[pos_x + 3];
weights_alpha += weight;
//colors
if (data[pos_x + 3] < 255)
weight = weight * data[pos_x + 3] / 250;
gx_r += weight * data[pos_x];
gx_g += weight * data[pos_x + 1];
gx_b += weight * data[pos_x + 2];
weights += weight;
}
}
data2[x2] = gx_r / weights;
data2[x2 + 1] = gx_g / weights;
data2[x2 + 2] = gx_b / weights;
data2[x2 + 3] = gx_a / weights_alpha;
}
}
//clear and resize canvas
if (resize_canvas === true) {
canvas.width = width;
canvas.height = height;
} else {
ctx.clearRect(0, 0, width_source, height_source);
}
//draw
ctx.putImageData(img2, 0, 0);
return canvas.toDataURL('image/jpeg', ReqQuality);
}
.section {
display:inline-block;
position:relative;
width:40%;
padding:12px;
border:1px solid lightgray;
box-sizing:border-box;
vertical-align:top;
}
canvas {
border:1px solid grey;
display:none;
}
#timer{
font-weight:bold;
}
<div class='section'>
<a href="https://i.imgur.com/8VsK7gS.png">Original image</a>
<button onclick='hermite_resize()'>Hermite Resize</button>
<canvas id="cc"></canvas>
<br />
<img id='hermite_img' src=''>
</div>
<div class='section'>
<input type='file' id='file1' onchange='resize_this()'><br>
<img id='our_image' src=''>
</div>
<p>
Resized in: <span id="timer">-</span> s
</p>