Why is Javascript Canvas putImageData drawing a white image to screen? [duplicate] - javascript

This question already has an answer here:
Unable to update HTML5 canvas pixel color using putImageData in JavaScript
(1 answer)
Closed last year.
I am trying to draw a gray gradient (or a few of them actually) to the canvas. To do this I am using the getImageData() and putImageData() methods of canvas. data is an Uint8ClampedArray with size 1000 * 1000 * 4. In the for loops the rgb color elements of data are correctly set to be x%255, as shown by printing the data array to console.
However the result of code differs from what is expected. A completely white cnvas is shown while 4 gray gradients are expected.
Minimal working example:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id = "canvas" width = "1000" height = "1000"></canvas>
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
draw();
function draw(){
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for(let y = 0; y < canvas.height; y++){
for(let x = 0; x < canvas.width; x++){
for(let color = 0; color < 3; color++){
let idx = (y * canvas.width + x) * 4;
data[idx + color] = x % 255;
}
}
}
ctx.putImageData(imageData, 0, 0);
}
</script>
</body>
</html>

You're not setting the alpha channel.
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
let idx = (y * canvas.width + x) * 4;
for (let color = 0; color < 3; color++) {
data[idx + color] = x % 255;
}
data[idx + 3] = 255; // alpha
}
}

Related

Why isn't my Mandelbrot set code working properly?

I'm trying to create the Mandelbrot set and other Julia Sets in JavaScript for a school project. I've looked over my code and it looks like it follows the formula correctly, but my Mandelbrot set is not being drawn correctly. Any tips on what's wrong?
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="canvas" width="600" height="600" style="background-color: black;"></canvas>
<script>
//center the canvas at the coordinate plane
var canvas = document.getElementById("canvas");
const width = canvas.width;
const height = canvas.height;
const scalingFactor = width / 4;
var graphics = canvas.getContext("2d");
function mandelbrotDraw(iterations) {
//multiply by 4 in order to get the scaling right
for (var i = 0; i <= width; i++) {
for (var j = 0; j <= height; j++) {
var x = 0;
var y = 0;
var re = (i - width / 2) / scalingFactor;
var im = (j - height / 2) / scalingFactor;
var k = 0;
while (x * x + y * y <= 4 && k < iterations) {
x = x * x - y * y + re;
y = 2 * x * y + im;
k++;
}
if (k < iterations) {
graphics.fillStyle = "black";
graphics.fillRect(i, j, 1, 1);
} else {
graphics.fillStyle = "white";
graphics.fillRect(i, j, 1, 1);
}
}
}
}
mandelbrotDraw(25);
</script>
</body>
</html>

Canvas Matrix Animation JS

