'context.putImageData() Not working in React
Im trying to create a drawing application in react, its working for the most part. But when i try add an undo button it doesnt work.
I try make the undo button with this finishDrawing()
function, where i use getImageData and store it in an array called restore_array
const finishDrawing = () => {
//console.log(contextRef.current)
contextRef.current.closePath()
setIsDrawing(false)
//restore_array.push(contextRef.current.getImageData(0, 0,400,256+80))
setIndex_array(index_array + 1)
setRestore_Array((prevState) => { return [...prevState, contextRef.current.getImageData(0, 0,400,256+80)]})
console.log(restore_array, restore_array.length)
}
then when a Undo button is clicked i try to reload this image in the restore_array
with a useEffect
which should re-render the entree canvas with the previous image
function undoLast() {
console.log(restore_array[index_array], index_array, restore_array)
if (index_array > -1) {
setIndex_array(index_array-1)
restore_array.pop()
setLines(!lines)
}
//contextRef.current.putImageData(restore_array[index_array-1], 0, 0)
}
and
useEffect(() => {
console.log('use effect 3c')
console.log(restore_array)
const canvas = canvasRef.current;
const context = canvas.getContext("2d")
context.lineWidth = thick
context.lineCap = "round"
context.strokeStyle = colour
if (restore_array.length === 0) {
console.log('empty')
return
}
//context.clearRect(0,0,400,256+80)
//context.fillRect(0,0,400,256+80)
context.putImageData(restore_array[index_array], 0, 0, 0, 0, 400, 256+80)
contextRef.current = context
}, [lines])
Does anyone know why my putImageData
isnt working in this case?
Heres my entire code for reference
import React, { useEffect, useRef, useState } from "react";
function DrawCanvas() {
const canvasRef = useRef(null)
const contextRef = useRef(null)
const [isDrawing, setIsDrawing] = useState(false)
const [colour, setColour] = useState('black')
const [prcolour, setPrColour] = useState(null)
const [thick, setThick] = useState(3)
const [image, setImage] = useState(null)
const [restore_array, setRestore_Array] = useState([])
const [index_array, setIndex_array] = useState(-1)
const [clear_, setClear_] = useState(true)
const [lines, setLines] = useState(true)
//let restore_array = []
useEffect(() => {
const canvas = canvasRef.current;
canvas.width = window.innerWidth/1.5 ;
canvas.height = window.innerHeight/1.5 ;
canvas.style.width = `${window.innerWidth/3}px`;
canvas.style.height = `${window.innerHeight/3}px`;
const context = canvas.getContext("2d")
context.scale(2,2)
context.lineCap = "round"
context.lineWidth = 3
context.strokeStyle = "black"
//const img = DogImage
context.fillStyle = 'gray'
context.fillRect(0,0,400,256+80)
contextRef.current = context
}, [])
useEffect(() => {
console.log('use effect 1a')
const canvas = canvasRef.current;
const context = canvas.getContext("2d")
context.lineWidth = thick
context.lineCap = "round"
context.strokeStyle = colour
contextRef.current = context
document.getElementById(colour).style.borderColor = 'gray'
if(prcolour === null) {
return
}
document.getElementById(prcolour).style.borderColor = 'black'
document.getElementById(prcolour).style.borderWidth = '3px'
}, [colour])
useEffect(() => {
console.log('use effect 2b')
const canvas = canvasRef.current;
const context = canvas.getContext("2d")
context.lineWidth = thick
context.lineCap = "round"
context.strokeStyle = colour
const Dogimage = new Image();
Dogimage.src = '../../images/dog-without-labels.png'
Dogimage.onLoad = () => {
console.log('load image')
context.current.drawImage(Dogimage, 0, 0,400,256+80);
}
contextRef.current = context
}, [thick])
useEffect(() => {
console.log('use effect 3c')
console.log(restore_array)
const canvas = canvasRef.current;
const context = canvas.getContext("2d")
context.lineWidth = thick
context.lineCap = "round"
context.strokeStyle = colour
if (restore_array.length === 0) {
console.log('empty')
return
}
//context.clearRect(0,0,400,256+80)
//context.fillRect(0,0,400,256+80)
context.putImageData(restore_array[index_array], 0, 0, 0, 0, 400, 256+80)
contextRef.current = context
}, [lines])
useEffect(() => {
console.log('use effect 4d')
const canvas = canvasRef.current;
const context = canvas.getContext("2d")
//contextRef.fillStyle = 'gray'
context.lineWidth = thick
context.lineCap = "round"
context.strokeStyle = colour
context.clearRect(0,0,400,256+80)
context.fillRect(0,0,400,256+80)
contextRef.current = context
}, [clear_])
function undoLast() {
console.log(restore_array[index_array], index_array, restore_array)
if (index_array > -1) {
setIndex_array(index_array-1)
restore_array.pop()
setLines(!lines)
}
//contextRef.current.putImageData(restore_array[index_array-1], 0, 0)
}
const clearCanvas = () => {
setClear_(!clear_)
}
const startDrawing = ({nativeEvent}) => {
const {offsetX, offsetY} = nativeEvent
contextRef.current.beginPath()
contextRef.current.moveTo(offsetX, offsetY)
setIsDrawing(true)
}
const finishDrawing = () => {
//console.log(contextRef.current)
contextRef.current.closePath()
setIsDrawing(false)
//restore_array.push(contextRef.current.getImageData(0, 0,400,256+80))
setIndex_array(index_array + 1)
setRestore_Array((prevState) => { return [...prevState, contextRef.current.getImageData(0, 0,400,256+80)]})
console.log(restore_array, restore_array.length)
}
const draw = ({nativeEvent}) => {
if (!isDrawing) {
return
}
const {offsetX, offsetY} = nativeEvent;
contextRef.current.lineTo(offsetX, offsetY)
contextRef.current.stroke()
}
const colourChange = (value) => {
setPrColour(colour)
setColour(value)
}
const thickChange = (event) => {
setThick(event)
}
return (
<div>
<div className="left">
<canvas className="canvas" onMouseDown={startDrawing}
onMouseUp={finishDrawing}
onMouseMove={draw}
ref={canvasRef}/>
</div>
<div className="right">
<h5><dt>Chart Draw</dt></h5>
<p>Use your finger or your mouse to draw directly on the chart opposite.</p>
<button className="draw">Draw</button>
<button className="select">Select</button>
<p><dt>Line thickness: {thick} </dt> </p>
<input className="slider" type="range" min="1" max="19" value={thick} class="slider" id="myRange" step="1" onChange={(event)=>thickChange(event.target.value)}></input>
<button className='red' id='red'onClick={() => colourChange('red')}> </button>
<button className='black' id='black' onClick={() => colourChange('black')}> </button>
<button className='yellow' id='rgba(204,153,0,255)' onClick={() => colourChange('rgba(204,153,0,255)')}> </button>
<button className='blue' id='rgba(1,102,255,255)' onClick={() => colourChange('rgba(1,102,255,255)')}> </button>
<button className="undoButton" onClick={()=>undoLast()}> <dt> Undo/Back a step</dt></button>
<button className="undoButton" onClick={()=>clearCanvas()}> <dt> Clear canvas</dt></button>
<button className="savecloseButton"> <dt>Save and Close</dt></button>
</div>
</div>
)
}
export default DrawCanvas;
Solution 1:[1]
Your code works "OK" for me...
I think it's obvious that your putImageData
in function undoLast is commented.
I'm assuming that was intentional, not the actual problem.
Yes only part is getting the undo.
That is because you have hardcoded values in the getImageData:
setRestore_Array((prevState) => {
return [...prevState, contextRef.current.getImageData(0, 0,400,256+80)]
})
That only gets a portion of the canvas, so only that portion is getting restored...
if you needed the entire canvas, change that to use the canvas dimensions.
Something like:
setRestore_Array((prevState) => {
return [...prevState, contextRef.current.getImageData(0, 0, canvasRef.current.width, canvasRef.current.height)]
})
Also you should look at what @Kaiido mentioned, he has a good point:
storing an Array of drawing commands will cause your memory to explode
For your approach using image data my recommendation is to limit the undo to a fix amount, like 10 and discard older, that will guarantee a cap on memory consumption and prevent any out of memory errors.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |