Darken Image Slider/Button -- Javascript - javascript

I have a webpage in which users upload an image of some hand written work. Sometimes it's scanned pencil which can be very difficult to read.
Is it possible to possible to have a slider/button that I could use to darken or maybe even sharpen a particular image? I would need a slider/button per image as the page I view contains several user uploaded images.
Thanks.

Yes, there are two ways, one is css filters (see posit labs answer), the other one is with canvas, here is a nice tutorial for that, and here is my demo.
For the demo, you would have to use an image in your own domain (otherwise the canvas becomes tainted and you can't access the pixels), that's why you see the Data URI src in the image, is the only way to make the image origin from the fiddle.
HTML
<img id="myImage" src="mydomain/img.png">
<button class="filter-btn" data-filter="darken" data-img="#myImage">Darken</button>
<button class="filter-btn" data-filter="sharpen" data-img="#myImage">Sharpen</button>
If you copy and paste the JavaScript, the only thing you have to do is use this markup for it to work, the image can be configured however you want, the buttons are the important part.
Each button has a filter-btn class, to indicate that it's intended to apply a filter, then, you specify the filter via the data-filter attribute (in this case it can be sharpen or darken), and finally you link the button to the image via the data-img attribute, where you can specify any css selector to get to the image.
JavaScript
Remember, you don't have to touch any of these if you follow the HTML markup, but if you have any questions about the code, shoot!
ImageFilter = {}
ImageFilter.init = function () {
var buttons = document.querySelectorAll(".filter-btn");
for (var i = 0; i < buttons.length; i++) {
var btn = buttons[i];
var filter = btn.dataset.filter;
var img = btn.dataset.img;
img.crossOrigin = "Anonymous";
(function (filter, img) {
btn.addEventListener("click", function () {
ImageFilter.doFilter(filter, img);
});
})(filter, img);
}
}
window.addEventListener("load", ImageFilter.init);
ImageFilter.getImage = function (selector) {
return document.querySelector(selector);
}
ImageFilter.createData = function (canvas, w, h) {
var context = canvas.getContext("2d");
return context.createImageData(w, h);
}
ImageFilter.doFilter = function (type, image) {
var image = ImageFilter.getImage(image);
switch (type) {
case "darken":
var adjustment = -5;
var canvas = ImageFilter.newCanvas(image);
var data = ImageFilter.getData(canvas);
var actualData = data.data;
for (var i = 0; i < actualData.length; i++) {
actualData[i] += adjustment;
actualData[i + 1] += adjustment;
actualData[i + 2] += adjustment;
}
ImageFilter.putData(data, canvas);
var newImg = image.cloneNode(true);
newImg.src = ImageFilter.getSource(canvas);
newImg.id = image.id;
replaceNode(image, newImg);
break;
case "sharpen":
var weights = [0, -1, 0, -1, 5, -1,
0, -1, 0];
var canvas = ImageFilter.newCanvas(image);
var data = ImageFilter.getData(canvas);
var side = Math.round(Math.sqrt(weights.length));
var halfSide = Math.floor(side / 2);
var src = data.data;
var sw = data.width;
var sh = data.height;
var w = sw;
var h = sh;
var output = ImageFilter.createData(canvas, w, h);
var dst = output.data;
var alphaFac = 1;
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var sy = y;
var sx = x;
var dstOff = (y * w + x) * 4;
var r = 0,
g = 0,
b = 0,
a = 0;
for (var cy = 0; cy < side; cy++) {
for (var cx = 0; cx < side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
var srcOff = (scy * sw + scx) * 4;
var wt = weights[cy * side + cx];
r += src[srcOff] * wt;
g += src[srcOff + 1] * wt;
b += src[srcOff + 2] * wt;
a += src[srcOff + 3] * wt;
}
}
}
dst[dstOff] = r;
dst[dstOff + 1] = g;
dst[dstOff + 2] = b;
dst[dstOff + 3] = a + alphaFac * (255 - a);
}
}
ImageFilter.putData(output, canvas);
var newImg = image.cloneNode(true);
newImg.src = ImageFilter.getSource(canvas);
replaceNode(image, newImg);
break;
}
}
ImageFilter.newCanvas = function (image) {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext("2d");
context.drawImage(image, 0, 0, image.width, image.height);
return canvas;
}
ImageFilter.getData = function (canvas) {
var context = canvas.getContext("2d");
return context.getImageData(0, 0, canvas.width, canvas.height);
}
ImageFilter.putData = function (data, canvas) {
var context = canvas.getContext("2d");
context.putImageData(data, 0, 0);
}
ImageFilter.getSource = function (canvas) {
return canvas.toDataURL();
}
function replaceNode(node1, node2) {
var parent = node1.parentNode;
var next = node1.nextSibling;
if (next) parent.insertBefore(node2, next);
else parent.appendChild(node2);
parent.removeChild(node1);
}
That's it, see the demo, hope it helps!
Updates
Firefox fix: creating a new image and replacing the old one each time seems to fix the firefox bug where it doesn't update the image's src. (29/01/15 2:07a.m)

