How to crop an image in P5? - image

I am working on a program in P5, an offshoot of Processing, that allows the user to upload an image, draw a line on top of the image, and then crop everything outside of the drawn shape out of the image.
(The green line jitters around on purpose)
I managed to get the points of the drawn line into an array, as well as make a shape out of these points. However, the cropping of the image is still a problem.
Processing has this functionality in vertex:
(https://processing.org/reference/vertex_.html)
However, I don't believe P5 has this functionality. I really don't want to have to convert the entire sketch into Processing. Is there any way to do this in P5, or to quickly convert this sketch into processing?
// Make a variable to store the start image, as well as the drop image.
var img;
var cropX = [];
var cropY = [];
var pos = 25;
// Make an array for all paths.
var paths = [];
// Make a bool for whether or not I am painting.
var painting = false;
// Int for how long until drawing the next circle
var next = 10;
// Make vars for vectors that determine where the line is drawn.
var current;
var previous;
// Make ints for how much the lines dance around.
var shake = 10;
var minShake = shake * -1;
var maxShake = shake * 1;
// Make an int for the line thickness.
var thickness = 2;
var camera;
var tracing = false;
// Make vars to store the random values for colours into.
var rc1;
var rc2;
var rc3;
// Variable for the framerate.
var fr;
// Variable that disables drawing lines when you didn't upload an image yet.
var tracing = false;
//------------------------------------------------------------------------------------------------
function preload() {
//Load the starting image, and store it in img.
img = loadImage("assets/startscreen.png");
//Load the sound that plays when you export a screenshot.
soundFormats('mp3');
camera = loadSound('assets/camera.mp3');
}
//------------------------------------------------------------------------------------------------
function setup() {
// Set the framerate so the lines don't jump about too quickly.
fr = 20;
// Setup a canvas
var c = createCanvas(1680, 1050);
// Store a random value out of 255 into the random colour values.
rc1 = random(255);
rc2 = random(255);
rc3 = random(255);
// Apply the right framerate
frameRate(fr);
// Add an event named drop, that runs function gotFile when a file is dropped onto the canvas
c.drop(gotFile);
// Store 0,0 vectors in current and previous.
current = createVector(0, 0);
previous = createVector(0, 0);
};
//------------------------------------------------------------------------------------------------
function draw() {
// Colour the background dark grey.
background(200);
// Draw the loaded image at 0,0 coordinates.
image(img, 0, 0);
//------------------------------------------------------------------------------------------------
// Count if I've been painting for longer than the 'next' variable.
// Also check if tracing is enabled (if I've dropped an image or not).
// If these are true I can draw a new line.
if (millis() > next && painting && tracing) {
// Grab mouse position and store it in variables mouseX and mouseY.
current.x = mouseX;
current.y = mouseY;
// Add new particle
paths[paths.length - 1].add(current);
// Update the 'next' variable, to allow itself 200 extra millisecond for drawing the actual line.
next = millis() + 200;
// Move the mouse values used to draw the end of the line
// to a variable used to draw the start of the line,
// so that the line is continuous.
previous.x = current.x;
previous.y = current.y;
append(cropX, current.x);
append(cropY, current.y);
}
// Make an integer called i, with a value of 0.
// Add 1 to i for each item in the array paths.
// Run this once for each item in the array.
// Name each item in the array 'i' while working.
// Display each item in the array.
for (var i = 0; i < paths.length; i++) {
// Update the current object in the array.
paths[i].update();
// Display each item in the array.
paths[i].display();
}
noStroke();
noFill();
beginShape();
for (var i = 0; i < cropX.length; ++i) {
vertex(cropX[i], cropY[i]);
}
endShape(CLOSE);
}
//------------------------------------------------------------------------------------------------
var ready = false;
// Make a function called gotFile, using the variable file.
function gotFile(file) {
// Check if the dropped file is an image file
if (file.type === 'image') {
// Enable drawing lines.
tracing = true;
// if (ready) {
// img.remove();
// }
// Store the dropped image in the container which used to hold the startimage.
img = createImg(file.data).style("opacity: 100; position: absolute; top: -10; right: -10; z-index: 100;draggable=false;");
ready = true;
// Error message in case not an image file.
} else {
println('Not an image file!');
}
}
//------------------------------------------------------------------------------------------------
function mouseWheel(event) {
//event.delta can be +1 or -1 depending
//on the wheel/scroll direction
print(event.delta);
//move the square one pixel up or down
pos += event.delta;
//uncomment to block page scrolling
return false;
}
function mouseDragged() {
return false;
}
// If left mousebutton is pressed down,
function mousePressed() {
if (mouseButton == LEFT) {
// set the variable counting when to place a new line to 0,
next = 0;
// set painting to true,
painting = true;
// store the mouse coordinates in mouseX and mouseY,
previous.x = mouseX;
previous.y = mouseY;
// and add a new Path method to the array.
paths.push(new Path());
}
}
// When mouse is released, set painting to false, which disables any paths being drawn.
function mouseReleased() {
painting = false;
}
//------------------------------------------------------------------------------------------------
// Describe the Path function that should be pushed to the array.
function Path() {
// Create an array inside this function named particles.
this.particles = [];
}
// Add the variable position to the function Path as its function'()' variable.
Path.prototype.add = function(position) {
// Add a new particle to this particle array with a position and hue.
this.particles.push(new Particle(position, this.hue));
}
// Take the Path() function, and and add this command to it.
Path.prototype.update = function() {
// Make an integer called i, with a value of 0.
// Add 1 to i for each item in the array paths.
// Run this once for each item in the array.
// Name each item in the array 'i' while working.
// Display each item in the array.
for (var i = 0; i < this.particles.length; i++) {
this.particles[i].update();
}
}
// Display the Path array.
Path.prototype.display = function() {
// Loop through the array of particles backwards.
for (var i = this.particles.length - 1; i >= 0; i--) {
// Display each of these particles.
this.particles[i].display(this.particles[i + 1]);
}
}
// Particles along the path
function Particle(position, hue) {
// Set the position of Particles to the mouseposition by creating a vector.
this.position = createVector(position.x, position.y);
}
// Constantly update Particle.
Particle.prototype.update = function() {}
// Draw particle and connect it with a line
// Draw a line to another
Particle.prototype.display = function(other) {
stroke(255, 255);
// If we need to draw a line
if (other) {
stroke(rc1, rc2, rc3);
strokeWeight(thickness);
line(this.position.x + random(minShake, maxShake), this.position.y + random(minShake, maxShake), other.position.x + random(minShake, maxShake), other.position.y + random(minShake, maxShake));
}
if (keyIsDown(LEFT_ARROW) && !camera.isPlaying()) {
camera.play();
save('myRemix.jpg');
print(cropX);
print(cropY)
}
}

The vertex() function you pointed out isn't cropping the image.
To crop an image, you probably want to use the PImage.mask() function. This lets you overlay a mask image overtop your original image so that only parts of your original image are visible.
You can also use a PGraphics instance as a mask image, so you can draw whatever mask you want. Here's a small example that creates a PGraphics mask consisting of an ellipse, then masks the original photo with that mask:
PImage photo;
PGraphics maskImage;
void setup() {
size(100, 100);
photo = loadImage("pic.jpg");
maskImage = createGraphics(photo.width, photo.height);
maskImage.beginDraw();
maskImage.background(0);
maskImage.fill(255);
maskImage.ellipseMode(RADIUS);
maskImage.ellipse(photo.width/2, photo.height/2, 500, 500);
maskImage.endDraw();
photo.mask(maskImage);
}
void draw() {
background(255, 0, 0);
image(photo, 0, 0, width, height);
}
Your sketch would use a shape instead of an ellipse, but those are the basics.

Related

Not able to copy paste a section of image in P5.js

I have been working on a project in which I have to select an area of image and then use CTRL+C and CTRL+V to copy and paste the selected area. I have tried creating a buffer using createGraphics() but did not work. I have also tried getting pixels one by one and creating image but that is not working either. Can somebody help? Below is my code.
if (this.isCtrlPressed == true && p5.key == 'v') {
let img = p5.createImage(this.dw, this.dh);
p5.draw = () => {
img.loadPixels();
for (let i = 0; i < parseInt(this.sw); i++) {
for (let j = 0; j < parseInt(this.sh); j++) {
img.set(i, j, p5.get(i, j));
}
}
img.updatePixels();
p5.stroke(255);
p5.image(img, 10, 10);
p5.noFill();
p5.rect(p5.mouseX, p5.mouseY, 10, 10);
return;
}
You're not too far off.
Bare in mind that you can also call get() with width, height arguments, not just the first two (x, y): this will make it easy to copy a section of the image.
The p5.Graphics you get using createGraphics() is handy indeed.
Remember to store the selection rectangle properties (x, y, width, height) to copy the image in the first place.
Here's a very rough sketch based on the CreateImage p5.js example:
/*
* #name Create Image
* #description The createImage() function provides a fresh buffer of pixels to
* play with. This example creates an image gradient.
*/
let img; // Declare variable 'img'.
// copy/paste selection rectangle
let selection = {x:0, y:0, w:0, h:0};
// p5.Image from main image using selection
let clipboard;
// where to paste the clipboard p5.Image
let pasteBuffer;
function setup() {
createCanvas(720, 400);
img = createImage(230, 230);
img.loadPixels();
for (let x = 0; x < img.width; x++) {
for (let y = 0; y < img.height; y++) {
let a = map(y, 0, img.height, 255, 0);
img.set(x, y, [0, 153, 204, a]);
}
}
img.updatePixels();
// selection drawing style
noFill();
stroke(255);
// setup buffer to paste into
pasteBuffer = createGraphics(width, height);
}
function draw() {
background(0);
// render original image
image(img, 0, 0);
// render pasted graphics
image(pasteBuffer, 0, 0);
// render selection rectangle
rect(selection.x, selection.y, selection.w, selection.h);
}
function mousePressed(){
// store selection start
selection.x = mouseX;
selection.y = mouseY;
}
function mouseDragged(){
// update selection dimension as the difference between the current mouse coordinates and the previous ones (selection x, y)
selection.w = mouseX - selection.x;
selection.h = mouseY - selection.y;
}
function keyPressed(keyEvent){
if (key == 'c') {
console.log("copy image", selection);
// use get() to "clone" a subsection of the main image
clipboard = img.get(selection.x, selection.y, selection.w, selection.h);
}
if (key == 'v') {
console.log("paste image");
if(clipboard){
// simply render the clipboard image
pasteBuffer.image(clipboard, mouseX, mouseY);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
Note the above isn't perfect (e.g. you need to use abs() to handle making selections from right to left, not just left to right), but hopefully it illustrates the usage of a selection rectangle, copying pixels and "pasting"/rendering them into a buffer.
Speaking of copying pixels, an alternative option could be using a p5.Image (using createImage() instead of createGraphics()) and copy() (instead of buffer.image()) (and you'd use the selection rectangle properties to know from where to where to paste pixels).

Processing 3.0 Question - struggling to implement some code

wondering if anyone can help with this. I have to write some ode using processing 3.0 for college, basically creating a series of circles that appear randomly and change colours etc. Ive got the circles to appear, and they change location on mouse click.
What Im struggling with is, its asked me to have the circles change colour when the mouse button is pressed, where circles to the right are blue and circles to the left of the mouse pointer are yellow? I have no idea how to implement that at all.
Here's what I have so far, any help would be hugely appreciated:
//declaring the variables
float[] circleXs = new float[10];
float[] circleYs = new float[10];
float[] circleSizes = new float[10];
color[] circleColors = new color[10];
void setup() {
size(600, 600);
createCircles();
}
//creation of showCricles function
void draw() {
background(0);
showCircles();
}
//creation of circles of random size greater than 10 but less than 50 - also of white background colour
void createCircles() {
for (int i = 0; i < circleXs.length; i++) {
circleXs[i] = random(width);
circleYs[i] = random(height);
circleSizes[i] = random(10, 50);
circleColors[i] = color(255,255,255);
}
}
void showCircles() {
for (int i = 0; i < circleXs.length; i++) {
fill(circleColors[i]);
circle(circleXs[i], circleYs[i], circleSizes[i]);
}
}
//creating new circles on mouse click
void mouseClicked() {
createCircles();
}
It's not very complicated, you just miss some of the basics. Here are 2 things you have to know to do what you want to do:
You can use mouseX or mouseY to compare coordinates with the current mouse pointer's position.
This would be way cleaner using class, but I am guessing that you are not quite there as you're using a couple arrays to store coordinates instead. But here's the thing with that method: the array's index always refer to the same object. Here your objects are circles, so every array's index n refers to the same circle. If you find a circle which x coordinate is leftward compared to the mouse pointer, you can change that circle's color by modifying the item at the same index but in the circleColors array.
So I added a couple lines to your mouseClicked() method which demonstrate what I just said. Here they are:
void mouseClicked() {
createCircles();
// here is the part that I added
// for each circle's X coordinate:
for( int i = 0; i < circleXs.length; i++) {
// if the X coordinate of the mouse is lower than the circle's...
if( mouseX > circleXs[i]) {
// then set it's color to yellow
circleColors[i] = color(255, 255, 0);
} else {
// else set it's color to blue
circleColors[i] = color(0, 0, 255);
}
}
}
It should show what you described, or close enough for you to clear the gap.
Hope it helps. Have fun!

How do I create a line that recedes as you draw more in p5.js?

Currently working on a site that features some line drawing while hovering. How can I make the line recede naturally as you draw more? Right now I've figured out how to draw a continual line.
var canvas;
var button;
function windowResized() {
console.log('resized');
resizeCanvas(windowWidth, windowHeight);
}
function setup () {
canvas = createCanvas(windowWidth, windowHeight);
canvas.position(0,0);
canvas.style('z-index', '-1')
background(175);
// button = createButton("Start Your Walk");
}
function draw () {
strokeWeight(4);
console.log('button')
line(pmouseX, pmouseY, mouseX, mouseY)
}
You may store the values of your mouse position in an array and then draw the points of the array in order. When the array is updated, if it is full, you will have to erase the last point of the array, move all the points one position backwards and add the new point. The following would be an example code. I recomend you to consult this page for documentation and also the p5 reference.
var mousePositions = [];
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
v = createVector(mouseX, mouseY);
mousePositions.push(v);
noFill();
beginShape();
for(var i = 0; i < mousePositions.length; i++){
vertex(mousePositions[i].x, mousePositions[i].y);
}
endShape();
if(mousePositions.length > 25){
mousePositions.shift();
}
}

In p5.js, is it possible to load a gif in the setup or preload function, and then use that gif multiple times elsewhere?

I have a class (lets call it "Enemies"), I want them to attack me when close enough (It will display an animated gif, that looks like a bite).
I've gotten all of this to work, except the only way I could figure it out, was by putting loadImage("attack.gif"), in the class. That got laggy really quick, since every time an enemy would spawn, it would have to reload that gif.
I've tried to use a loaded gif from the setup(), in my class, but all of their attacks were in sync.
Is there another way to do this?
You can preload the gif (or gifs) and store them in a array which you can reuse later to draw in multiple places (e.g. multiple spawned enemies in your case).
Here's a basic example that demonstrates:
loading images into an array
spawning objects on screen (re)using the loaded images
let images;
let enemies = [];
function preload(){
// load images and store them in an array
images = [
loadImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAPFBMVEX///8AAADf399fX19/f38/Pz+/v78fHx+fn58XFxeHh4cPDw+np6fX19fHx8evr6+3t7dvb2/39/dPT08jn1NmAAAA+UlEQVR4nO3ZSRKCMBBAUQnzPHj/u7qwOwtS0QhBwfpviaH9bDTG2w0AAAAAgNNLE2HC7zF6T0oAAQT8Q0CTC1M8Df61gywxek8TIcAq5anKXUsIIICACwaMmWhlept5OUvGCAH2o3iLqN8FBBBAwJcDJt3U1Dqsk+1O538/Z0mtU6aPAypnuu4JjfNKwJKKAAIIuEzAi+lxvPuFSQABBJwnoHY2JFu4U4ID7GYqzpYs+KiLAAIIOHPA7D+aWZsPCdh1Wk4AAQRcJ2CRQ5ai1yu9XrnnK/YfqWb9SuqfsoQ/xpoehSf+x1PHHtcTQAABPwsAAAAAAOAYD7+UFrmL/czUAAAAAElFTkSuQmCC"), loadImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAclBMVEX///8AAAAMDAwUFBRtbW20tLTKysrr6+s0NDSMjIzo6OgrKytzc3Py8vJKSkolJSWSkpIODg5jY2OlpaW7u7ubm5urq6tEREQaGhr29vZRUVHg4ODDw8Pc3Nw/Pz9VVVWAgIDU1NQ3NzdcXFx6enovLy+wzkUiAAACjElEQVR4nO2a63qCMAxAlU5QEEEUuSMivP8rjptS2iid0qmfOT/XfM0BHGlLZjMEQRAE+ShMXQBTXn79aAgQ6tIE4u1cgG0sTWAvJrBHARRAART4bIFEvUm8Fsk/nxe3p1BH60RskJsIXX+FcnsKkp5GBAQv8nGyEQFHtoCDAiiAAihwP38gXyAAE592LbYhW6B0u1QJnX9nKS2y09d0mTIqv5n+R2KGH0pAW6IACqAACqDAewtYGwaPQGHEY+OsaQRK1WTJoLiMC1PLSQToyI4FULeVBR/3M4nA+nGBeztNFEABFEABcQFgM7WDBHZc2N2NnrgAcVYMWQ7F5Rkb54BF688CkkABFEABFEABWsDcvEBgUGRjm8YdAthtHTbItR2gQp7Z8kQh3mWw4if2ND5M83jPaVoZAIEN0CcCPEciTSCFBPg7IFEAeATAcas8gRz6DfDLJKLKEli+WqAEPgEDbzN5AgYgoPOnEfIELEiA/9YhT8CHBPgTIYkC0TsK+FzYdh8EgWma2lMNdsH5OqHihS1r6E1YhOGxxuurUn4+hhsvTcOxtgUItVg3OH3jRB4FLZrbDfbYwawb1IHy6T4gYPPTeJcL3/Obvv4fg7plV+yJBYDt+TsJHL9eIESBFwtAi/wvE+BXp1MJXFfjQF8fJXD2LYYSOEMcoB449JXCYl3f6JpD2EFqoaxHbLu51hRFU6tp/xIldEGtnho7HzmcFixU00WgsoP3692iKopVVczzZVka1S3xfVJQw8B6Ejj3fAbg49HI9hwFUAAFUOB/BYDDDeUwqUDBCwyO6dzSal7QPcY027oL6pJQk9fJjOEVdjVKjxqSJAE2f0+h13M2XCok3NiIIMgX8guBhVuAILp8rwAAAABJRU5ErkJggg=="),
];
}
function setup(){
createCanvas(600, 600);
imageMode(CENTER);
}
function draw(){
background(255);
// update + draw enemies
for(let i = 0; i < enemies.length; i++){
enemies[i].update();
}
text("click to spawn", 10, 15);
}
function mousePressed(){
// pick a random image
let randomImage = images[int(random(images.length))];
// make a new enemy
enemies.push(new Enemy(randomImage, mouseX, mouseY));
}
class Enemy {
constructor(skin, x, y) {
this.skin = skin;
this.position = createVector(x, y);
this.velocity = createVector(random(-1,1), random(-1,1));
}
update(){
// add velocity
this.position.add(this.velocity);
// check borders and flip direction (roughly)
if(this.position.x < 0 || this.position.x > width ||
this.position.y < 0 || this.position.y > height){
this.velocity.mult(-1);
}
// render
push();
blendMode(MULTIPLY);
image(this.skin, this.position.x, this.position.y);
pop();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.0/p5.min.js"></script>
In the example above I'm using base64 encoded images to avoid CORS issues, however you should be fine loading your custom images instead.
The idea is to store the images globally so you can re-use them later when instantiating new enemies. (If you have a couple of images individual variables per image should do, otherwise an array will make it easier to manage)
Notice in Enemy constructor simply receives a reference to the previously loaded image:
// when declared
constructor(skin, x, y) {
this.skin = skin;
// when intantiated
new Enemy(randomImage, mouseX, mouseY)
The only thing left to do is to render the image as needed:
image(this.skin, this.position.x, this.position.y);
Update Based on the comment and shared code the is that each enemy shares the same gif which updates at the same rate with the same frame number in sync.
One option would be to fill the images array with multiple copies of the same image, essentially reloading the gif once per enemy, though that seems wasteful.
Unfortunately p5.Image.get() returns a snapshot of the current frame and there is no magic function to clone a loaded gif, however the undocummented gifProperties holds the necessary data to manually do so:
let images;
let enemies = [];
function preload(){
// load images and store them in an array
images = [
loadImage("data:image/gif;base64,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"),
];
}
function setup(){
createCanvas(600, 600);
imageMode(CENTER);
}
function draw(){
background(255);
// update + draw enemies
for(let i = 0; i < enemies.length; i++){
enemies[i].update();
}
text("click to spawn", 10, 15);
}
function mousePressed(){
// pick a random image
let randomImage = images[int(random(images.length))];
// offset the start frame of each enemy by one
let startFrame = enemies.length % randomImage.numFrames();
// make a new enemy
enemies.push(new Enemy(cloneGif(randomImage,startFrame), mouseX, mouseY));
}
function cloneGif(gif, startFrame){
let gifClone = gif.get();
// access original gif properties
gp = gif.gifProperties;
// make a new object for the clone
gifClone.gifProperties = {
displayIndex: gp.displayIndex,
// we still point to the original array of frames
frames: gp.frames,
lastChangeTime: gp.lastChangeTime,
loopCount: gp.loopCount,
loopLimit: gp.loopLimit,
numFrames: gp.numFrames,
playing: gp.playing,
timeDisplayed: gp.timeDisplayed
};
// optional tweak the start frame
gifClone.setFrame(startFrame);
return gifClone;
}
class Enemy {
constructor(skin, x, y) {
this.skin = skin;
this.x = x;
this.y = y;
}
update(){
image(this.skin, this.x, this.y);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.0/p5.min.js"></script>
The cloneGif makes a new object (which means new start frame, etc.), however it should point to the original gif's frames (which hold the pixels / ImageData)
Now each enemy starts at a new frame.
I noticed that changing the delay seems to affect all enemies: once a delay is set to one it seems the underlying imagedata is copied at the same rate.
If you need to have different delay times you can manage still access the same gif frame ImageData, but rather than relying on p5.Image to control the gif playback (e.g. setFrame() / delay()) you'd need manually manage that and render to p5's canvas:
let images;
let enemies = [];
function preload(){
// load images and store them in an array
images = [
loadImage("data:image/gif;base64,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"),
];
}
function setup(){
createCanvas(600, 600);
imageMode(CENTER);
}
function draw(){
background(255);
// update + draw enemies
for(let i = 0; i < enemies.length; i++){
enemies[i].update();
}
text("click to spawn", 10, 15);
}
function mousePressed(){
// pick a random image
let randomImage = images[int(random(images.length))];
// make a new enemy
let enemy = new Enemy({frames: randomImage.gifProperties.frames, gifWidth: randomImage.width, gifH: randomImage.height}, mouseX, mouseY);
// pick a random start frame
enemy.currentFrame = int(random(images.length));
// pick a random per gif frame delay (e.g. the larger the number the slower the gif will play)
enemy.frameDelay = int(random(40, 240));
enemies.push(enemy);
}
class Enemy {
constructor(gifData, x, y) {
this.frames = gifData.frames;
this.offX = -int(gifData.gifWidth * 0.5);
this.offY = -int(gifData.gifHeight * 0.5);
this.currentFrame = 0;
this.numFrames = this.frames.length;
this.frameDelay = 100;
this.lastFrameUpdate = millis();
this.x = x;
this.y = y;
}
update(){
let millisNow = millis();
// increment frame
if(millisNow - this.lastFrameUpdate >= this.frameDelay){
this.currentFrame = (this.currentFrame + 1) % this.numFrames;
this.lastFrameUpdate = millisNow;
}
// render directly to p5's canvas context
drawingContext.putImageData(this.frames[this.currentFrame].image, this.x + this.offX, this.y + this.offY);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.0/p5.min.js"></script>
Also notice the p5.Image does something nice about transparency (even when the original gif is missing it): that's something you'd need to manually address if going this lower level route.

How do I save a p5.js canvas as a very large PNG?

I know how to save the canvas using p5.js. However I want to save the canvas as a very large png (for example 8000x8000) so that I can use it in Photoshop and scale down the image to the appropriate size. Is there a simple way of doing this besides creating a new canvas behind the scenes that is too large for the browser window?
You could use the createGraphics() function to create an off-screen buffer. Then you can draw it to the screen using the image() function, or you can call its save() function to store it as a file. Here's an example:
let pg;
function setup() {
createCanvas(400, 400);
pg = createGraphics(4000, 4000);
pg.background(32);
}
function draw() {
pg.ellipse(random(pg.width), random(pg.height), 100, 100);
image(pg, 0, 0, width, height);
}
function mousePressed(){
pg.save("pg.png");
}
Draw everything into a pGraphics object.
Normally you draw this "output" just as an image to the canvas.
But if you want to export a high-res version of it, you scale it up first.
let scaleOutput = 1;
let output;
let canvas;
// setup
function setup() {
// other stuff...
output = createGraphics(1000, 640);
canvas = createCanvas(1000, 640);
}
// the draw loop
function draw() {
// Clear Canvas
background(255);
output.clear();
// Set scale
output.push();
output.scale(scaleOutput);
// Draw to your output here...
output.pop();
// Show on canvas
image(output, 0, 0);
}
// Scale up graphics before exporting
function exportHighRes() {
// HighRes Export
scaleOutput = 5;
output = createGraphics(scaleOutput * 1000, scaleOutput * 640);
draw();
save(output, "filename", 'png');
// Reset Default
scaleOutput = 1;
output = createGraphics(1000, 640);
draw();
}
// Export when key is pressed
function keyReleased() {
if (key == 'e' || key == 'E') exportHighRes();
}

Resources