I am executing the following script in order to convert an image into grayscale using Javascript.
<body>
<canvas id="myCanvas" width="578" height="400"></canvas>
<script>
function drawImage(imageObj) {
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = 69;
var y = 50;
context.drawImage(imageObj, x, y);
var imageData = context.getImageData(x, y, imageObj.width, imageObj.height);
var data = imageData.data;
for(var i = 0; i < data.length; i += 4) {
var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2];
// red
data[i] = brightness;
// green
data[i + 1] = brightness;
// blue
data[i + 2] = brightness;
}
// overwrite original image
context.putImageData(imageData, x, y);
}
var imageObj = new Image();
imageObj.onload = function() {
drawImage(this);
};
imageObj.src = 'text.png';
</script>
Whenever i change the imageObj.src, my browser displays the indicated image in grayscale. However, what I want is to only display the converted image when i click on some sort of button. Given that my image was uploaded at the beginning of the script.
I tried
<button onclick="drawimage(uploadedFile)">Here</button>
It ovbiously didn't work.
I also want to use the new converted image in the rest of my code (store it in some sort of variable) and still couldnt figure out a way to do it.
Thank you for helping me out !
Try this,
Here is a live version https://jsbin.com/relokugumo/1/edit?html,js,output
Html To
<canvas id="myCanvas" width="578" height="400"></canvas>
<button onclick="setImage('<?php echo $uploadefile ?>')">Here</button>
And JS To,
function setImage(imagePath){
var imageObj = new Image();
imageObj.src = imagePath;
drawImage(imageObj);
}
function drawImage(imageObj) {
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = 69;
var y = 50;
context.drawImage(imageObj, x, y);
var imageData = context.getImageData(x, y, imageObj.width, imageObj.height);
var data = imageData.data;
for(var i = 0; i < data.length; i += 4) {
var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2];
// red
data[i] = brightness;
// green
data[i + 1] = brightness;
// blue
data[i + 2] = brightness;
}
// overwrite original image
context.putImageData(imageData, x, y);
}
the onload() doesn't fire because the image is never loaded. you should do an ajax request to load the image and use your function in callback
This is what I use. Hope this helps.
<html>
<head>
<title>Canvas</title>
</head>
<body>
<input id="upload_file" type="file">
<button id="convert_button" onclick="convert()">Convert</button>
<a onclick="downloadimage(this)">Download image</a>
<script>
var upload_file_input = document.getElementById("upload_file");
upload_file_input.addEventListener("change",upload);
var bodyelem = document.getElementsByTagName("body")[0];
var canvas = document.createElement("canvas");
canvas.setAttribute("id","main_canvas");
var context = canvas.getContext("2d");
function upload(e)
{
var reader = new FileReader();
reader.onload = function(event){
var img = new Image();
img.onload = function(){
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img,0,0);
}
img.src = event.target.result;
}
reader.readAsDataURL(e.target.files[0]);
var canvaselem = document.getElementById("main_canvas");
canvaselem && bodyelem.removeChild(canvaselem);
bodyelem.appendChild(canvas);
}
function convert()
{
var imagedata = context.getImageData(0,0,canvas.width,canvas.height);
var data = imagedata.data;
for(var i = 0,n=data.length;i<n;i+=4)
{
var average = data[i]*0.3 +data[i+1]*0.59 + data[i+2]*0.11;
data[i] = data [i+1] = data[i+2] = average;
}
context.putImageData(imagedata,0,0,0,0,imagedata.width,imagedata.height);
}
function downloadimage(elem)
{
var image = canvas.toDataURL();
elem.href = image;
var imagename = prompt("Name : ","");
elem.download = imagename ? imagename : "image" + ".png";
}
</script>
</body>
</html>
Related
I've been creating a Flappy Bird game with JavaScript and for some reason, it's stopped displaying in the browser. I've checked the canvas displays by changing the background colour, but the actual JavaScript doesn't. I'm new to JavaScript, so I'm not sure where I'm going wrong. Thank you in advance.
Here is the html file:
<!DOCTYPE html>
<html>
<head>
<title>Flappy Bird using JS</title>
</head>
<body>
<canvas id="canvas" width="288" height="512"></canvas>
<script src="flappyBird.js"></script>
</body>
</html>
And here is the JS:
var cvs = document.getElementById("canvas");
var ctx = cvs.getContext("2d");
// load images
var bird = new Image();
var bg = new Image();
var fg = new Image();
var pipeNorth = new Image();
var pipeSouth = new Image();
bird.src = "images/bird.png";
bg.src = "images/bg.png";
fg.src = "images/fg.png";
pipeNorth.src = "images/pipeNorth.png";
pipeSouth.src = "images/pipeSouth.png";
// some variables
var gap = 85;
var constant = pipeNorth.height+gap;
var bX = 10;
var bY = 150;
var gravity = 1.5;
// on key down
document.addEventListener("keydown",moveUp);
function moveUp(){
bY -= 25;
}
// pipe coordinates
var pipe = [];
pipe[0] = {
x : cvs.width;
y : 0;
}
// draw images
function draw(){
ctx.drawImage(bg,0,0);
for(var i = 0; i < pipe.length; i++){
ctx.drawImage(pipeNorth,pipe[i].x,pipe[i].y);
ctx.drawImage(pipeSouth,pipe[i].x,pipe[i].y+constant);
pipe[i].x--;
if(pipe[i].x == 125){
pipe.push({
x : cvs.width,
y : Math.floor(Math.random()*pipeNorth.height) - pipeNorth.height
});
}
// detect collision
if(bX + bird.width >= pipe[i].x && bX <= pipe[i].x + pipeNorth.width &&
(bY <= pipe[i].y + pipeNorth.height || bY+bird.height >= pipe[i].y+constant)
|| bY + bird.height >= cvs.height - fg.height){
location.reload();
}
if(pipe[i].x == 5){
score++;
}
}
ctx.drawImage(fg,0,cvs.height - fg.height);
ctx.drawImage(bird,bX,bY);
bY += gravity;
ctx.fillStyle = "#000";
ctx.font = "20px Verdana";
ctx.fillText("Score: "+score,10,cvs.height-20);
requestAnimationFrame(draw);
}
draw();
Im using clusterfck.js to analyze and return colors. I wan't to grap images from search-results, but it seems to be a problem with the way clusterfck does the data reading. Here's the code from clusterfck:
$(".kmeans-button").click(function() {
if (!colors) {
colors = getImageColors();
}
var k = $(this).attr("data-k"); // $.data() returned undefined
$("#clusters").empty().append("<div>calculating distances...</div>");
kmeans.clusterColors(colors, k); // Threaded analyzing (worker)
})
function getImageColors() {
var img = $("#test-image");
var width = img.width();
var height = img.height();
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var context = canvas.getContext("2d");
context.drawImage(img.get(0), 0, 0);
var data = context.getImageData(0, 0, width, height).data;
var colors = [];
for(var x = 0; x < width-12; x += 3) {
for(var y = 0; y < height-12; y += 3) { // sample image, every 3rd row and column
var offs = x*4 + y*4*width;
var color = [data[offs + 0], data[offs + 1], data[offs + 2]];
colors.push(color);
}
}
return colors;
}
I tried to put in img.crossOrigin = "Anonymous"; with no luck. I guess it needs to be loaded in af function and somehow callback colors. This is the best i could come up with, but it's not working.
$(".kmeans-button").click(function() {
if (!colors) {
getImageColors2(function(colors2) {
colors=colors2.slice(0);
var k = $(this).attr("data-k-e"); // $.data() returned undefined
$("#clusters").empty().append("<div>calculating colors...</div>");
kmeans.clusterColors(colors, k);
})
}
})
function getImageColors2(callback) {
var img2= document.getElementById("test-image");
var img = new Image();
img.onload = function () {
var width = img.width();
var height = img.height();
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var context = canvas.getContext("2d");
context.drawImage(img.get(0), 0, 0);
var data = context.getImageData(0, 0, width, height).data;
var colors = [];
for(var x = 0; x < width-12; x += 3) {
for(var y = 0; y < height-12; y += 3) { // sample image, every 3rd row and column
var offs = x*4 + y*4*width;
var color = [data[offs + 0], data[offs + 1], data[offs + 2]];
colors.push(color);
}
}
callback(colors);
}
img.src=img2.src;
}
I assume that it can't access the data without loading into a image when on a external server. What am i missing her ? Maybe i got it all wrong ?
Thanks for your comments! I got it working now with the callback function :-)
function getpalette(colors) {
$("#clusters").empty().append("<div>calculating colors...</div>");
kmeans_paint.clusterColors(colors, k);
}
$(".kmeans-error-button").click(function() {
k = $(this).attr("data-k-e");
if (!colors) {
getImageColors(getpalette);
}
})
function getImageColors(callback) {
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = function () {
var width = img.width;
var height = img.height;
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var context = canvas.getContext("2d");
context.drawImage(img, 0, 0);
var data = context.getImageData(0, 0, width, height).data;
var colors = [];
for(var x = 0; x < width-12; x += 3) {
for(var y = 0; y < height-12; y += 3) { // sample image, every 3rd row and column
var offs = x*4 + y*4*width;
var color = [data[offs + 0], data[offs + 1], data[offs + 2]];
colors.push(color);
}
}
callback(colors);
}
img.src=document.getElementById("test-image").src;
}
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
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.
Currently, I'm using canvases to dynamically generate grayscale images with javascript.
The grayscale code is as follows:
// Grayscale w canvas method
function grayscale(src)
{
var canvasUrl = false;
try
{
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var imgObj = new Image();
imgObj.src = src;
canvas.width = imgObj.width;
canvas.height = imgObj.height;
ctx.drawImage(imgObj, 0, 0);
var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
for(var y = 0; y < imgPixels.height; y++){
for(var x = 0; x < imgPixels.width; x++){
var i = (y * 4) * imgPixels.width + x * 4;
var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
imgPixels.data[i] = avg;
imgPixels.data[i + 1] = avg;
imgPixels.data[i + 2] = avg;
}
}
ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
canvasUrl = canvas.toDataURL();
}
catch(err)
{
canvasUrl = false;
}
return canvasUrl;
}
In my code, my images are placed in a div. The div has the setting "display:none" until the javascript executes and finishes generating grayscale representations of all the images, at which point it sets the div to have "display: block".
This works most of the time but not all of the time. Occasionally, the div will not show up, because an error occurs on the following line:
var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
Based on related questions here on SO, the general solution seems to be to use $(window).load(func); to run the grayscale generation code.
However, that is what I am doing:
<script type="text/javascript">
var animateTime = 250;
// On window load. This waits until images have loaded which is essential
$(window).load(function(){
// Fade in images so there isn't a color "pop" document load and then on window load
$(".item img").animate({opacity:1},animateTime);
// clone image
$('.item img').each(function(){
var el = $(this);
el.css({"position":"absolute"}).wrap("<div class='img_wrapper' style='display: inline-block'>").clone().addClass('img_grayscale').css({"position":"absolute","z-index":"998","opacity":"0"}).insertBefore(el).queue(function(){
var el = $(this);
el.parent().css({"width":el.css("width"),"height":el.css("height")});
el.dequeue();
});
this.src = grayscale(this.src);
if(!this.src)
alert('An error occurred.'); // handle the occasional DOM error...
});
// Fade image
$('.item img').mouseover(function(){
$(this).parent().find('img:first').stop().animate({opacity:1}, animateTime);
})
$('.img_grayscale').mouseout(function(){
$(this).stop().animate({opacity:0}, animateTime);
});
$('.item').mouseover(function() {
$(this).children('h3').css('display', 'block');
$(this).children('h3').stop().animate({opacity:1}, animateTime);
});
$('.item').mouseout(function() {
$(this).children('h3').css('display', 'none');
$(this).children('h3').stop();
$(this).children('h3').css('opacity', '0');
});
$("#loading").css('display', 'none'); // hide the loading GIF
$(".outer_content_container").css('display', 'block');
$(".outer_content_container").animate({opacity:1}, animateTime*4);
});
</script>
Which leads me to think: could the fact that I am setting "display: none;" on the parent div containing the images be causing the browser to not load the images at all and proceed to call window.onload?
Because you have a race condition, the width and height of the image is not set until the image is loaded. Use the onload event to know when you can read the dimensions.
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var imgObj = new Image();
imgObj.onload = function() {
//do the processing here
};
imgObj.src = src;
canvas.height = imgObj.height;gagein
What's gagin?
imgObj.src = src;
Before that line use the image load method and wrap the rest of your function in it.
imgObj.addEventListener("load", function() {
canvas.width = imgObj.width;
canvas.height = imgObj.height;gagein
ctx.drawImage(imgObj, 0, 0);
var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
for(var y = 0; y < imgPixels.height; y++){
for(var x = 0; x < imgPixels.width; x++){
var i = (y * 4) * imgPixels.width + x * 4;
var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
imgPixels.data[i] = avg;
imgPixels.data[i + 1] = avg;
imgPixels.data[i + 2] = avg;
}
}
ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
canvasUrl = canvas.toDataURL();
}, false);