The project that i have put together here called Swellcloud. The animation was forked from here. This code connects to a wave buoy just off my local beach and the animation is relevant to the live conditions. If the 'swell' is high, I would like the peaks & troughs to be large, the swell data has a range of min 0.1m = smallest swell so low troughs & peaks in the animation. And maximum 10m large waves so large troughs & peaks... then the surf 'period' data which has a range of 0s to 20s would reflect the 'smoothness' of the animation, so high period nice straight lines on the animation, and low period would be choppy/ragged lines.
I have managed to get the data to 'speed' up the animation if the swell data is large but i cant control the height of the waves on the animation or the period
Does anyone have any pointers?
We make these variables global so we know when they have loaded:
let surfheight, surfperiod;
fetch(
"https://data.channelcoast.org/observations/waves/latest?key='my key"
)
.then(function (resp) {
return resp.text();
})
.then(function (data) {
//console.log(data);
let parser = new DOMParser(),
xmlDoc = parser.parseFromString(data, "text/xml");
//console.log(xmlDoc.getElementsByTagName('ms:hs')[36].innerHTML); //76=Perran,36 Porthleven
surfheight = xmlDoc.getElementsByTagName("ms:hs")[36].innerHTML;
surfperiod = xmlDoc.getElementsByTagName("ms:tp")[36].innerHTML;
// you can set the surf variable here, because the sketch will start only after the data loads,
// also make sure to first convert it to a number like "Number(surfheight)" otherwise it won't work
surfht = Number(surfheight);
surfpd = Number(surfperiod);
document.getElementById("surfheight").textContent = surfheight;
document.getElementById("surfperiod").textContent = surfperiod;
});
var yoff = 0; // 2nd dimension of perlin noise
var waveColor, waveColor2, waveColor3;
var waveColorArr;
var controls, waveSpeed;
var canvas;
let surfht;
let surfpd;
function setup() {
canvas = createCanvas(windowWidth, windowHeight);
waveColor = color(0, 50, 120, 100);
waveColor2 = color(0, 100, 150, 100);
waveColor3 = color(0, 200, 250, 100);
noiseDetail(2, 0.2);
waveColorArr = [waveColor, waveColor, waveColor2, waveColor2, waveColor3, waveColor3];
}
function draw() {
// after these load, the sketch starts
if (!surfperiod && !surfheight) {
return;
}
background(0);
noStroke();
const amp = map(surfht, 0, 10, 0, 1);
//const amp = map(surfpd, 0, 10, 0, 1);
for (var i = 0; i <= 5; i++) {
// We are going to draw a polygon out of the wave points
beginShape();
fill(waveColorArr[i]);
var xoff = 0;
for (var x = 0; x <= width + 500; x += 100) {
var y = map(
noise(xoff, yoff - 0.5 * i),
0,
1,
(height / 10) * (i + 1),
height - height / 10 + (height / 10) * i
);
vertex(x, y);
// i've extracted this into a variable for cleaner code
const inc = map(surfpd, 0, 20, 0.01, 0.5);
xoff += inc + 0.5 / 10000.0;
}
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
}
const inc = map(surfht, 0, 10, 0, 0.025);
yoff += 0.007 + inc + 0.5 / 10000.0;
}
function windowResized() {
resizeCanvas(window.innerWidth, window.innerHeight);
}
This is a the bit of code that mainly draws a single wave:
// We are going to draw a polygon out of the wave points
beginShape();
fill(waveColorArr[i]);
var xoff = 0;
for (var x = 0; x <= width + 500; x += 100) {
var y = map(
noise(xoff, yoff - 0.5 * i),
0,
1,
(height / 10) * (i + 1),
height - height / 10 + (height / 10) * i
);
vertex(x, y);
// i've extracted this into a variable for cleaner code
const inc = map(surfpd, 0, 20, 0.01, 0.5);
xoff += inc + 0.5 / 10000.0;
}
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
You've already figured out how to map() the inc value.
Similar notice y is mapped as well, from 0.0 -> 1.0 range to (height / 10) * (i + 1)
to height - height / 10 + (height / 10) * i range.
A quick and hacky way to do it is to multiply those values by a value which would scale the wave height.
Better yet, you could encapsulate the instructions into a re-usable function, configurable with parameters.
You can also have a look at this detailed answer on drawing sine waves and remember that you can add/multiply waves together to get different shapes.
Related
I have written a sine graph plotting program. It works apart from the fact that the graph is choppy. I need to make it so that I can stretch the line without changing the x values of the points.
Here is the project: https://editor.p5js.org/korczaka6052/sketches/49-sJW6wz
let slidery;
let sliderx
width_ = 800
height_ = 400
linelen = width_ - 100
function setup() {
createCanvas(width_, height_);
//create sliders controlling xstep and ystep
slidery = createSlider(0, 100);
slidery.position(10, 10);
slidery.style('width', '200px');
sliderx = createSlider(0, 100);
sliderx.position(250, 10);
sliderx.style('width', '200px');
}
class createPoint { //create a point with its own x and y coordinate, ik could use vectors
constructor(x_, y_) {
this.x = 50 + x_;
this.y = height_ / 2 - y_;
}
show() {
fill(0)
circle(this.x, this.y, 1)
}
writetext() {
textSize(10)
text(this.x, this.x, height / 2 + 10)
}
}
//where all the points will be stored
points = [];
xstep = 1;
ystep = 50;
looper = 0;
function draw() {
//set xstep and ystep to their slider values
ystep = slidery.value()
xstep = sliderx.value()
stroke(0)
background(220);
//create graph lines
line(50, height / 2, width - 50, height / 2);
line(50, 50, 50, height - 50);
//for every [ystep] pixels calculate y based off equation
for (i = 0; i < 800; i++) {
points[i] = new createPoint(i * xstep, sin(i) * ystep); //creates that point as object with x and y value, y = sin(x)
}
//create line between current and previous point
for (i = 1; i < 800; i++) {
stroke(255, 0, 0)
//create only if point is inside canvas
if (points[i - 1].y < height) {
line(points[i - 1].x, points[i - 1].y, points[i].x, points[i].y)
}
}
//create white borders to cut off extreme points
noStroke(0)
fill(220)
rect(width - 50, 0, width, height)
rect(width, height, width - 50, 0)
rect(0, 0, width, 50)
rect(0, height, width, -50)
looper++
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
I am relatively new to p5.js so please excuse bad code.
The reason your line is choppy is because when xstep is greater than 1, the horizontal distance between each pair of points is relatively large. When you start out with an xstep of 50 you may have 800 points but only the first 16 or so are actually on the screen. Instead of advancing your x coordinate by xstep you should just advance by 1 (i.e., use i for the first argument to createPoint instead of i * xstep). When you do so, in order to draw the desired curve, you need to change the input to the sin() function used to calculate the corresponding y value. Because when you move forward by one pixel you are only moving to the right by 1 / xstep in the graph coordinate system, you simply need to use sin(i / xstep) instead of sin(i).
let slidery;
let sliderx;
let smooothCheckbox;
const width_ = 800;
const height_ = 400;
const linelen = width_ - 100;
function setup() {
createCanvas(width_, height_);
//create sliders controlling xstep and ystep
slidery = createSlider(0, 100);
slidery.position(10, 10);
slidery.style('width', '200px');
sliderx = createSlider(0, 100);
sliderx.position(250, 10);
sliderx.style('width', '200px');
smoothCheckbox = createCheckbox('Smooth?', true);
smoothCheckbox.position(10, 30);
}
class createPoint { //create a point with its own x and y coordinate, ik could use vectors
constructor(x_, y_) {
this.x = 50 + x_;
this.y = height_ / 2 - y_;
}
show() {
fill(0)
circle(this.x, this.y, 1)
}
writetext() {
textSize(10)
text(this.x, this.x, height / 2 + 10)
}
}
//where all the points will be stored
let points = [];
let xstep = 1;
let ystep = 50;
let looper = 0;
function draw() {
//set xstep and ystep to their slider values
ystep = slidery.value();
xstep = sliderx.value();
stroke(0);
background(220);
//create graph lines
line(50, height / 2, width - 50, height / 2);
line(50, 50, 50, height - 50);
//for every [xstep] pixels calculate y based off equation
for (i = 0; i < 800; i++) {
if (smoothCheckbox.checked()) {
points[i] = new createPoint(i, sin(i / xstep) * ystep); //creates that point as object with x and y value, y = sin(x)
} else {
points[i] = new createPoint(i * xstep, sin(i) * ystep);
}
}
//create line between current and previous point
for (i = 1; i < 800; i++) {
stroke(255, 0, 0);
//create only if point is inside canvas
if (points[i - 1].y < height) {
line(points[i - 1].x, points[i - 1].y, points[i].x, points[i].y);
}
}
//create white borders to cut off extreme points
noStroke(0);
fill(220);
rect(width - 50, 0, width, height);
rect(width, height, width - 50, 0);
rect(0, 0, width, 50);
rect(0, height, width, -50);
looper++;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
I have an exponential curve made using p5.js that draws itself over time as follow :
However I am trying to have it responsive in such a way that as the curve grows, it would always be fully visible inside the canvas.
Here are screenshots of what I mean to achieve :
Working examples found on a website
As you can see on this example, once it reaches the edges of the canvas, it kind of "zooms out" in order for the canvas to fit the whole curve, as a result since it never leaves the canvas the curve bends the more it grows.
To try and achieve this, I explored using scale scale(x / 100, y / 100) to be triggered once the limits of the canvas are reached. That way the canvas starts scaling proportionally to the curve's expansion.
However, using this method does not solve my problem because it seems that reducing the scaling while adding points to the curve does not make the curve grow anymore.
Here is my current (updated) code :
var y = 49;
var x = 0;
var inc = 0.02;
var translateY;
let createTopY = false;
let createTopX = false;
var topY;
var percentageIncY = 100;
var perecntageIncX = 100;
// Scaling
var scaleX = 1
var scaleY = 1
function setup() {
createCanvas(400, 200);
background(20, 25, 29)
}
function draw() {
frameRate(20)
// Set curve history for continuous lines
let prev_x = x
let prev_y = y
// Recreate canvas position (origin) based on Scale Y (zoom)
translateY = height * scaleY
translate(0, (height - translateY) + 49 ) // 49 is the initial y value
scale(scaleX, scaleY)
// Exponential curve values
x = x + 5 // Approximate
y = y + y * inc
// Draw curve
strokeWeight(3)
stroke(229, 34, 71);
line(prev_x, height - prev_y, x, height - y);
// Create topY when top at scale(1) is reached
if (createTopY !== true) checkInt(y)
if (createTopX !== true) checkInt(x)
//-------------- SCALING ---------------//
// From TopX, decrease scale exponentialy
if (x > width - 20) { // Temporarily set to 20 (50-30) to better visualize
// The increased value of x in % starting at topX
percentageIncX = (x * 100) / (width - 20)
// Decrease scaleX exponentialy
scaleX = 100 / percentageIncX
print(
"% increase X: " +
percentageIncX
)
}
// From topY, decrease scale exponentialy
if (y > height + 20) { // Temporarily set to 20 (50-30) to visualize
// The increased value of y in % starting at topY
percentageIncY = (y * 100) / (height + 20) // Temporarily set to 20 (50-30) to better visualize
// Decrease scaleY exponentialy
scaleY = 100 / percentageIncY
print(
"% increase Y: " +
percentageIncY
)
}
//-------------------------------------//
}
const checkInt = (prop) => {
const parsed = int(prop)
if (parsed > height + 20) { // Temporarily set to 20 (50-30) to better visualize
createTopY = true
createTopX = true
topY = y
print('createTopY is: ' + createTopY)
print('createTopX is: ' + createTopX)
print("---START SCALING---")
print('starting at ' + y)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
Use frameRate() to control to the number of frames to be displayed every second. Stitch the curve with line segments (line()) instead of drawing with single dots (ellipse()) to draw a curve without interruptions.
var y = 1;
var x = 0;
var inc = 0.01;
function setup() {
createCanvas(400, 400);
background(100)
frameRate(100)
}
function draw() {
let prev_x = x;
let prev_y = y;
x = x + 0.5
y = y + y * inc;
noFill();
stroke(255, 0, 0, 255);
strokeWeight(3);
line(prev_x, height-prev_y, x, height-y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
paintForegroundBars(hours: Array<number>) {
let barColor = "#b3bec9";
let numBars = hours.length;
let barWidth = Math.floor((this.canvasWidth / numBars) - this.barsSpacing);
let maxBarHeight = this.canvasHeight - (this.timesHeight + (this.timesSpacing * 2));
let barLeft = 0 + (this.barsSpacing / 2);
this.canvasContext.fillStyle = barColor;
this.canvasContext.strokeStyle = barColor;
this.canvasContext.lineJoin = "round";
this.canvasContext.lineWidth = this.cornerRadius;
for (let i = 0; i < numBars; i++) {
let barHeight = Math.round(maxBarHeight * hours[i]);
if (barHeight > 0) {
let barTop = maxBarHeight - barHeight;
let roundedBarTop = barTop + (this.cornerRadius / 2);
let roundedBarLeft = barLeft + (this.cornerRadius / 2);
let roundedBarWidth = barWidth - this.cornerRadius;
let roundedBarHeight = barHeight - this.cornerRadius;
this.canvasContext.strokeRect(roundedBarLeft, roundedBarTop, roundedBarWidth, roundedBarHeight);
this.canvasContext.fillRect(roundedBarLeft, roundedBarTop, roundedBarWidth, roundedBarHeight);
}
barLeft = Math.floor(barLeft + barWidth) + (this.barsSpacing);
}
}
At the moment I am drawing the height of a bar chart with the below code:
this.canvasContext.strokeRect(roundedBarLeft, roundedBarTop, roundedBarWidth, roundedBarHeight);
this.canvasContext.fillRect(roundedBarLeft, roundedBarTop, roundedBarWidth, roundedBarHeight);
Instead of when this runs it just being a fixed height I want it to animate from 0 to the height that has been calculated in my JS. How do you go about doing this?
Many thanks
Below is a simple example of how this kind of animation works. The thing you are looking for is the easing value - once you have that, you are set! In this case I store the start time inside the start variable, and then you simply take the current time, remove the start time and divide it by the time you want to pass. This will give you a number between 0 and 1, and multiplying any other number by that will give you the number in the range 0 to n. If you want to add a base value to this, your general formula is basically this:
fromValue + (nowTime - sinceTime) / duration * (toValue - fromValue);
The reason the easing value is so important is that it allows tweening. For example, you can create a smooth curve by multiplying this easing value by itself:
var easing = (nowTime - sinceTime) / duration;
easing = easing * easing;
fromValue + easing * (toValue - fromValue);
Use a graphing application to learn more about these curves :)
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var start = Date.now();
var duration = 5000;
var animationFrame = false;
canvas.width = 40;
canvas.height = 400;
function drawBar(){
var progress = (Date.now() - start) / duration;
if( progress >= 1 ){
// Final state before stopping any drawing function
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.fillRect( 0, 0, canvas.width, canvas.height );
window.cancelAnimationFrame( drawBar );
} else {
// In progress
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.fillRect( 0, 0, canvas.width, canvas.height * progress );
window.requestAnimationFrame( drawBar );
}
}
animationFrame = setInterval( drawBar, 1000 / 60 );
document.body.addEventListener('click', function( event ){
start = Date.now();
window.cancelAnimationFrame( drawBar );
window.requestAnimationFrame( drawBar );
});
<canvas></canvas>
I'm creating a Canvas animation, and have managed to position x number of circles in a circular path. Here's a simplified version of the code I'm using:
var total = circles.length,
i = 0,
radius = 500,
angle = 0,
step = (2*Math.PI) / total;
for( i; i < total; i++ ) {
var circle = circles[i].children[0],
cRadius = circle[i].r,
x = Math.round(winWidth/2 + radius * Math.cos( angle ) - cRadius),
y = Math.round(winHeight/2 + radius * Math.sin( angle ) - cRadius);
circle.x = x;
circle.y = y;
angle += step;
}
Which results in this:
What I am trying to achieve is for all the circles to be next to each other with no gap between them (except the first and last). The circles sizes (radius) are generated randomly and shouldn't adjust to fit the circular path:
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
I expect there to be a gap between the first and last circle.
I'm struggling to get my head around this so any help would be much appreciated :)
Cheers!
Main creation loop :
• take a current radius
• compute the angles it cover ( = atan2(smallRadius/bigRadius) )
• move base angle by this latest angle.
http://jsfiddle.net/dj2v7mbw/9/
function CircledCircle(x, y, radius, margin, subCircleCount, subRadiusMin, subRadiusMax) {
this.x = x;
this.y = y;
this.radius = radius;
this.subCircleCount = subCircleCount;
var circles = this.circles = [];
// build top sub-circles
var halfCount = Math.floor(subCircleCount / 2);
var totalAngle = addCircles(halfCount);
// re-center top circles
var correction = totalAngle / 2 + Math.PI / 2;
for (var i = 0; i < halfCount; i++) this.circles[i].angle -= correction;
// build bottom sub-circles
totalAngle = addCircles(subCircleCount - halfCount);
// re-center bottom circles
var correction = totalAngle / 2 - Math.PI / 2;
for (var i = halfCount; i < subCircleCount; i++) this.circles[i].angle -= correction;
// -- draw this C
this.draw = function (angleShift) {
for (var i = 0; i < this.circles.length; i++) drawDistantCircle(this.circles[i], angleShift);
}
//
function drawDistantCircle(c, angleShift) {
angleShift = angleShift || 0;
var thisX = x + radius * Math.cos(c.angle + angleShift);
var thisY = y + radius * Math.sin(c.angle + angleShift);
ctx.beginPath();
ctx.arc(thisX, thisY, c.r, 0, 2 * Math.PI);
ctx.fillStyle = 'hsl(' + (c.index * 15) + ',75%, 75%)';
ctx.fill();
ctx.stroke();
}
//
function addCircles(cnt) {
var currAngle = 0;
for (var i = 0; i < cnt; i++) {
var thisRadius = getRandomInt(subRadiusMin, subRadiusMax);
var thisAngle = Math.atan2(2 * thisRadius + margin, radius);
var thisCircle = new subCircle(thisRadius, currAngle + thisAngle / 2, i);
currAngle += thisAngle;
circles.push(thisCircle);
}
return currAngle;
}
}
with
function subCircle(radius, angle, index) {
this.r = radius;
this.angle = angle;
this.index = index;
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
use with
var myCircles = new CircledCircle(winWidth / 2, winHeight / 2, 350, 2, 24, 5, 50);
myCircles.draw();
animate with :
var angleShift = 0;
function draw() {
requestAnimationFrame(draw);
ctx.clearRect(0, 0, winWidth, winHeight);
myCircles.draw(angleShift);
angleShift += 0.010;
}
draw();
It's something like this, but you're gonna have to figure out the last circle's size:
http://jsfiddle.net/rudiedirkx/ufvf62yf/2/
The main logic:
var firstStep = 0, rad = 0, step = 0;
firstStep = step = stepSize();
for ( var i=0; i<30; i++ ) {
draw.radCircle(rad, step);
rad += step;
step = stepSize();
rad += step;
}
stepSize() creates a random rad between Math.PI/48 and Math.PI/48 + Math.PI/36 (no special reason, but it looked good). You can fix that to be the right sizes.
draw.radCircle(rad, step) creates a circle at position rad of size step (also in rad).
step is added twice per iteration: once to step from current circle's center to its edge and once to find the next circle's center
firstStep is important because you have to know where to stop drawing (because the first circle crosses into negative angle)
I haven't figured out how to make the last circle the perfect size yet =)
There's also a bit of magic in draw.radCircle():
var r = rRad * Math.PI/3 * 200 * .95;
The 200 is obviously the big circle's radius
The 95% is because the circle's edge length is slightly longer than the (straight) radius of every circle
I have no idea why Math.PI/3 is that... I figured it had to be Math.PI/2, which is 1 rad, but that didn't work at all. 1/3 for some reason does..... Explain that!
If you want to animate these circle sizes and keep them aligned, you'll have a hard time. They're all random now. In an animation, only the very first iteration can be random, and the rest is a big mess of cache and math =)
I have a canvas in my webpage; I create a new Image data in this canvas then I modify some pixel through myImgData.data[] array. Now I would like to scale this image and make it bigger. I tried by scaling the context but the image remains small. Is it possible to do this?
Thanks
You could draw the imageData to a new canvas, scale the original canvas and then draw the new canvas to the original canvas.
Something like this should work:
var imageData = context.getImageData(0, 0, 100, 100);
var newCanvas = $("<canvas>")
.attr("width", imageData.width)
.attr("height", imageData.height)[0];
newCanvas.getContext("2d").putImageData(imageData, 0, 0);
context.scale(1.5, 1.5);
context.drawImage(newCanvas, 0, 0);
Here's a functioning demo http://jsfiddle.net/Hm2xq/2/.
I needed to do it without the interpolation that putImageData() causes, so I did it by scaling the image data into a new, resized ImageData object. I can't think of any other time I've thought that using 5 nested for loops was a good idea:
function scaleImageData(imageData, scale) {
var scaled = c.createImageData(imageData.width * scale, imageData.height * scale);
for(var row = 0; row < imageData.height; row++) {
for(var col = 0; col < imageData.width; col++) {
var sourcePixel = [
imageData.data[(row * imageData.width + col) * 4 + 0],
imageData.data[(row * imageData.width + col) * 4 + 1],
imageData.data[(row * imageData.width + col) * 4 + 2],
imageData.data[(row * imageData.width + col) * 4 + 3]
];
for(var y = 0; y < scale; y++) {
var destRow = row * scale + y;
for(var x = 0; x < scale; x++) {
var destCol = col * scale + x;
for(var i = 0; i < 4; i++) {
scaled.data[(destRow * scaled.width + destCol) * 4 + i] =
sourcePixel[i];
}
}
}
}
}
return scaled;
}
I hope that at least one other programmer can copy and paste this into their editor while muttering, "There but for the grace of god go I."
I know it's an old subject, but since people like may find it useful, I add my optimization to the code of rodarmor :
function scaleImageData(imageData, scale) {
var scaled = ctx.createImageData(imageData.width * scale, imageData.height * scale);
var subLine = ctx.createImageData(scale, 1).data
for (var row = 0; row < imageData.height; row++) {
for (var col = 0; col < imageData.width; col++) {
var sourcePixel = imageData.data.subarray(
(row * imageData.width + col) * 4,
(row * imageData.width + col) * 4 + 4
);
for (var x = 0; x < scale; x++) subLine.set(sourcePixel, x*4)
for (var y = 0; y < scale; y++) {
var destRow = row * scale + y;
var destCol = col * scale;
scaled.data.set(subLine, (destRow * scaled.width + destCol) * 4)
}
}
}
return scaled;
}
This code uses less loops and runs roughly 30 times faster. For instance, on a 100x zoom of a 100*100 area this codes takes 250 ms while the other takes more than 8 seconds.
You can scale the canvas using the drawImage method.
context = canvas.getContext('2d');
context.drawImage( canvas, 0, 0, 2*canvas.width, 2*canvas.height );
This would scale the image to double the size and render the north-west part of it to the canvas. Scaling is achieved with the third and fourth parameters to the drawImage method, which specify the resulting width and height of the image.
See docs at MDN https://developer.mozilla.org/en-US/docs/DOM/CanvasRenderingContext2D#drawImage%28%29
#Castrohenge's answer works, but as Muhammad Umer points out, it messes up the mouse coordinates on the original canvas after that. If you want to maintain the ability to perform additional scales (for cropping, etc.) then you need to utilize a second canvas (for scaling) and then fetch the image data from the second canvas and put that into the original canvas. Like so:
function scaleImageData(imageData, scale){
var newCanvas = $("<canvas>")
.attr("width", imageData.width)
.attr("height", imageData.height)[0];
newCanvas.getContext("2d").putImageData(imageData, 0, 0);
// Second canvas, for scaling
var scaleCanvas = $("<canvas>")
.attr("width", canvas.width)
.attr("height", canvas.height)[0];
var scaleCtx = scaleCanvas.getContext("2d");
scaleCtx.scale(scale, scale);
scaleCtx.drawImage(newCanvas, 0, 0);
var scaledImageData = scaleCtx.getImageData(0, 0, scaleCanvas.width, scaleCanvas.height);
return scaledImageData;
}
Nowadays, the best way to render a scaled ImageData object is generally to create an ImageBitmap from it.
All modern browsers finally do support it.
This will use a faster path to render the ImageData's content to a bitmap readily available to be painted by drawImage(), theoretically a lot faster than the second best option of putting the ImageData on a secondary <canvas> and redraw that <canvas>.
The main catch-up is that createImageBitmap() is asynchronous*, and thus it may not fit in an animation frame very well.
(async () => {
// here I'll just make some noise in my ImageData
const imagedata = new ImageData(100, 100);
const arr = new Uint32Array(imagedata.data.buffer);
for( let i = 0; i < arr.length; i++ ) {
arr[i] = Math.random() * 0xFFFFFF + 0xFF000000;
}
// now to render it bigger
const bitmap = await createImageBitmap(imagedata);
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false; // keep pixel perfect
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
})();
<canvas width="1000" height="1000"></canvas>
* Technically, only Firefox does implement createImageBitmap(<ImageData>) asynchronously, Chrome and Safari will resolve the returned Promise synchronously, and there, it's safe to use it in an animation frame: https://jsfiddle.net/vraz3xcg/