Short answer: yes.
The easiest way to do this would be with CSS Filters, but they aren't supported on old browsers (support table). The example below applies a 200% contrast filter.
filter: contrast(2);
Another option would be to use HTML Canvas to draw the images and manually manipulate the pixels. It's not very fast, and it's much more complicated than CSS Filters. I won't go into depth, but here is an article about filtering images with canvas.
In my opinion, the users should be responsible for uploading quality images. It seems silly to correct their mistake by adding extra controls to your site.

Related

How to divide image in tiles?

I have to achieve the following task:
divides the image into tiles, computes the average color of each tile,
fetches a tile from the server for that color, and composites the
results into a photomosaic of the original image.
What would be the best strategy? the first solution coming to my mind is using canvas.
A simple way to get pixel data and finding the means of tiles. The code will need more checks for images that do not have dimensions that can be divided by the number of tiles.
var image = new Image();
image.src = ??? // the URL if the image is not from your domain you will have to move it to your server first
// wait for image to load
image.onload = function(){
// create a canvas
var canvas = document.createElement("canvas");
//set its size to match the image
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext("2d"); // get the 2d interface
// draw the image on the canvas
ctx.drawImage(this,0,0);
// get the tile size
var tileSizeX = Math.floor(this.width / 10);
var tileSizeY = Math.floor(this.height / 10);
var x,y;
// array to hold tile colours
var tileColours = [];
// for each tile
for(y = 0; y < this.height; y += tileSizeY){
for(x = 0; x < this.width; x += tileSizeX){
// get the pixel data
var imgData = ctx.getImageData(x,y,tileSizeX,tileSizeY);
var r,g,b,ind;
var i = tileSizeY * tileSizeX; // get pixel count
ind = r = g = b = 0;
// for each pixel (rgba 8 bits each)
while(i > 0){
// sum the channels
r += imgData.data[ind++];
g += imgData.data[ind++];
b += imgData.data[ind++];
ind ++;
i --;
}
i = ind /4; // get the count again
// calculate channel means
r /= i;
g /= i;
b /= i;
//store the tile coords and colour
tileColours[tileColours.length] = {
rgb : [r,g,b],
x : x,
y : y,
}
}
// all done now fetch the images for the found tiles.
}
I created a solution for this (I am not getting the tile images from back end)
// first function call to create photomosaic
function photomosaic(image) {
// Dimensions of each tile
var tileWidth = TILE_WIDTH;
var tileHeight = TILE_HEIGHT;
//creating the canvas for photomosaic
var canvas = document.createElement('canvas');
var context = canvas.getContext("2d");
canvas.height = image.height;
canvas.width = image.width;
var imageData = context.getImageData(0, 0, image.width, image.height);
var pixels = imageData.data;
// Number of mosaic tiles
var numTileRows = image.width / tileWidth;
var numTileCols = image.height / tileHeight;
//canvas copy of image
var imageCanvas = document.createElement('canvas');
var imageCanvasContext = canvas.getContext('2d');
imageCanvas.height = image.height;
imageCanvas.width = image.width;
imageCanvasContext.drawImage(image, 0, 0);
//function for finding the average color
function averageColor(row, column) {
var blockSize = 1, // we can set how many pixels to skip
data, width, height,
i = -4,
length,
rgb = {
r: 0,
g: 0,
b: 0
},
count = 0;
try {
data = imageCanvasContext.getImageData(column * TILE_WIDTH, row * TILE_HEIGHT, TILE_HEIGHT, TILE_WIDTH);
} catch (e) {
alert('Not happening this time!');
return rgb;
}
length = data.data.length;
while ((i += blockSize * 4) < length) {
++count;
rgb.r += data.data[i];
rgb.g += data.data[i + 1];
rgb.b += data.data[i + 2];
}
// ~~ used to floor values
rgb.r = ~~(rgb.r / count);
rgb.g = ~~(rgb.g / count);
rgb.b = ~~(rgb.b / count);
return rgb;
}
// Loop through each tile
for (var r = 0; r < numTileRows; r++) {
for (var c = 0; c < numTileCols; c++) {
// Set the pixel values for each tile
var rgb = averageColor(r, c)
var red = rgb.r;
var green = rgb.g;
var blue = rgb.b;
// Loop through each tile pixel
for (var tr = 0; tr < tileHeight; tr++) {
for (var tc = 0; tc < tileWidth; tc++) {
// Calculate the true position of the tile pixel
var trueRow = (r * tileHeight) + tr;
var trueCol = (c * tileWidth) + tc;
// Calculate the position of the current pixel in the array
var pos = (trueRow * (imageData.width * 4)) + (trueCol * 4);
// Assign the colour to each pixel
pixels[pos + 0] = red;
pixels[pos + 1] = green;
pixels[pos + 2] = blue;
pixels[pos + 3] = 255;
};
};
};
};
// Draw image data to the canvas
context.putImageData(imageData, 0, 0);
return canvas;
}
function create() {
var image = document.getElementById('image');
var canvas = photomosaic(image);
document.getElementById("output").appendChild(canvas);
};
DEMO:https://jsfiddle.net/gurinderiitr/sx735L5n/
Try using the JIMP javascript library to read the pixel color and use invert, normalize or similar property for modifying the image.
Have a look on the jimp library
https://github.com/oliver-moran/jimp

emulate div behavior in canvas

I have a dom editor which a user can insert textbox and images. One of my requirements involve saving a snapshot of what is in the editor into an image. I did some research and there are some solutions, but they don't seem 100% foolproof. I've tried implementing a solution myself, clobbering code here and there:
function measureText(text, size, font) {
var lDiv = document.createElement('lDiv');
document.body.appendChild(lDiv);
lDiv.style.fontSize = size;
lDiv.style.fontFamily = font;
lDiv.style.position = "absolute";
lDiv.style.left = -1000;
lDiv.style.top = -1000;
lDiv.innerHTML = text;
var metrics = font.measureText(text, size.slice(0, size.length - 2));
var lResult = {
width: lDiv.clientWidth,
height: metrics.height + lDiv.clientHeight
};
document.body.removeChild(lDiv);
lDiv = null;
return lResult;
}
function wrapText(context, item) {
var words = item.text.split(' ');
var line = '';
var x = parseInt(item.x);
var y = parseInt(item.y);
var width = parseInt(item.width.slice(0, item.width.length - 2));
var height = parseInt(item.height.slice(0, item.height.length - 2));
var fontsize = parseInt(item.size.slice(0, item.size.length - 2));
var font = new Font();
font.onload = function () {
context.save();
context.beginPath();
context.rect(x, y, width, height);
context.clip();
context.font = item.size + " " + item.font;
context.textBaseline = "top";
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = measureText(testLine, item.size, font);
var testWidth = metrics.width;
if (testWidth > width && n > 0) {
console.log("Drawing '" + line + "' to " + x + " " + y);
context.fillText(line, x, y);
line = words[n] + ' ';
y += metrics.height
}
else {
line = testLine;
}
}
context.fillText(line, x, y);
context.restore();
}
font.fontFamily = item.font;
font.src = font.fontFamily;
}
this.toImage = function () {
console.log("testing");
var canvas = document.getElementById("testcanvas");
canvas.width = 400;
canvas.height = 400;
var ctx = canvas.getContext("2d");
var imageObj = new Image();
var thisService = this;
imageObj.onload = function () {
ctx.drawImage(imageObj, 0, 0, 400, 400);
for (var i = 0; i < thisService.canvasItems.length; i++) {
var component = thisService.canvasItems[i];
if (component.type == "textbox") {
var x = component.x.slice(0, component.x.length - 2);
var y = component.y.slice(0, component.y.length - 2);
var w = component.width.slice(0, component.width.length - 2);
var h = component.height.slice(0, component.height.length - 2);
wrapText(ctx, component);
}
}
};
imageObj.src = this.base.front_image;
}
Somehow I believe I almost made it, however from the ,
There seems to be some positioning/font placement issues, just a few pixels lower. The top 1 a div with no padding, (its model can be seem on the panel on the left), while the bottom one is the canvas.
I wish to have a 1 to 1 accurate mapping here, can anyone enlighten what might be the problem?
As far as I know its not possible to draw HTML into a canvas with 100% accuracy due to the obvious "security" reasons.
You can still get pretty close using the rasterizeHTML.js library.
It uses a SVG image containing the content you want to render. To draw HTML content, you'd use a element containing the HTML, then draw that SVG image into your canvas.