I've found this example on codePen, and the first iteration when letters moving in one line looks terrible,
is it possible to start moving columns from different postions from start? (not from second iteration)
my solution is terrible too: I've added #keyframes with opacity 0 to 100 and added animation with duration 3s and animation-timing-function: step-end;
// geting canvas by Boujjou Achraf
var c = document.getElementById("c");
var ctx = c.getContext("2d");
//making the canvas full screen
c.height = window.innerHeight;
c.width = window.innerWidth;
//chinese characters - taken from the unicode charset
var matrix = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789##$%^&*()*&^%+-/~{[|`]}";
//converting the string into an array of single characters
matrix = matrix.split("");
var font_size = 10;
var columns = c.width/font_size; //number of columns for the rain
//an array of drops - one per column
var drops = [];
//x below is the x coordinate
//1 = y co-ordinate of the drop(same for every drop initially)
for(var x = 0; x < columns; x++)
drops[x] = 1;
//drawing the characters
function draw()
{
//Black BG for the canvas
//translucent BG to show trail
ctx.fillStyle = "rgba(0, 0, 0, 0.04)";
ctx.fillRect(0, 0, c.width, c.height);
ctx.fillStyle = "#f4427d";//green text
ctx.font = font_size + "px arial";
//looping over drops
for(var i = 0; i < drops.length; i++)
{
//a random chinese character to print
var text = matrix[Math.floor(Math.random()*matrix.length)];
//x = i*font_size, y = value of drops[i]*font_size
ctx.fillText(text, i*font_size, drops[i]*font_size);
//sending the drop back to the top randomly after it has crossed the screen
//adding a randomness to the reset to make the drops scattered on the Y axis
if(drops[i]*font_size > c.height && Math.random() > 0.975)
drops[i] = 0;
//incrementing Y coordinate
drops[i]++;
}
}
setInterval(draw, 35);
/* By Boujjou Achraf*/
/*basic reset */
*{
margin: 0;
padding: 0;
}
body {background: black;}
canvas {display:block;}
/* By Boujjou Achraf*/
<html>
<head>
</head>
<body>
<canvas id="c"></canvas>
</body>
</html>
The vertical screen position for each of the animations' letters is stored in an array called drops.
At startup each letter gets the same initial position by the following two lines:
for (var x = 0; x < columns; x++)
drops[x] = 1;
You can make each letter's position a random number from 0 to the height of the canvas element by changing the above to this:
for (var x = 0; x < columns; x++)
drops[x] = parseInt(Math.random() * c.height);
Math.random() returns a floating point number between 0 and 1 and c.height returns the height of the <canvas> element. By multiplying these two values we get a random number between 0 and the canvas' height.
Here's your modified example:
var c = document.getElementById("c");
var ctx = c.getContext("2d");
//making the canvas full screen
c.height = window.innerHeight;
c.width = window.innerWidth;
//chinese characters - taken from the unicode charset
var matrix = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789##$%^&*()*&^%+-/~{[|`]}";
//converting the string into an array of single characters
matrix = matrix.split("");
var font_size = 10;
var columns = c.width / font_size; //number of columns for the rain
//an array of drops - one per column
var drops = [];
//x below is the x coordinate
//1 = y co-ordinate of the drop(same for every drop initially)
for (var x = 0; x < columns; x++)
drops[x] = parseInt(Math.random() * c.height);
//drawing the characters
function draw() {
//Black BG for the canvas
//translucent BG to show trail
ctx.fillStyle = "rgba(0, 0, 0, 0.04)";
ctx.fillRect(0, 0, c.width, c.height);
ctx.fillStyle = "#f4427d"; //green text
ctx.font = font_size + "px arial";
//looping over drops
for (var i = 0; i < drops.length; i++) {
//a random chinese character to print
var text = matrix[Math.floor(Math.random() * matrix.length)];
//x = i*font_size, y = value of drops[i]*font_size
ctx.fillText(text, i * font_size, drops[i] * font_size);
//sending the drop back to the top randomly after it has crossed the screen
//adding a randomness to the reset to make the drops scattered on the Y axis
if (drops[i] * font_size > c.height && Math.random() > 0.975)
drops[i] = 0;
//incrementing Y coordinate
drops[i]++;
}
}
setInterval(draw, 35);
* {
margin: 0;
padding: 0;
}
body {
background: black;
}
canvas {
display: block;
}
<canvas id="c"></canvas>

Converting image to black and white with Javascript [duplicate]

This question already has answers here:
How do you convert a color image to black/white using Javascript?
(11 answers)
Closed 3 years ago.
I have a js function, it returns the image on canvas that I give in the source and B&W of it side by side, the thing is I am a beginner in js so I do not know which line should I edit to get the only B&W image as a result on the canvas not the original image.
function imageLoaded(ev) {
element = document.getElementById("canvas");
const c = element.getContext("2d");
im = ev.target;
width = element.width;
height = element.height;
//image on the left of the canvas:
c.drawImage(im, 0, 0);
let imageData = c.getImageData(0, 0, width, height);
// width index is output position.
w2 = width / 2;
// run through the image.
// height of the image.
for (y = 0; y < height; y++) {
// *4 for 4 ints per pixel.
//this is an input index.
inpos = y * width * 4;
//this is an output index.
outpos = inpos + w2 * 4
// width of the image.
for (x = 0; x < w2; x++) {
r = imageData.data[inpos++];
g = imageData.data[inpos++];
b = imageData.data[inpos++];
a = imageData.data[inpos++];
// this is transforming RGB color space to gray scale.
gray = (0.30 * r + 0.59 * g + 0.11 * b);
// proper threshold value for black and white
if (gray > 55) {
//set the pixel to white.
imageData.data[outpos++] = 255;
imageData.data[outpos++] = 255;
imageData.data[outpos++] = 255;
imageData.data[outpos++] = a;
} else {
//set the pixel to black.
imageData.data[outpos++] = 0;
imageData.data[outpos++] = 0;
imageData.data[outpos++] = 0;
imageData.data[outpos++] = a;
}
}
}
//put pixel data on canvas.
c.putImageData(imageData, 0, 0);
}
im = new Image();
im.onload = imageLoaded;
//im.src = "B_01.jpg";
//im.src = "img.png";
//im.src = "164228060-dodge-challenger-wallpapers.jpg";
im.src = "rab.jpg";
//im.src = "https://picsum.photos/id/431/200/300";
Here is the final code:
function imageLoaded(ev) {
element = document.getElementById("canvas");
const c = element.getContext("2d");
im = ev.target;
width = element.width;
height = element.height;
//image on the left of the canvas:
c.drawImage(im, 0, 0);
let imageData = c.getImageData(0, 0, width, height);
// width index is output position.
w2 = width;
// run through the image.
// height of the image.
for (y = 0; y < height; y++) {
// *4 for 4 ints per pixel.
//this is an input index.
inpos = y * width * 4;
//this is an output index.
outpos = inpos;
// width of the image.
for (x = 0; x < w2; x++) {
r = imageData.data[inpos++];
g = imageData.data[inpos++];
b = imageData.data[inpos++];
a = imageData.data[inpos++];
// this is transforming RGB color space to gray scale.
gray = (0.30 * r + 0.59 * g + 0.11 * b);
// proper threshold value for black and white
if (gray > 55) {
//set the pixel to white.
imageData.data[outpos++] = 255;
imageData.data[outpos++] = 255;
imageData.data[outpos++] = 255;
imageData.data[outpos++] = a;
} else {
//set the pixel to black.
imageData.data[outpos++] = 0;
imageData.data[outpos++] = 0;
imageData.data[outpos++] = 0;
imageData.data[outpos++] = a;
}
}
}
//put pixel data on canvas.
c.putImageData(imageData, 0, 0);
}
im = new Image();
im.onload = imageLoaded;
im.src = "rab.jpg";
I have changed two things:
I removed the division by 2, so you can see the full image:
// width index is output position.
w2 = width;
And I set outpos equal to inpos:
//this is an input index.
inpos = y * width * 4;
//this is an output index.
outpos = inpos;
This mean that the input position ist the same as the output position, which causes an override of the current pixel color.

