Reputation: 1965
I have posted a question regarding moving image in p5js which is answered in this question. I have another problem with p5js when I try to upload an image in React input field.
I take input as
<input
type="file"
name="file"
id="file"
onChange={(e) => setSelectedFile(e.target.files[0])}
/>
And use it in draw
function like following:
if (selectedFile != null) {
const url = URL.createObjectURL(selectedFile);
backgroundImage = p5.loadImage(url);
}
The strange behavior is that it does not change the image and gives blank screen canvas. I want to upload image as input element, not createFileInput().
I have uploaded the complete code into StackBlitz.
I don't want to copy the code here because it is already mentioned in previous question and StackBlitz.
Any help is appreciated.
Upvotes: 3
Views: 343
Reputation: 51867
There are two main things you're overlooking:
onChange={(e) => setSelectedFile(e.target.files[0])}
, files[0] points to data about the selected file (e.g. filename, how large is it (in bytes), etc., but it's not the actual file. const url = URL.createObjectURL(selectedFile);
would simply point to the stackblitz url where your project lives and passing that loadImage()
will result in a 1x1 pixel image. You need to use FileReader
to load bytes from files[0]
Here's a tweaked version of your code:
import React, { useState } from 'react';
import Sketch from 'react-p5';
import './style.css';
export default function App() {
let backgroundImage;
let dragging = false; // Is the object being dragged?
let rollover = false; // Is the mouse over the ellipse?
let x, y, w, h; // Location and size
let offsetX, offsetY; // Mouseclick offset
let theP5Reference;
const setup = (p5, parentRef) => {
p5.createCanvas(1000, 500).parent(parentRef);
// Starting location
x = 350;
y = 50;
const url =
'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png';
backgroundImage = p5.loadImage(url);
// Dimensions
w = 700;
h = 700;
// keep a reference to p5 for reuse outside of this function
theP5Reference = p5;
};
const draw = (p5) => {
p5.background(233);
if (
p5.mouseX > x &&
p5.mouseX < x + w &&
p5.mouseY > y &&
p5.mouseY < y + h
) {
rollover = true;
} else {
rollover = false;
}
// Adjust location if being dragged
if (dragging) {
x = p5.mouseX + offsetX;
y = p5.mouseY + offsetY;
}
p5.image(backgroundImage, x, y);
drawMaskOverlay(p5);
};
const drawMaskOverlay = (p5) => {
p5.fill(255);
p5.noStroke();
p5.beginShape();
// CW vertex winding
p5.vertex(0, 0);
p5.vertex(p5.width, 0);
p5.vertex(p5.width, p5.height);
p5.vertex(0, p5.height);
// cutout contour CCW
p5.beginContour();
p5.vertex(400, 100);
p5.vertex(400, 400);
p5.vertex(600, 400);
p5.vertex(600, 100);
p5.endContour();
p5.endShape();
};
const mousePressed = (p5) => {
if (
p5.mouseX > x &&
p5.mouseX < x + w &&
p5.mouseY > y &&
p5.mouseY < y + h
) {
dragging = true;
offsetX = x - p5.mouseX;
offsetY = y - p5.mouseY;
}
};
const mouseReleased = (p5) => {
// Quit dragging
dragging = false;
};
const onFileSelected = (e) => {
const files = e.target.files;
if (!files || !files[0]) {
console.log('no files selecting, early exit');
return;
}
// create a file reader to load the image (and get base64 data)
const reader = new FileReader();
reader.addEventListener('load', function (evt) {
// event.target.result is the base64 image
// use the references to p5 from setup()
backgroundImage = theP5Reference.loadImage(evt.target.result);
});
// trigger the load / base64 conversion (when ready will trigger the event handler above)
reader.readAsDataURL(files[0]);
};
return (
<div className="App">
<div>
<h1>Select an image</h1>
<input type="file" name="file" id="file" onChange={onFileSelected} />
<Sketch
setup={setup}
draw={draw}
mouseReleased={mouseReleased}
mousePressed={mousePressed}
/>
</div>
</div>
);
}
The key elements are these:
react-p5
gives you a reference to p5 (the general library, not your actual sketch) in methods such as setup()
(which is a good candidate as it triggers only once, at the start). You can keep a reference to p5
in a separate variable in the outer scope (so it's visible from outside setup()
) (theP5Reference
in the snippet above).<input>
change handler (onFileSelected
) is not inlined, as within the App
's scope.onFileSelected
a FileReader
is instantiated, the load is triggered via readAsDataURL
and in the load
handler, the previously stored theP5Reference
reference to p5
is reused to simply call loadImage()
on. Because the handler is within App
's scope, backgroundImage
is already visible.As an excercise, I suggest you read more react specific scope, look at react-p5's source code and see if you can make use of this.sketch
/ props, etc. to access p5
from App without keeping storing a reference from setup()
.
Upvotes: 2