Automatically contrasting text color based on background image color

I'm looking for a way to change the color of text to either #000 or #fff depending on the main color of a background image within a div called "banner". The background images are chosen at random on each page so I need to be able to do this automatically. I came across JavaScript color contraster but I'm struggling to understand how to use it properly. I notice the link I've posted gives a solution in javascript and I've also read about a possible solution in jquery.
I'm clueless with functions so if anyone could explain clearly how I could achieve this, where I place functions and how I "call them" (if that's the right term!) to use it I'd be really grateful.
Thanks for any help.
You could do something like this. (using Colours.js and this answer)
Note, this will only work with images on the same domain and in browsers that support HTML5 canvas.
'use strict';
var getAverageRGB = function(imgEl) {
var rgb = {
b: 0,
g: 0,
r: 0
};
var canvas = document.createElement('canvas');
var context = canvas.getContext && canvas.getContext('2d');
if (Boolean(context) === false) {
return rgb;
}
var height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
var width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
canvas.height = height;
canvas.width = width;
context.drawImage(imgEl, 0, 0);
var data;
try {
data = context.getImageData(0, 0, width, height).data;
} catch (e) {
console.error('security error, img on diff domain');
return rgb;
}
var count = 0;
var length = data.length;
// only visit every 5 pixels
var blockSize = 5;
var step = (blockSize * 4) - 4;
for (var i = step; i < length; i += step) {
count += 1;
rgb.r += data[i];
rgb.g += data[i + 1];
rgb.b += data[i + 2];
}
rgb.r = Math.floor(rgb.r / count);
rgb.g = Math.floor(rgb.g / count);
rgb.b = Math.floor(rgb.b / count);
return rgb;
};
var rgb = getAverageRGB(document.getElementById('image'));
var avgComplement = Colors.complement(rgb.r, rgb.b, rgb.g);
var avgComplementHex = Colors.rgb2hex.apply(null, avgComplement.a);
var compliment = parseInt(avgComplementHex.slice(1), 16);
document.body.style.backgroundColor = 'rgb(' + [
rgb.r,
rgb.g,
rgb.b
].join(',') + ')';
var maxColors = 0xFFFFFF;
var midPoint = Math.floor(maxColors / 2);
document.getElementById('text').style.color = compliment > midPoint ? '#000' : '#fff';
<script src="https://cdnjs.cloudflare.com/ajax/libs/Colors.js/1.2.3/colors.min.js"></script>
<div id="text">Setting the BODY's background to the average color in the following image and this text to a complimentary colour of black or white:</div>
<img id="image" src="data:image/jpeg;base64,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" />

