This question already has an answer here:
CanvasContext2D drawImage() issue [onload and CORS]
(1 answer)
Closed 4 years ago.
I'm using canvas for the first time, and i´m practicing getting data from image to change its properties. The thing is i'm using this code:
<script type="text/javascript" src="jquery-3.3.1.min.js">
</script>
<script type="text/javascript">
$(document).ready(inicio);
function inicio() {
setTimeout(drawing(),100000);
}
function drawing() {
var canvas = document.getElementById("cnv1");
var context = canvas.getContext("2d");
var img = new Image();
img.src = "tomatoes.jpg"
context.drawImage(img, 10, 10);
var imageData = context.getImageData(0, 0, 500, 500);
var data = imageData.data;
/*for (var i=0; i<data.length; i+= 4) {
data[i] = 255-data[i];
data[i+1] = 255-data[i+1];
data[i+2] = 255-data[i+2];
}*/
/*for (var i=0; i<data.length; i+= 4) {
data[i] = data[i]+100;
data[i+1] = data[i+1]+100;
data[i+2] = data[i+2]+100;
}*/
for (var i=0; i<data.length; i+= 4) {
data[i] = data[i]-100;
data[i+1] = data[i+1]-100;
data[i+2] = data[i+2]-100;
}
context.putImageData(imageData,0,0)
}
</script>
<style type="text/css">
canvas {
border: 1px solid black;
}
</style>
I'm getting the correct results for the For Methods, my problem is most of the time the image doesn't loads at all, it just appears on one page refresh, and the next refresh is gone, it doesn't work properly. Any ideas?.
Next comes my code but it won't work on Stack Overflow. You will have to copy the code and test it in your computer. You can see it working in this Codepen project.
IMPORTANT: you have to use an image from your site. Otherwise you'll get an error like this: Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data. And yes, there are some ways to circumvent this problem. Not always working
An observation about your code: Please don't do this: data[i]-100 since data[i] may be smaller than 100.
function drawing() {
var data;
var canvas = document.getElementById("cnv1");
var cw = (canvas.width = 270);
var ch = (canvas.height = 250);
var context = canvas.getContext("2d");
var img=new Image();
img.src = "image.jpg";
img.onload = function() {
context.drawImage(this, 10, 10);
var imageData = context.getImageData(0, 0, 270, 250);
data = imageData.data;
console.log(imageData.data)
for (var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
context.putImageData(imageData, 0, 0);
}
}
drawing()
<canvas id="cnv1"></canvas>
Related
I'm just trying to convert imagedata to an heightmap, to show in on the canvas. But when i do this, a strange thing appears, for all the images I tested.
Here is my code :
window.onload = function()
{
var canvas = document.getElementById('game');
if(!canvas)
{
alert("Impossible de récupérer le canvas.");
return;
}
var context = canvas.getContext('2d');
if(!context)
{
alert("Impossible de récupérer le contexte du canvas.");
return;
}
var img = new Image();
img.src = "noise.png";
var size = 250000;
var data = new Float32Array(size);
var pxlData = new Array(size);
for ( var i = 0; i < size; i ++ ) {
data[i] = 0
}
for (var i = 0; i < size; i++)
{
pxlData[i] = new Array(4);
pxlData[i][0] = 0;
pxlData[i][1] = 0;
pxlData[i][2] = 0;
}
img.onload = function()
{
context.drawImage(img, 0, 0);
var imgd = context.getImageData(0, 0, 500, 500);
context.clearRect(0, 0, canvas.width, canvas.height);
var pix = imgd.data;
var j=0;
var x=0;
var y=0;
var i=0;
for (var i = 0, n = pix.length; i < n; i += (4)) {
var all = pix[i]+pix[i+1]+pix[i+2];
pxlData[j][0] = pix[i];
pxlData[j][1] = pix[i+1];
pxlData[j][2] = pix[i+2];
pxlData[j][3] = pix[i+3];
data[j++] = all/3;
}
var alpha;
for(y = 0; y < 500; y++)
{
for(x = 0; x < 500; x++)
{
if(data[x * y] <= 100){
context.fillStyle = "blue";
}else if(data[x * y] >= 100){
context.fillStyle = "green";
}
//context.fillStyle = 'rgba('+ data[x * y] +', '+ data[x * y] +', '+ data[x * y] +', 1)';
context.fillRect(x, y, 1, 1);
// context.fillStyle = 'rgba('+ pxlData[x * y][0] +', '+ pxlData[x * y][1] +', '+ pxlData[x * y][2] +', '+ pxlData[x * y][3] +')';
// context.fillRect(x, y, 1, 1);
}
}
};
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css" type="text/css">
<script type="text/javascript" src="game.js"></script>
<title>Génération de terrain</title>
</head>
<body>
<canvas id="game" width="500" height ="500">Votre navigateur ne supporte pas les canvas.</canvas>
</body>
</html>
That's what it's looking like when i run it :
canvas
The error is how you index the pixels in the 32 bit float array.
You have data[x * y] that means the pixel at 0,0 will be at index 0 * 0 = 0and pixel at 0,100 will also be at 0 * 100 = 0 and all other indexes will be wrong. To get the correct pixel address use x + y * width when indexing from an array where one item is a pixel. If indexing into pixel data 'imageData.data' each pixel is 4 items (r,g,b,a) so you would use data[x * 4 + y * canvas.width * 4] or more simply imageData.data[x + y * canvas.width * 4]
Looking at your code you have create some common mistakes that will make you code run very slow compared to what it could do. I have simplified your code. It does the same but without all the overhead. I have added comments, removing your code and suggesting alternative methods of doing the same.
The biggest change is the rendering green and blue loops. You where setting each pixel with context.fillRect(x,y,1,1); this is very very slow. Rather than draw a rectangle for each pixel use the imageData you got and fill the colour after you read the height then just put that data back onto the canvas. I used two typeArray views to set and read the data this also improved the performance.
// convert r,g,b,a to 32 bit colour using correct little or big endian
function create32Pixel(r, g, b, a){ // dont call this inside loops as very slow
var endianConvert = new Uint8ClampedArray(4); // use to convert to correct endian
var endianConvert32 = new Uint32Array(endianConvert.buffer);
endianConvert[0] = r;
endianConvert[1] = g;
endianConvert[2] = b;
endianConvert[3] = a;
return endianConvert32[0];
}
window.onload = function()
{
var canvas = document.getElementById('game');
if(!canvas)
{
alert("Impossible de récupérer le canvas.");
return;
}
var context = canvas.getContext('2d');
if(!context)
{
alert("Impossible de récupérer le contexte du canvas.");
return;
}
var img = new Image();
img.src = "noise.png";
var size = 250000;
// Do you really need floats?? 16 bit unsigned int array can hold 255 * 3 and all javascript
// numbers are converted to 64 bit floats so you will not lose precision from original when manipulating the 16bit values.
// following array is not needed.
//var dataFloat = new Float32Array(size);
// following array is not needed.
//var pxlData = new Array(size); // bad way to create an array
//var pxlData = []; // create empty array and push onto it.
// can use dataFloat.fill()
/*for ( var i = 0; i < size; i ++ ) {
dataFloat[i] = 0
}*/
//dataFloat.fill(0); // but not needed as array is zeroed when created (not from an existing buffer)
// Very inefficient as you are creating a new array for every pixel. Use flat array instead.
/*for (var i = 0; i < size; i++)
{
pxlData[i] = new Array(4);
pxlData[i][0] = 0;
pxlData[i][1] = 0;
pxlData[i][2] = 0;
}*/
// should do
/*var i;
while(i < size * 4){
pxlData[i++] = 0; // array grows as you increase i;
}*/
img.onload = function()
{
context.drawImage(img, 0, 0);
var imgd = context.getImageData(0, 0, canvas.width, canvas.height);
// don't need to clear
// context.clearRect(0, 0, canvas.width, canvas.height);
// make two views one 8bit and the other 32bit. Both point to the same data change one
// changes the other
var pixChannels = imgd.data;
var pixels = new Uint32Array(pixChannels.buffer);
var j,x,y,j;
j = x = y = i = 0;
// Create pixel colours. Need to ensure correct order as some systems
// use little edian and others big endian
// see https://en.wikipedia.org/wiki/Endianness for info.
var green = create32Pixel(0,255,0,255);
var blue = create32Pixel(0,0,255,255);
// use j as 32bit pixel index and i as 8bit index
// read the height and set pixel colour accordingly.
while(j < pixels.length){
var height = pixChannels[i++] + pixChannels[i++] + pixChannels[i++];
if(height <= 300){ // no need to divide by 3 just test for 3 time 100
pixels[j++] = blue;
}else{
pixels[j++] = green;
}
i++; // skip alpha channel
}
context.putImageData(imgd,0,0); // put pixels back to canvas.
};
}
I have set a canvas to display an image with JavaScript, now I'm trying to get the image to convert to a set colour palette. I've searched through other questions and even the MDN and have had trouble finding anything on it.
I came up with this:
var image = "http://upload.wikimedia.org/wikipedia/commons/d/d7/RGB_24bits_palette_sample_image.jpg";
var canvas = document.getElementById("c");
var context = canvas.getContext("2d");
// load image from data url
var imageObj = new Image();
imageObj.src = image;
imageObj.onload = function() {
context.drawImage(this, 0, 0);
};
var imgData = context.getImageData(0,0,canvas.width,canvas.height);
var data = imgData.data;
var palette = [[0,0,0],[118,38,64],[64,51,127],[228,52,254],[14,89,64],[128,128,128],[27,154,254],[191,179,255]];
for(var i = 0; i < data.length; i += 4) {
var red = data[i];
var green = data[i+1];
var blue = data[i+2];
var alpha = data[i+3];
min = 9999999999;
paleteMatch = null;
for (var j = 0; j < palette.length; j++) {
var lsd = (Math.pow(palette[j][0] - red, 2) + green + blue) / 3;
if (lsd < min) {
min = lsd;
paletteMatch = palette[j];
}
}
data[i] = paletteMatch[0];
data[i+1] = paletteMatch[1];
data[i+2] = paletteMatch[2];
}
imgData.data.set(data);
context.putImageData(imgData, 0, 0);
The variable data seems to just hold 0s and nothing else. This is a problem when it enters the for-loop as nothing happens. As far as I can tell, I've done nothing wrong.
How come the image data is telling me every pixel is black and how can I fix this to work?
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);
I've had problems with getting a rain effekt on my canvas. After some searching on google I found this
<script type="text/javascript">
var ctx;
var imgBg;
var imgDrops;
var x = 0;
var y = 0;
var noOfDrops = 50;
var fallingDrops = [];
function setup() {
var canvas = document.getElementById('canvasRegn');
if (canvas.getContext) {
ctx = canvas.getContext('2d');
imgBg = new Image();
imgBg.src = "http://lorempixel.com/600/600/sports/";
setInterval(draw, 36);
for (var i = 0; i < noOfDrops; i++) {
var fallingDr = new Object();
fallingDr["image"] = new Image();
fallingDr.image.src = "http://lorempixel.com/10/10/sports/";
fallingDr["x"] = Math.random() * 600;
fallingDr["y"] = Math.random() * 5;
fallingDr["speed"] = 3 + Math.random() * 5;
fallingDrops.push(fallingDr);
}
}
}
function draw() {
drawBackground();
for (var i=0; i< noOfDrops; i++)
{
ctx.drawImage (fallingDrops[i].image, fallingDrops[i].x, fallingDrops[i].y); //The rain drop
fallingDrops[i].y += fallingDrops[i].speed; //Set the falling speed
if (fallingDrops[i].y > 450) { //Repeat the raindrop when it falls out of view
fallingDrops[i].y = -25 //Account for the image size
fallingDrops[i].x = Math.random() * 600; //Make it appear randomly along the width
}
}
}
function drawBackground(){
ctx.drawImage(imgBg, 0, 0); //Background
}
</script>
The strange thing is that the code works as long as I don't change the image source from the link to my png-files. All I get is copies of my file drawn over and over again til the canvas's is full of lines.
Help please!
It seems that for some reason your background is not rendered.
If you don't want to have any background, you have to clear the canvas before drawing drops at their new positions, or else your canvas will flood :)
Replace: ctx.drawImage(imgBg, 0, 0);
with: clearRect(0, 0, width, height)
See, also, this short demo.