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>
Related
I've created a very simple JavaScript snippet to illustrate a weird HTML5 canvas behavior that I have been experiencing.
I keep drawing the same set of strokes, just in a different order, every 100ms. Why is it that the color of some of the strokes keeps changing? It only happens when I shuffle the draw order between calls, even though the lines are drawn in the same location and same color each frame.
const canvasWidth = 500;
const gapBetweenLines = 5;
const nbrLines = canvasWidth / gapBetweenLines;
const canvasHeight = 500;
const canvas = document.getElementById('map');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// create an array of line objects, each with a with random color
let lines = [];
for (let i = 0; i < nbrLines; i++) {
lines.push({
index: i,
x: i * gapBetweenLines,
color: '#' + Math.floor(Math.random() * 16777215).toString(16)
});
}
// function to shuffle the given array in place
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
// draw lines on the canvas at specific intervals with the random colors
function drawLines() {
const shuffledLines = [...lines];
shuffle(shuffledLines);
let ctx = canvas.getContext('2d');
for (let i = 0; i < nbrLines; i++) {
const line = shuffledLines[i];
ctx.strokeStyle = line.color;
// ctx.save();
ctx.beginPath();
ctx.moveTo(line.x, 0);
ctx.lineTo(line.x, canvasHeight);
ctx.stroke();
// ctx.restore();
}
}
// call the drawLines function every 100ms
setInterval(drawLines, 100);
<!DOCTYPE html>
<html>
<body>
<h1>Flickering Lines</h1>
<canvas id="map"></canvas>
<div id="lineinfo"></div>
</body>
</html>
The past day had me spending an embarrassing amount of time trying to narrow down the cause of this. Is there something about HTML5 Canvas drawing that I simply misunderstand?
Saving and restoring the context between each stroke does not make any difference.
The problem is in your color generation.
color: '#' + Math.floor(Math.random() * 16777215).toString(16)
Number#toString(16) does not pad the returned string with zeroes:
console.log(12..toString(16)) // "c", not "0C"
This means that in your code, some of your lines may have their color property set to values that aren't a valid HEX format (e.g a five or two chars HEX is invalid).
To fix that, you can force your color generator to be always 6 length by padding as many zeroes as needed using the String#padStart() method.
const canvasWidth = 500;
const gapBetweenLines = 5;
const nbrLines = canvasWidth / gapBetweenLines;
const canvasHeight = 500;
const canvas = document.getElementById('map');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// create an array of line objects, each with a with random color
let lines = [];
for (let i = 0; i < nbrLines; i++) {
lines.push({
index: i,
x: i * gapBetweenLines,
color: '#' + Math.floor(Math.random() * 16777215).toString(16)
// force always 6 length
.padStart(6, "0")
});
}
// function to shuffle the given array in place
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
// draw lines on the canvas at specific intervals with the random colors
function drawLines() {
const shuffledLines = [...lines];
shuffle(shuffledLines);
let ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < nbrLines; i++) {
const line = shuffledLines[i];
ctx.strokeStyle = line.color;
// ctx.save();
ctx.beginPath();
ctx.moveTo(line.x, 0);
ctx.lineTo(line.x, canvasHeight);
ctx.stroke();
// ctx.restore();
}
}
// call the drawLines function every 100ms
setInterval(drawLines, 100);
<!DOCTYPE html>
<html>
<body>
<h1>Flickering Lines</h1>
<canvas id="map"></canvas>
<div id="lineinfo"></div>
</body>
</html>
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
}
}
I've got a script that cycle's through images. The images start pixelated and then when they are in view, become unpixelated. I achieve that by calling this function x amount of times with requestAnimationFrame
Images.prototype.setPixels = function() {
var sw = this.imageWidth,
sh = this.imageHeight,
imageData = this.context.getImageData( 0, 0, sw, sh ),
data = imageData.data,
y, x, n, m;
for ( y = 0; y < sh; y += this.pixelation ) {
for ( x = 0; x < sw; x += this.pixelation ) {
var red = data[((sw * y) + x) * 4];
var green = data[((sw * y) + x) * 4 + 1];
var blue = data[((sw * y) + x) * 4 + 2];
for ( n = 0; n < this.pixelation; n++ ) {
for ( m = 0; m < this.pixelation; m++ ) {
if ( x + m < sw ) {
data[((sw * (y + n)) + (x + m)) * 4] = red;
data[((sw * (y + n)) + (x + m)) * 4 + 1] = green;
data[((sw * (y + n)) + (x + m)) * 4 + 2] = blue;
}
}
}
}
}
this.context.putImageData( imageData, 0, 0 );
}
Question: How can I make the individual pixels larger blocks than they are right now. Right now they are pretty small, and the effect is a little jarring. I'm hoping to fix this by having less pixel blocks on the screen, by making them bigger.
I hope this makes sense, I'm fairly green with canvas, so anything you could do to point me in the right direction would be great!
The best for this kind of effect is to simply use drawImage and let the browser handle the pixelation thanks to the nearest-neighbor anti-aliasing algorithm that can be set by changing the imageSmoothingEnabled property to false.
It then becomes a two step process to pixelate an image at any pixel_size:
draw the full quality image (or canvas / video ...) at its original size / pixel_size.
At this stage, each "pixel" is one pixel large.
draw this small image again but up-scaled by pixel_size. To do so, you just need to draw the canvas over itself.
Each pixel is now pixel_size large.
Instead of dealing with hard to read many parameters of drawImage, we can deal the scaling quite easily by just using ctx.scale() method.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function drawPixelated( source, pixel_size ) {
// scale down
ctx.scale(1 / pixel_size, 1 / pixel_size)
ctx.drawImage(source, 0, 0);
// make next drawing erase what's currently on the canvas
ctx.globalCompositeOperation = 'copy';
// nearest-neighbor
ctx.imageSmoothingEnabled = false;
// scale up
ctx.setTransform(pixel_size, 0, 0, pixel_size, 0, 0);
ctx.drawImage(canvas, 0, 0);
// reset all to defaults
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalCompositeOperation = 'source-over';
ctx.imageSmoothingEnabled = true;
}
const img = new Image();
img.onload = animeLoop;
img.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
let size = 1;
let speed = 0.1;
function animeLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
size += speed;
if(size > 30 || size <= 1) {
speed *= -1
}
drawPixelated( img, size );
requestAnimationFrame(animeLoop);
}
<canvas id="canvas" width="800" height="600"></canvas>
Now for the ones that come with a real need to use an ImageData, for instance because they are generating pixel-art, then know that you can simply use the same technique:
put your ImageData with each pixel being 1 pixel large.
scale your context to pixel_size
draw your canvas over itself upscaled
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function putPixelated( imageData, pixel_size ) {
ctx.putImageData(imageData, 0, 0);
// make next drawing erase what's currently on the canvas
ctx.globalCompositeOperation = 'copy';
// nearest-neighbor
ctx.imageSmoothingEnabled = false;
// scale up
ctx.setTransform(pixel_size, 0, 0, pixel_size, 0, 0);
ctx.drawImage(canvas, 0, 0);
// reset all to defaults
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalCompositeOperation = 'source-over';
ctx.imageSmoothingEnabled = true;
}
const img = new ImageData(16, 16);
crypto.getRandomValues(img.data);
let size = 1;
let speed = 0.1;
animeLoop();
function animeLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
size += speed;
if(size > 30 || size <= 1) {
speed *= -1
}
putPixelated( img, size );
requestAnimationFrame(animeLoop);
}
<canvas id="canvas" width="800" height="600"></canvas>
I have the following code which creates a rectangle as shown in the image below:
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<p id='one'></p>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = 'black';
ctx.fillRect(20, 20, 40, 40)
var imgData = ctx.createImageData(100, 100);
var i;
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
// ctx.putImageData(imgData, 10, 10);
</script>
</body>
</html>
When I make sure that the image data is added by not commenting ctx.putImageData , I get only the image data like the picture shown below:
How do I make sure that the image is created on top of the black rectangle in the sense that in the white spaces between the red lines?
I want to be able to see the black rectangle. I tried changing the alpha channel but that did not do anything.
Thanks in advance :).
putImageData will replace the targeted pixels with the ones contained in your ImageData, whatever they are.
This means that if your ImageData contains transparent pixels, these transparent pixels will be there once you put the ImageData on your canvas.
There are several ways to accomplish what you want though.
The one requiring the less code change, is to make your pixel manipulation from the current state of your context. By calling getImageData instead of createImageData, you will have an ImageData which contains the currently drawn pixels in the area you selected.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = 'black';
ctx.fillRect(20, 20, 40, 40);
// get the current pixels at (x, y, width, height)
var imgData = ctx.getImageData(10, 10, 100, 100);
var i;
// and do the pixel manip over these pixels
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
ctx.putImageData(imgData, 10, 10);
<canvas id="myCanvas"></canvas>
The main caveat is that getImageData is slower and requires more memory than createImageData, so if you are gonna do it every frame in an animation, it may not be suitable.
An other solution in this case (animation), is to use a second off-screen canvas, on which you will draw once your ImageData, an then draw this off-screen canvas on the main one using drawImage.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = 'black';
var gridImage = generateGrid(100, 100);
var x = 0;
anim();
/*
Makes your pixel manip on an off-screen canvas
Returns the off-screen canvas
*/
function generateGrid(width, height){
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var off_ctx = canvas.getContext('2d');
var imgData = off_ctx.createImageData(width, height);
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
off_ctx.putImageData(imgData, 0,0);
return canvas;
}
function anim(){
ctx.clearRect(0,0,c.width, c.height);
x = (x + 1) % (c.width + 100);
ctx.fillRect(x, 20, 40, 40);
// and here you draw your generated canvas
ctx.drawImage(gridImage, x - 10, 0);
requestAnimationFrame(anim);
}
<canvas id="myCanvas"></canvas>
You could also refactor your code in order to make your normal drawings (here fillRect) after you did draw the ImageData, but behind current drawings, thanks to the globalCompositeOperation property of your context:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// do first your pixel manip
var imgData = ctx.createImageData(100, 100);
var i;
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
ctx.putImageData(imgData, 10, 10);
// change the gCO to draw behind current non-transparent pixels
ctx.globalCompositeOperation = 'destination-over';
// now make your normal drawings
ctx.fillStyle = 'black';
ctx.fillRect(20, 20, 40, 40);
// reset the gCO
ctx.globalCompositeOperation = 'source-over';
<canvas id="myCanvas"></canvas>
And finally, if you target only next-gen browsers, then you could make use of the ImageBitmap object, which will produce the same result as the off-screen canvas, in less lines of code:
(async () => {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = 'black';
var gridImage = await generateGrid(100, 100);
var x = 0;
anim();
/*
Makes your pixel manip on an empty ImageData
Returns an Promise resolving to an ImageBitmap
*/
function generateGrid(width, height) {
var imgData = ctx.createImageData(width, height);
for (i = 0; i < imgData.data.length; i += 16) {
imgData.data[i + 0] = 255;
imgData.data[i + 1] = 0;
imgData.data[i + 2] = 0;
imgData.data[i + 3] = 255;
}
return createImageBitmap(imgData);
}
function anim() {
ctx.clearRect(0, 0, c.width, c.height);
x = (x + 1) % (c.width + 100);
ctx.fillRect(x, 20, 40, 40);
// and here you draw your generated canvas
ctx.drawImage(gridImage, x - 10, 0);
requestAnimationFrame(anim);
}
})();
<canvas id="myCanvas"></canvas>
I couldn't make it work with you exact code but when I tried with 2 images overlapping I managed to make them overlap.
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
</canvas>
<p id = 'one'></p>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = ctx.createImageData(100, 100);
var i;
for (i = 0; i < img.data.length; i += 6) {
img.data[i+0] = 255;
img.data[i+1] = 0;
img.data[i+2] = 0;
img.data[i+3] = 255;
}
var img2 = ctx.createImageData(100, 100);
var i;
for (i = 0; i < img2.data.length; i += 20) {
img2.data[i+0] = 0;
img2.data[i+1] = 255;
img2.data[i+2] = 0;
img2.data[i+3] = 255;
}
var pixels = 4*100*100;
var imgData1 = img.data;
var imgData2 = img2.data;
while (pixels--){
imgData1[pixels] = imgData1[pixels] * 0.5 + imgData2[pixels] *0.5;
}
img.data = imgData1;
ctx.putImageData(img, 10, 0);
</script>
hello Guys i was trying to copy an image pixel to a matrix so i can use it later. i was wondering if i was using the matrix in js correctly since i am a begginer thanks
<canvas id="Canvas" >
Navigator doesnt support canvas
</canvas>
<script type="text/javascript">
var img = new Image();
img.src = "jj.JPG"; // Set source path
img.onload = function() {
var matrix = [];
context.putImageData(idata, 0, 0);
for(var y = 0; y < canvas.height; y++) {
matrix[y] = [];
for(var x = 0; x < canvas.width; x++){
var imgd = context.getImageData(x, y, canvas.width, canvas.height);
var pix = imgd.data;
var pos = (y * canvas.width + x) * 4;
matrix[y][pos]= pix[pos];; //red
matrix[y][pos+1] =pix[pos+1];; //bleu
matrix[y][pos+2]= pix[pos+2];; //green
matrix[y][pos+3]= 255; //alpha
}
}
</script>
</body>
</html>
for(var y = 0; y < canvas.height; y++) {//ligne
matrix[y] = [];
for(var x = 0; x < canvas.width; x++){
//colonne
var imgd = context.getImageData(x, y, canvas.width, canvas.height);
var pix = imgd.data;
var pos = (y * canvas.width + x) * 4; // position de pixel
matrix[pos]= pix[pos];; // some R value [0, 255]
matrix[pos+1] =pix[pos+1];; // some G value
matrix[pos+2]= pix[pos+2];; // some B value
matrix[pos+3]= 255; // set alpha channel
}
}
return matrix;
}
for(var y = 0; y < canvas.height; y++) {//ligne
for(var x = 0; x < canvas.width; x++){
//colonne
var imgd = context.getImageData(x, y, canvas.width, canvas.height);
var pix = imgd.data;
var pos = (y * canvas.width + x) * 4; // position de pixel
matrix[pos]= pix[pos];; // some R value [0, 255]
matrix[pos+1] =pix[pos+1];; // some G value
matrix[pos+2]= pix[pos+2];; // some B value
matrix[pos+3]= 255; // set alpha channel
}
}
return matrix;
}