Javascript Image Transform? [scale and rotate]

I'm looking to build an image transform tool with Javascript. Something that utilizes handles around the image similar to Photoshop and allows the user to scale and rotate. I'm looking to make this work in IE 6 and up and Firefox 3+ and Safari 3+.
Does anyone know of a library or tool that could help with this? I've seen a lot of tools that utilize the Canvas element but that leaves out IT. I've also seen the Raphael library which might work. Any other options out there?
Have a look at this rotorzoom.
It rotates it zooms it's fast and i can do with a few more hits.
http://codepen.io/hex2bin/pen/tHwhF
var requestId = 0;
var animationStartTime = 0;
var img = new Image();
initimg(img);
dst = document.getElementById("dst").getContext("2d");
dst.drawImage(img, 0, 0, 256, 256);
// read the width and height of the canvas
i = 0;
var imageDataDst = dst.getImageData(0, 0, 1024, 512);
var bufDst = new ArrayBuffer(imageDataDst.data.length);
var buf8Dst = new Uint8ClampedArray(bufDst);
var data32Dst = new Uint32Array(bufDst);
var data32Src = new Uint32Array(256*256);
var scan1=0;
var scan4=0
// fill the source array with the image
for (var y = 0; y < 256; ++y) {
scan4=y*1024*4;
for (var x = 0; x < 256; ++x) {
data32Src[scan1++] =
(255 << 24) + // alpha
(imageDataDst.data[scan4+2] << 16) + // blue
(imageDataDst.data[scan4+1] << 8) + // green
imageDataDst.data[scan4]; // red
scan4=scan4+4;
}
}
animationStartTime = window.performance.now();
requestId = window.requestAnimationFrame(animate);
var j=0;
function animate(time) {
var height=512;
var width=1024;
j=j+1;
var timestamp = j / 100;
var pos;
var startY = 128;
var startX = 128;
var i=0;
var scaledHeight = 512 + Math.sin(timestamp*1.4) * 500;
var scaledWidth = 512 + Math.sin(timestamp*1.4) * 500
var angleRadians = timestamp;
var deltaX1 = Math.cos(angleRadians) * scaledHeight / 256;
var deltaY1 = Math.sin(angleRadians) * scaledHeight / 256;
var deltaY1x256=deltaY1*256;
var deltaX2 =Math.sin(angleRadians ) * scaledWidth / 256;
var deltaY2 =- Math.cos(angleRadians ) * scaledWidth / 256;
var h = height;
while (h--) {
var x =262144+ startX+deltaX1*-512+deltaX2*-256;
var y =262144+ startY+deltaY1*-512+deltaY2*-256;
var y256=y*256;
var w = width;
while (w--) {
//Optimised inner loop. Can it be done better?
pos =(y256>>0&0xff00)+(x>>0&0xff);
data32Dst[i++] =data32Src[pos];
x += deltaX1;
y256 += deltaY1x256;
//end of inner loop
}
startX += deltaX2
startY += deltaY2;
}
imageDataDst.data.set(buf8Dst);
dst.putImageData(imageDataDst, 0, 0);
requestId = window.requestAnimationFrame(animate);
}
function initimg(image1)
{
image1.src = 'data:image/jpeg;base64,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';
}
<body>
<canvas id="dst" width="1024", height="512">
Random Canvas
</canvas>
</body>
Check out Raphael library on Github: https://github.com/DmitryBaranovskiy/raphael

Resizing an image in an HTML5 canvas