How to calculate actual width and height of rotated object in canvas?

Am trying to calculate width and height of object i loaded into canvas. When object is not rotated i get correct left right top bottom values, but when i load rotated object in canvas then i not get correct values , so i wonder what will be the logic or math formula to do achieve it.
how am doing.
initially load image into canvas
get image data from canvas
loop through image data to get only colored pixels by using alpha check
from colored pixel array find min max xy values
var temp_ray = []; // pixel array
for (var y = 0; y < imgData.height; ++y) {
for (var x = 0; x < imgData.width; ++x) {
var index = (y * imgData.width + x) * 4;
if(imgData.data[index+3]){
var xc = (index / 4) % imgData.width;
var yc = Math.floor((index / 4) / imgData.width);
temp_ray.push([xc,yc]);
}
}
}
if(temp_ray.length > 0){
var Xind = MaxMin2darray(temp_ray,0);
var Yind = MaxMin2darray(temp_ray,1);
var W = parseFloat(Xind['max']) - parseFloat(Xind['min']);
var H = parseFloat(Yind['max']) - parseFloat(Yind['min']);
var center_x = Xind['min'] + (W/2);
var center_y = Yind['min'] + (H/2);
// find corners of object
// find *min x , min y
let top_left = temp_ray[Xind['imin']]; // min X priority , min Y // top left
// find max x , *min y
let top_right = temp_ray[Yind['imin']]; // max X, min Y priority , // top right
// find *max x , min y
let bot_right = temp_ray[Xind['imax']]; // max X priority , min Y // bottom right
// find max x , *max y
let bot_left = temp_ray[Yind['imax']]; // max X , max Y priority // bottom left
var dim = {'W':W,'H':H,'CenterX':center_x,'CenterY':center_y,'top_left':top_left,'top_right':top_right,'bot_right':bot_right,'bot_left':bot_left,'Xend':Xind['max'],'Yend':Yind['max'],'Xstart':Xind['min'],'Ystart':Yind['min'],'Xend':Xind['max'],'Yend':Yind['max']};
console.log(dim);
}
and then using min max xy value find corners of object which works with none rotated objects but not work with rotated/tilted objects.
so any idea how to solve this problem
openpnp project is achieving this through opencv, but i think in js we do not have opencv library nor am that pro of java :(.
https://github.com/openpnp/openpnp/blob/develop/src/main/java/org/openpnp/vision/pipeline/stages/DrawRotatedRects.java
jsfiddle: http://jsfiddle.net/4L13vtaj/
In some simple cases (like rectangular objects), you could try to rotate the image until you minimize the number of uncolored pixels.
So you start with your image, and for each of the possible 360°, you compute the ratio. This is not perfect, but "doable" simply in pure js.
Here's a pseudoCode that might help you:
for degree in [0,365]{
rotateOriginalImageBy(degree);
cost[degree] = NemptyPixels/NfilledPixels;
}
predictedDegree = Math.min(cost);
rotateOriginalImageBy(predictedDegree);
compute 2 dimensions;
width = largerDimension;
height = shorterDimension;
Begining of an implementation (I edited your jsfiddle):
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var rotatioDegree = 45;
var imageObject = new Image();
imageObject.onload = function() {
var canvasWidth = imageObject.width;
var canvasHeight = canvasWidth; // not useful since width==height
document.getElementById('canvas').width = canvasWidth;
document.getElementById('canvas').height = canvasWidth;
ctx.clearRect(0, 0, canvasWidth, canvasWidth);
// Move registration point to the center of the canvas
ctx.translate(canvasWidth/2, canvasWidth/2)
ctx.rotate(rotatioDegree*3.1415/180);
ctx.translate(-canvasWidth/2,-canvasWidth/2)
ctx.drawImage(imageObject,0,0);
ctx.translate(canvasWidth/2, canvasWidth/2)
ctx.rotate(-rotatioDegree*3.1415/180);
ctx.translate(-canvasWidth/2,-canvasWidth/2)
var imgData = ctx.getImageData(0, 0, canvasWidth, canvasWidth);
http://jsfiddle.net/4L13vtaj/17/
If this doesn't work, you could implement some image detection techniques (Mathematical morphology for example). But i think this is outside the scope of stackoverflow.
If you work with some approximation, you can have something like that; I hope at least it can provide to you some ideas:
// some pixels in this image are not transparent, so we add a tollerance
// you can try to remove the second condition.
const isNotEmpty = (color) => color && color < 0xffaaaaaa;
function getTop(buff, w, h) {
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
let i = y * w + x;
if (isNotEmpty(buff[i])) {
return {x, y}
}
}
}
}
function getRight(buff, w, h) {
for (let x = w; x >=0; x--) {
for (let y = 0; y < h; y++) {
let i = y * w + x;
if (isNotEmpty(buff[i])) {
return {x, y}
}
}
}
}
function getBottom(buff, w, h) {
for (let y = h; y >= 0; y--) {
for (let x = 0; x < w; x++) {
let i = y * w + x;
if (isNotEmpty(buff[i])) {
return {x, y}
}
}
}
}
function getLeft(buff, w, h) {
for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
let i = y * w + x;
if (isNotEmpty(buff[i])) {
return {x, y}
}
}
}
}
async function main(imageSource) {
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const imageObject = new Image();
imageObject.src = imageSource;
await new Promise(r => imageObject.onload = r);
const w = canvas.width = imageObject.width;
const h = canvas.height = imageObject.height;
ctx.clearRect(0, 0, w, h);
ctx.drawImage(imageObject, 0, 0);
const imgData = ctx.getImageData(0, 0, w, h);
const buff = new Uint32Array(imgData.data.buffer);
const points = [
getTop(buff, w, h),
getRight(buff, w, h),
getBottom(buff, w, h),
getLeft(buff, w, h)
];
ctx.strokeStyle = "#0000ff"
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[1].x, points[1].y);
ctx.lineTo(points[2].x, points[2].y);
ctx.lineTo(points[3].x, points[3].y);
ctx.closePath();
ctx.stroke();
}
main(/* image's url*/);
Here the link for testing: https://codepen.io/zer0/pen/zLxyQV
There are several problem with this approach: as said, with irregular images, it's not precise, in fact you will see the pin are making the image's bounding box a little bit smaller.
But the thing can be worse: try in the link above to use the 2nd image, that is quite irregular, and you will see.
Of course we can compensate, using also a bit more complex algorithm instead this simple one, but the question is: what the expected result for something like the 2nd image? Depends by that you can decide how to proceed.

