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?
Related
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>
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.
};
}
So I made a program that is supposed to make an empty 2d game board using stroke rect or draw img. Here it is (using stroke rect):
window.onload = function() {
//set up the canvas items
var canvas = document.getElementById("paper");
var emptySquare = canvas.getContext("2d");
var player = canvas.getContext("2d");
var background = canvas.getContext("2d");
//An empty game board, with basic stats
var boardArray = [];
var rowNum = 7;
var colNum = 7;
//Makes the board with the empty squares
for (i = 0; i < colNum; i++) {
for (x = 0; x < rowNum; x++) {
boardArray.push([colNum*10, rowNum*10]);
}
}
//This is the png of an empty board part of an array
var emptySquareImg = new Image();
emptySquareImg.src = "border.png";
function displayBoard() {
background.fillStyle = "rgb(0, 0, 0)";
background.fillRect(0, 0, canvas.width, canvas.height);
for (x = 0; x < boardArray.length; x++) {
for (y = 0; y < x.length; y++) {
emptySquare.beginPath();
emptySquare.lineWidth = "4";
emptySquare.strokeStyle = "rgb(200, 34, 22)";
emptySquare.rect(boardArray[x], boardArray[y], 10, 10)
emptySquare.stroke();
}
}
}
displayBoard();
}
It does not display anything except the black background. It also throws no errors, which is sort of weird. Thank you for any help, and I can soon make my little board game!
There are a few issues with your loops and generation of the array of squares. Remember to use the var keyword when setting up for-loops in javascript. Otherwise the variable will not be in the local scope and you probably won't get what you expect. Especially with x in your case since it's used in two loops.
http://jsfiddle.net/mfohao5x/
window.onload = function() {
var canvas = document.getElementById("paper");
var emptySquare = canvas.getContext("2d");
var player = canvas.getContext("2d");
var background = canvas.getContext("2d");
var boardArray = [];
var rowNum = 7;
var colNum = 7;
// probably worth defining the width and height of cells here
var width = 10;
var height = 10;
// remember to include the keyword "var"
for (var i = 0; i < rowNum; i++) {
// add a new row to boardArray
boardArray.push([]);
for (var x = 0; x < colNum; x++) {
// add your values for this square within this row
boardArray[i].push([i*width, x*height]);
}
}
//console.log(boardArray);
var emptySquareImg = new Image();
emptySquareImg.src = "border.png";
function displayBoard() {
background.fillStyle = "rgb(0, 0, 0)";
background.fillRect(0, 0, canvas.width, canvas.height);
for (var x = 0; x < boardArray.length; x++) {
// get the row here and then iterate through it
var row = boardArray[x];
for (var y = 0; y < row.length; y++) {
// now row[y] will give you your square
emptySquare.beginPath();
emptySquare.lineWidth = "4";
emptySquare.strokeStyle = "rgb(200, 34, 22)";
// use row[y][0] and row[y][1] to position the rect
emptySquare.rect(row[y][0], row[y][1], width, height);
emptySquare.stroke();
}
}
}
displayBoard();
}
Here's your code with adjusted loops. boardArray.push([colNum*10, rowNum*10]); is changed to boardArray.push([i*10, x*10]);. boardArray[x], boardArray[y] is changed to arr[0], arr[1].
window.onload = function() {
//set up the canvas items
var canvas = document.getElementById("paper");
var emptySquare = canvas.getContext("2d");
var player = canvas.getContext("2d");
var background = canvas.getContext("2d");
//An empty game board, with basic stats
var boardArray = [];
var rowNum = 7;
var colNum = 7;
//Makes the board with the empty squares
for (i = 0; i < colNum; i++) {
for (x = 0; x < rowNum; x++) {
boardArray.push([i*10, x*10]);
}
}
//This is the png of an empty board part of an array
var emptySquareImg = new Image();
emptySquareImg.src = "border.png";
function displayBoard() {
background.fillStyle = "rgb(0, 0, 0)";
background.fillRect(0, 0, canvas.width, canvas.height);
for (x = 0; x < colNum; x++) {
for (y = 0; y < rowNum; y++) {
emptySquare.beginPath();
emptySquare.lineWidth = "4";
emptySquare.strokeStyle = "rgb(200, 34, 22)";
var arr = boardArray[y+x*colNum];
emptySquare.rect(arr[0], arr[1], 10, 10)
emptySquare.stroke();
}
}
}
displayBoard();
}
<canvas id='paper'></canvas>
I'm trying to get the pixel array from a 16 x 16 image with getImageData(). I will use that to make a map from larger tiles. For example, if the color of the the top left pixel is green, then on my actual game map there will be a grass tile at 0, 0. My code for getting the array:
function getData() {
map = new Image();
map.src = "./res/maps/sample.png";
ctxTemp.drawImage(map, 0, 0);
var temp = ctxTemp.getImageData(0, 0, 16, 16).data;
console.log(temp);
for (var i = 0; i < temp.length; i+=4) {
var red = temp[i];
green = temp[i + 1];
blue = temp[i + 2];
alpha = temp[i + 3];
}
}
Must wait until the image is loaded. Works fine after attaching the code to onload.
function getData() {
var map = new Image();
map.onload = function() {
var canvas = document.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext("2d");
ctx.drawImage(map, 0, 0);
var temp = ctx.getImageData(0, 0, 16, 16).data;
console.log(temp);
for (var i = 0; i < temp.length; i += 4) {
var red = temp[i];
green = temp[i + 1];
blue = temp[i + 2];
alpha = temp[i + 3];
}
};
map.src = "./res/maps/sample.png";
}
window.onload = getData;
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.