I'm trying to create a thumbnail image on the client side using javascript and a canvas element, but when I shrink the image down, it looks terrible. It looks as if it was downsized in photoshop with the resampling set to 'Nearest Neighbor' instead of Bicubic. I know its possible to get this to look right, because this site can do it just fine using a canvas as well. I've tried using the same code they do as shown in the "[Source]" link, but it still looks terrible. Is there something I'm missing, some setting that needs to be set or something?
EDIT:
I'm trying to resize a jpg. I have tried resizing the same jpg on the linked site and in photoshop, and it looks fine when downsized.
Here is the relevant code:
reader.onloadend = function(e)
{
var img = new Image();
var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
img.onload = function()
{
var ratio = 1;
if(img.width > maxWidth)
ratio = maxWidth / img.width;
else if(img.height > maxHeight)
ratio = maxHeight / img.height;
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
};
img.src = reader.result;
}
EDIT2:
Seems I was mistaken, the linked website wasn't doing any better of a job of downsizing the image. I tried the other methods suggested and none of them look any better. This is what the different methods resulted in:
Photoshop:
Canvas:
Image with image-rendering: optimizeQuality set and scaled with width/height:
Image with image-rendering: optimizeQuality set and scaled with -moz-transform:
Canvas resize on pixastic:
I guess this means firefox isn't using bicubic sampling like its supposed to. I'll just have to wait until they actually add it.
EDIT3:
Original Image
So what do you do if all the browsers (actually, Chrome 5 gave me quite good one) won't give you good enough resampling quality? You implement them yourself then! Oh come on, we're entering the new age of Web 3.0, HTML5 compliant browsers, super optimized JIT javascript compilers, multi-core(†) machines, with tons of memory, what are you afraid of? Hey, there's the word java in javascript, so that should guarantee the performance, right? Behold, the thumbnail generating code:
// returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
return function(x) {
if (x > lobes)
return 0;
x *= Math.PI;
if (Math.abs(x) < 1e-16)
return 1;
var xx = x / lobes;
return Math.sin(x) * Math.sin(xx) / x / xx;
};
}
// elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes) {
this.canvas = elem;
elem.width = img.width;
elem.height = img.height;
elem.style.display = "none";
this.ctx = elem.getContext("2d");
this.ctx.drawImage(img, 0, 0);
this.img = img;
this.src = this.ctx.getImageData(0, 0, img.width, img.height);
this.dest = {
width : sx,
height : Math.round(img.height * sx / img.width),
};
this.dest.data = new Array(this.dest.width * this.dest.height * 3);
this.lanczos = lanczosCreate(lobes);
this.ratio = img.width / sx;
this.rcp_ratio = 2 / this.ratio;
this.range2 = Math.ceil(this.ratio * lobes / 2);
this.cacheLanc = {};
this.center = {};
this.icenter = {};
setTimeout(this.process1, 0, this, 0);
}
thumbnailer.prototype.process1 = function(self, u) {
self.center.x = (u + 0.5) * self.ratio;
self.icenter.x = Math.floor(self.center.x);
for (var v = 0; v < self.dest.height; v++) {
self.center.y = (v + 0.5) * self.ratio;
self.icenter.y = Math.floor(self.center.y);
var a, r, g, b;
a = r = g = b = 0;
for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
if (i < 0 || i >= self.src.width)
continue;
var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
if (!self.cacheLanc[f_x])
self.cacheLanc[f_x] = {};
for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
if (j < 0 || j >= self.src.height)
continue;
var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
if (self.cacheLanc[f_x][f_y] == undefined)
self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2)
+ Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
weight = self.cacheLanc[f_x][f_y];
if (weight > 0) {
var idx = (j * self.src.width + i) * 4;
a += weight;
r += weight * self.src.data[idx];
g += weight * self.src.data[idx + 1];
b += weight * self.src.data[idx + 2];
}
}
}
var idx = (v * self.dest.width + u) * 3;
self.dest.data[idx] = r / a;
self.dest.data[idx + 1] = g / a;
self.dest.data[idx + 2] = b / a;
}
if (++u < self.dest.width)
setTimeout(self.process1, 0, self, u);
else
setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self) {
self.canvas.width = self.dest.width;
self.canvas.height = self.dest.height;
self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height);
self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
var idx, idx2;
for (var i = 0; i < self.dest.width; i++) {
for (var j = 0; j < self.dest.height; j++) {
idx = (j * self.dest.width + i) * 3;
idx2 = (j * self.dest.width + i) * 4;
self.src.data[idx2] = self.dest.data[idx];
self.src.data[idx2 + 1] = self.dest.data[idx + 1];
self.src.data[idx2 + 2] = self.dest.data[idx + 2];
}
}
self.ctx.putImageData(self.src, 0, 0);
self.canvas.style.display = "block";
};
...with which you can produce results like these!
so anyway, here is a 'fixed' version of your example:
img.onload = function() {
var canvas = document.createElement("canvas");
new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
// but feel free to raise it up to 8. Your client will appreciate
// that the program makes full use of his machine.
document.body.appendChild(canvas);
};
Now it's time to pit your best browsers out there and see which one will least likely increase your client's blood pressure!
Umm, where's my sarcasm tag?
(since many parts of the code is based on Anrieff Gallery Generator is it also covered under GPL2? I don't know)
† actually due to limitation of javascript, multi-core is not supported.
Fast image resize/resample algorithm using Hermite filter with JavaScript. Support transparency, gives good quality. Preview:
Update: version 2.0 added on GitHub (faster, web workers + transferable objects). Finally i got it working!
Git: https://github.com/viliusle/Hermite-resize
Demo: http://viliusle.github.io/miniPaint/
/**
* 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) {
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);
}
Try pica - that's a highly optimized resizer with selectable algorythms. See demo.
For example, original image from first post is resized in 120ms with Lanczos filter and 3px window or 60ms with Box filter and 0.5px window. For huge 17mb image 5000x3000px resize takes ~1s on desktop and 3s on mobile.
All resize principles were described very well in this thread, and pica does not add rocket science. But it's optimized very well for modern JIT-s, and is ready to use out of box (via npm or bower). Also, it use webworkers when available to avoid interface freezes.
I also plan to add unsharp mask support soon, because it's very useful after downscale.
I know this is an old thread but it might be useful for some people such as myself that months after are hitting this issue for the first time.
Here is some code that resizes the image every time you reload the image. I am aware this is not optimal at all, but I provide it as a proof of concept.
Also, sorry for using jQuery for simple selectors but I just feel too comfortable with the syntax.
$(document).on('ready', createImage);
$(window).on('resize', createImage);
var createImage = function(){
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth || $(window).width();
canvas.height = window.innerHeight || $(window).height();
var ctx = canvas.getContext('2d');
img = new Image();
img.addEventListener('load', function () {
ctx.drawImage(this, 0, 0, w, h);
});
img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
};
html, body{
height: 100%;
width: 100%;
margin: 0;
padding: 0;
background: #000;
}
canvas{
position: absolute;
left: 0;
top: 0;
z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<head>
<meta charset="utf-8" />
<title>Canvas Resize</title>
</head>
<body>
<canvas id="myCanvas"></canvas>
</body>
</html>
My createImage function is called once when the document is loaded and after that it is called every time the window receives a resize event.
I tested it in Chrome 6 and Firefox 3.6, both on the Mac. This "technique" eats processor as it if was ice cream in the summer, but it does the trick.
I've put up some algorithms to do image interpolation on html canvas pixel arrays that might be useful here:
https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2
These can be copy/pasted and can be used inside of web workers to resize images (or any other operation that requires interpolation - I'm using them to defish images at the moment).
I haven't added the lanczos stuff above, so feel free to add that as a comparison if you'd like.
This is a javascript function adapted from #Telanor's code. When passing a image base64 as first argument to the function, it returns the base64 of the resized image. maxWidth and maxHeight are optional.
function thumbnail(base64, maxWidth, maxHeight) {
// Max size for thumbnail
if(typeof(maxWidth) === 'undefined') var maxWidth = 500;
if(typeof(maxHeight) === 'undefined') var maxHeight = 500;
// Create and initialize two canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
// Create original image
var img = new Image();
img.src = base64;
// Determine new ratio based on max size
var ratio = 1;
if(img.width > maxWidth)
ratio = maxWidth / img.width;
else if(img.height > maxHeight)
ratio = maxHeight / img.height;
// Draw original image in second canvas
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
// Copy and resize second canvas to first canvas
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL();
}
I'd highly suggest you check out this link and make sure it is set to true.
Controlling image scaling behavior
Introduced in Gecko 1.9.2 (Firefox 3.6
/ Thunderbird 3.1 / Fennec 1.0)
Gecko 1.9.2 introduced the
mozImageSmoothingEnabled property to
the canvas element; if this Boolean
value is false, images won't be
smoothed when scaled. This property is
true by default. view plainprint?
cx.mozImageSmoothingEnabled = false;
If you're simply trying to resize an image, I'd recommend setting width and height of the image with CSS. Here's a quick example:
.small-image {
width: 100px;
height: 100px;
}
Note that the height and width can also be set using JavaScript. Here's quick code sample:
var img = document.getElement("my-image");
img.style.width = 100 + "px"; // Make sure you add the "px" to the end,
img.style.height = 100 + "px"; // otherwise you'll confuse IE
Also, to ensure that the resized image looks good, add the following css rules to image selector:
-ms-interpolation-mode: bicubic: introduce in IE7
image-rendering: optimizeQuality: introduced in FireFox 3.6
As far as I can tell, all browsers except IE using an bicubic algorithm to resize images by default, so your resized images should look good in Firefox and Chrome.
If setting the css width and height doesn't work, you may want to play with a css transform:
-moz-transform: scale(sx[, sy])
-webkit-transform:scale(sx[, sy])
If for whatever reason you need to use a canvas, please note that there are two ways an image can be resize: by resizing the canvas with css or by drawing the image at a smaller size.
See this question for more details.
i got this image by right clicking the canvas element in firefox and saving as.
var img = new Image();
img.onload = function () {
console.debug(this.width,this.height);
var canvas = document.createElement('canvas'), ctx;
canvas.width = 188;
canvas.height = 150;
document.body.appendChild(canvas);
ctx = canvas.getContext('2d');
ctx.drawImage(img,0,0,188,150);
};
img.src = 'original.jpg';
so anyway, here is a 'fixed' version of your example:
var img = new Image();
// added cause it wasnt defined
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
// adding it to the body
document.body.appendChild(canvasCopy);
var copyContext = canvasCopy.getContext("2d");
img.onload = function()
{
var ratio = 1;
// defining cause it wasnt
var maxWidth = 188,
maxHeight = 150;
if(img.width > maxWidth)
ratio = maxWidth / img.width;
else if(img.height > maxHeight)
ratio = maxHeight / img.height;
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
// the line to change
// ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
// the method signature you are using is for slicing
ctx.drawImage(canvasCopy, 0, 0, canvas.width, canvas.height);
};
// changed for example
img.src = 'original.jpg';
For resizing to image with width less that original, i use:
function resize2(i) {
var cc = document.createElement("canvas");
cc.width = i.width / 2;
cc.height = i.height / 2;
var ctx = cc.getContext("2d");
ctx.drawImage(i, 0, 0, cc.width, cc.height);
return cc;
}
var cc = img;
while (cc.width > 64 * 2) {
cc = resize2(cc);
}
// .. than drawImage(cc, .... )
and it works =).
I have a feeling the module I wrote will produce similar results to photoshop, as it preserves color data by averaging them, not applying an algorithm. It's kind of slow, but to me it is the best, because it preserves all the color data.
https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js
It doesn't take the nearest neighbor and drop other pixels, or sample a group and take a random average. It takes the exact proportion each source pixel should output into the destination pixel. The average pixel color in the source will be the average pixel color in the destination, which these other formulas, I think they will not be.
an example of how to use is at the bottom of
https://github.com/danschumann/limby-resize
UPDATE OCT 2018: These days my example is more academic than anything else. Webgl is pretty much 100%, so you'd be better off resizing with that to produce similar results, but faster. PICA.js does this, I believe. –
The problem with some of this solutions is that they access directly the pixel data and loop through it to perform the downsampling. Depending on the size of the image this can be very resource intensive, and it would be better to use the browser's internal algorithms.
The drawImage() function is using a linear-interpolation, nearest-neighbor resampling method. That works well when you are not resizing down more than half the original size.
If you loop to only resize max one half at a time, the results would be quite good, and much faster than accessing pixel data.
This function downsample to half at a time until reaching the desired size:
function resize_image( src, dst, type, quality ) {
var tmp = new Image(),
canvas, context, cW, cH;
type = type || 'image/jpeg';
quality = quality || 0.92;
cW = src.naturalWidth;
cH = src.naturalHeight;
tmp.src = src.src;
tmp.onload = function() {
canvas = document.createElement( 'canvas' );
cW /= 2;
cH /= 2;
if ( cW < src.width ) cW = src.width;
if ( cH < src.height ) cH = src.height;
canvas.width = cW;
canvas.height = cH;
context = canvas.getContext( '2d' );
context.drawImage( tmp, 0, 0, cW, cH );
dst.src = canvas.toDataURL( type, quality );
if ( cW <= src.width || cH <= src.height )
return;
tmp.src = dst.src;
}
}
// The images sent as parameters can be in the DOM or be image objects
resize_image( $( '#original' )[0], $( '#smaller' )[0] );
Credits to this post
So something interesting that I found a while ago while working with canvas that might be helpful:
To resize the canvas control on its own, you need to use the height="" and width="" attributes (or canvas.width/canvas.height elements). If you use CSS to resize the canvas, it will actually stretch (i.e.: resize) the content of the canvas to fit the full canvas (rather than simply increasing or decreasing the area of the canvas.
It'd be worth a shot to try drawing the image into a canvas control with the height and width attributes set to the size of the image and then using CSS to resize the canvas to the size you're looking for. Perhaps this would use a different resizing algorithm.
It should also be noted that canvas has different effects in different browsers (and even different versions of different browsers). The algorithms and techniques used in the browsers is likely to change over time (especially with Firefox 4 and Chrome 6 coming out so soon, which will place heavy emphasis on canvas rendering performance).
In addition, you may want to give SVG a shot, too, as it likely uses a different algorithm as well.
Best of luck!
Fast and simple Javascript image resizer:
https://github.com/calvintwr/blitz-hermite-resize
const blitz = Blitz.create()
/* Promise */
blitz({
source: DOM Image/DOM Canvas/jQuery/DataURL/File,
width: 400,
height: 600
}).then(output => {
// handle output
})catch(error => {
// handle error
})
/* Await */
let resized = await blizt({...})
/* Old school callback */
const blitz = Blitz.create('callback')
blitz({...}, function(output) {
// run your callback.
})
History
This is really after many rounds of research, reading and trying.
The resizer algorithm uses #ViliusL's Hermite script (Hermite resizer is really the fastest and gives reasonably good output). Extended with features you need.
Forks 1 worker to do the resizing so that it doesn't freeze your browser when resizing, unlike all other JS resizers out there.
I converted #syockit's answer as well as the step-down approach into a reusable Angular service for anyone who's interested: https://gist.github.com/fisch0920/37bac5e741eaec60e983
I included both 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
})
})
Thanks #syockit for an awesome answer. however, I had to reformat a little as follows to make it work. Perhaps due to DOM scanning issues:
$(document).ready(function () {
$('img').on("load", clickA);
function clickA() {
var img = this;
var canvas = document.createElement("canvas");
new thumbnailer(canvas, img, 50, 3);
document.body.appendChild(canvas);
}
function thumbnailer(elem, img, sx, lobes) {
this.canvas = elem;
elem.width = img.width;
elem.height = img.height;
elem.style.display = "none";
this.ctx = elem.getContext("2d");
this.ctx.drawImage(img, 0, 0);
this.img = img;
this.src = this.ctx.getImageData(0, 0, img.width, img.height);
this.dest = {
width: sx,
height: Math.round(img.height * sx / img.width)
};
this.dest.data = new Array(this.dest.width * this.dest.height * 3);
this.lanczos = lanczosCreate(lobes);
this.ratio = img.width / sx;
this.rcp_ratio = 2 / this.ratio;
this.range2 = Math.ceil(this.ratio * lobes / 2);
this.cacheLanc = {};
this.center = {};
this.icenter = {};
setTimeout(process1, 0, this, 0);
}
//returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
return function (x) {
if (x > lobes)
return 0;
x *= Math.PI;
if (Math.abs(x) < 1e-16)
return 1
var xx = x / lobes;
return Math.sin(x) * Math.sin(xx) / x / xx;
}
}
process1 = function (self, u) {
self.center.x = (u + 0.5) * self.ratio;
self.icenter.x = Math.floor(self.center.x);
for (var v = 0; v < self.dest.height; v++) {
self.center.y = (v + 0.5) * self.ratio;
self.icenter.y = Math.floor(self.center.y);
var a, r, g, b;
a = r = g = b = 0;
for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
if (i < 0 || i >= self.src.width)
continue;
var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
if (!self.cacheLanc[f_x])
self.cacheLanc[f_x] = {};
for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
if (j < 0 || j >= self.src.height)
continue;
var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
if (self.cacheLanc[f_x][f_y] == undefined)
self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
weight = self.cacheLanc[f_x][f_y];
if (weight > 0) {
var idx = (j * self.src.width + i) * 4;
a += weight;
r += weight * self.src.data[idx];
g += weight * self.src.data[idx + 1];
b += weight * self.src.data[idx + 2];
}
}
}
var idx = (v * self.dest.width + u) * 3;
self.dest.data[idx] = r / a;
self.dest.data[idx + 1] = g / a;
self.dest.data[idx + 2] = b / a;
}
if (++u < self.dest.width)
setTimeout(process1, 0, self, u);
else
setTimeout(process2, 0, self);
};
process2 = function (self) {
self.canvas.width = self.dest.width;
self.canvas.height = self.dest.height;
self.ctx.drawImage(self.img, 0, 0);
self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
var idx, idx2;
for (var i = 0; i < self.dest.width; i++) {
for (var j = 0; j < self.dest.height; j++) {
idx = (j * self.dest.width + i) * 3;
idx2 = (j * self.dest.width + i) * 4;
self.src.data[idx2] = self.dest.data[idx];
self.src.data[idx2 + 1] = self.dest.data[idx + 1];
self.src.data[idx2 + 2] = self.dest.data[idx + 2];
}
}
self.ctx.putImageData(self.src, 0, 0);
self.canvas.style.display = "block";
}
});
I wanted some well defined functions out of answers here so ended up with these which am hoping would be useful for others also,
function getImageFromLink(link) {
return new Promise(function (resolve) {
var image = new Image();
image.onload = function () { resolve(image); };
image.src = link;
});
}
function resizeImageToBlob(image, width, height, mime) {
return new Promise(function (resolve) {
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
return canvas.toBlob(resolve, mime);
});
}
getImageFromLink(location.href).then(function (image) {
// calculate these based on the original size
var width = image.width / 4;
var height = image.height / 4;
return resizeImageToBlob(image, width, height, 'image/jpeg');
}).then(function (blob) {
// Do something with the result Blob object
document.querySelector('img').src = URL.createObjectURL(blob);
});
Just for the sake of testing this run it on a image opened in a tab.
I just ran a page of side by sides comparisons and unless something has changed recently, I could see no better downsizing (scaling) using canvas vs. simple css. I tested in FF6 Mac OSX 10.7. Still slightly soft vs. the original.
I did however stumble upon something that did make a huge difference and that was using image filters in browsers that support canvas. You can actually manipulate images much like you can in Photoshop with blur, sharpen, saturation, ripple, grayscale, etc.
I then found an awesome jQuery plug-in which makes application of these filters a snap:
http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234
I simply apply the sharpen filter right after resizing the image which should give you the desired effect. I didn't even have to use a canvas element.
Looking for another great simple solution?
var img=document.createElement('img');
img.src=canvas.toDataURL();
$(img).css("background", backgroundColor);
$(img).width(settings.width);
$(img).height(settings.height);
This solution will use the resize algorith of browser! :)

Categories

Resources