How to rescale image to draw with canvas?

If you have an idea ,
Starting from the code of this post In javascript , how to reverse y axis of canvas?( with the Flip rendered content solution)
i would like to draw this last image (200*200) in a 100*100 canvas, rescaling it using ctx.drawImage(ctx.canvas,0,0,200,200,0,0,100,100) but it only crops it
regards
Scale the context by 0.5 before drawing.
ctx.scale(0.5,0.5)
var i,j;
const n = 200;
const size = n ** 2; // ** is to power of
//const canvas = document.querySelector('canvas'); // not needed canvas is
// already defined with
// id="canvas"
const ctx = canvas.getContext("2d");
const imgData = ctx.createImageData(n, n);
const Z = [];
for (j = 0; j < size; j ++) { Z[j] = j * 256 / size }
Z[n * n - 1] = 0;
i = 0;
for (j = 0; j < size; j++) {
imgData.data[i++] = Z[j];
imgData.data[i++] = Z[j];
imgData.data[i++] = Z[j];
imgData.data[i++] = 255;
}
ctx.putImageData(imgData, 0, 0);
ctx.scale(0.5,0.5);
// flip the canvas
ctx.transform(1, 0, 0, -1, 0, canvas.height)
ctx.globalCompositeOperation = "copy"; // if you have transparent pixels
ctx.drawImage(ctx.canvas,0,0);
ctx.globalCompositeOperation = "source-over"; // reset to default
<canvas id="canvas" width=200 height=200></canvas>

Categories