I'm trying to implement this Stack Overflow questions' top answer in react-konva, but stuck on this line of code:
ctx.drawImage(canvas,
blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height,
blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height
);
How do we create Image component with 9 arguments?
Which canvas element do we add to the second Image component and how?
// draft code
import useImage from 'use-image';
const [image] = useImage(url, "anonymous");
<Stage width={width} height={height}>
<Layer>
<Image image={image} width={width} height={height}></Image>
// second Image here with the blur?
<Rect fill="rgba(255,255,255,0.2)" x={80} y={80} width={200} height={200} />
</Layer>
</Stage>
You don't need to use <Rect> component from the initial question, as that you need to just create canvas element and use it for <Image> component.
There are many ways to do that. This is example how you can use hooks to make such canvas:
import React, { Component } from "react";
import { render } from "react-dom";
import { Stage, Layer, Image } from "react-konva";
import useImage from "use-image";
const useCanvas = () => {
const [image] = useImage(
"https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg"
);
const [canvas, setCanvas] = React.useState(null);
React.useEffect(() => {
// do this only when image is loaded
if (!image) {
return;
}
var blurredRect = {
x: 80,
y: 80,
height: 200,
width: 200,
spread: 10
};
const canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = image.width / 2;
canvas.height = image.height / 2;
// first pass draw everything
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
// next drawings will be blurred
ctx.filter = "blur(" + blurredRect.spread + "px)";
// draw the canvas over itself, cropping to our required rect
ctx.drawImage(
canvas,
blurredRect.x,
blurredRect.y,
blurredRect.width,
blurredRect.height,
blurredRect.x,
blurredRect.y,
blurredRect.width,
blurredRect.height
);
// draw the coloring (white-ish) layer, without blur
ctx.filter = "none"; // remove filter
ctx.fillStyle = "rgba(255,255,255,0.2)";
ctx.fillRect(
blurredRect.x,
blurredRect.y,
blurredRect.width,
blurredRect.height
);
setCanvas(canvas);
}, [image]);
return canvas;
};
const App = () => {
const canvas = useCanvas();
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Image image={canvas} />
</Layer>
</Stage>
);
};
render(<App />, document.getElementById("root"));
https://codesandbox.io/s/react-konva-canvas-for-image-ypfmj
Related
I have this easy animation but I have issue. Everytime I update something on page (for example r of arc) animation become faster and faster after each update or after I saved changes in code.
Here is my code:
import React, { useEffect, useRef } from "react";
const CanvasPage = () => {
const canvasRef = useRef(null);
const x = useRef(0);
const r = 50
const v = useRef(1);
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext("2d");
//animation function
const render = () => {
window.requestAnimationFrame(render);
//bouncing left and right
x.current = x.current + v.current;
if(x.current > canvas.width){
v.current = -1;
}
if(x.current <= 0){
v.current = 1;
}
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.arc(x.current, 75, r, 0, 2 * Math.PI);
context.stroke();
};
render();
}, []);
return (
<div className="Canvas">
<h1>Canvas page</h1>
<canvas id="canvas" ref={canvasRef} height="500px" width="500px" />
</div>
)
}
export default CanvasPage;
I have tried all topics regarding with this issue sutch a cancelAnimationFrame() but it did not work.
Can some one help me please?
Thank you very much.
It is most likely because the render function is being triggered on each load. And since requestAnimationFrame call is not being cancelled in your code, previous animation function will keep being triggered.
Below fix may solve your issue:
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext("2d");
const timerIdHolder = {timerId: null};
//animation function
const render = () => {
// let's store the last timerId in our ref object
timerIdHolder.timerId = window.requestAnimationFrame(render);
//bouncing left and right
x.current = x.current + v.current;
if(x.current > canvas.width){
v.current = -1;
}
if(x.current <= 0){
v.current = 1;
}
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.arc(x.current, 75, r, 0, 2 * Math.PI);
context.stroke();
};
render();
// let's return a unmount callback
return () => cancelAnimationFrame(timerIdHolder.timerId);
}, []);
I have a very specific question regarding Three Fiber. When you click on download, it creates a base64 using toDataURL which you can then download. The image has the height and width of the canvas and the canvas has the height and width of the browser window. If the browser window is 1024x768 the image has the same size.
Is it possible that no matter what the height and width of the canvas is, that the image has the height and width of 1920x1080 pixels? I have no idea how to implement this.
export default function Home() {
const canvas = useRef()
const [downloadDatei, setDownloadDatei] = useState('')
const downloadCanvas = () => {
const download = canvas.current.toDataURL('image/jpeg', );
setDownloadDatei(download)
}
return (
<div className="mitte">
<section className="canvasSection">
<Canvas
ref={canvas}
className="canvas"
shadows
linear
camera={{ position: [10, 0, 80], fov: 45 }}
>
<Suspense fallback={false}>
<Content />
</Suspense>
</Canvas>
</section>
<button >
<a href={downloadDatei} download='test.jpg' onClick={() => downloadCanvas()}>
Download
</a>
</button>
</div>
)
}
The .toDataUrl() method of a HTMLCanvasElement object returns the canvas as it is thus you can not directly change it's size.
What you can do instead is drawing the contents of your canvas to a temporary <canvas> element the size of your desired resolution e.g. 1920x1080.
Afterwards execute .toDataUrl on the temporary canvas.
Here's an example:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "green";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.lineWidth = 10;
ctx.arc(canvas.width / 2, canvas.height / 2, 50, 0, 2 * Math.PI);
ctx.stroke();
function downloadCanvas() {
let tempCanvas = document.createElement("canvas");
tempCanvas.width = 1920;
tempCanvas.height = 1080;
let tempContext = tempCanvas.getContext("2d");
tempContext.drawImage(canvas, 0, 0, tempCanvas.width, tempCanvas.height)
const download = tempCanvas.toDataURL('image/jpeg');
document.querySelectorAll("a")[0].href = download;
}
<canvas id="canvas" width="320" height="180"></canvas><br>
Download
I try to build mini paint in browser with using typescript + styled-components + react, however Canvas calculates position of mouse wrongly when I try to draw something. Thing is that it happens only when I use canvas which is located inside CanvasContainer, If I just write tag <canvas ... /> inside ImageEditor then position is counted correctly. Does anyone has any idea why does it happen and how to fix it? As I don't really understand this behaviour.
export default function ImageEditorContainer() {
const CANVAS_REF = useRef<any>(null);
const isDrawing = useRef(false);
function startDrawing(event: MouseEvent) {
isDrawing.current = true;
const canvas = CANVAS_REF.current;
const context = canvas.getContext('2d');
console.log('STARt');
context.beginPath();
context.moveTo(event.clientX - canvas.offsetLeft, event.clientY - canvas.offsetTop);
event.preventDefault();
}
function draw(event: MouseEvent) {
if (isDrawing.current) {
const canvas = CANVAS_REF.current;
const context = canvas.getContext('2d');
console.log('Draw');
context.lineTo(event.clientX - canvas.offsetLeft, event.clientY - canvas.offsetTop);
context.strokeStyle = 'black';
context.lineWidth = '3px';
context.lineCap = 'round';
context.lineJoin = 'round';
context.stroke();
}
event.preventDefault();
}
function stopDrawing(event: MouseEvent) {
if (isDrawing.current) {
const canvas = CANVAS_REF.current;
const context = canvas.getContext('2d');
console.log('STOP');
context.stroke();
context.closePath();
isDrawing.current = false;
}
event.preventDefault();
}
useEffect(() => {
const canvas = CANVAS_REF.current;
if (canvas) {
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
}
}, []);
return (
<>
....
<CanvasContainer CANVAS_REF={CANVAS_REF} ... />
</>
);
}
export default function CanvasContainer({ ...restProps }: TCanvasContent) {
return (
<Canvas.Container>
<Canvas.Content {...restProps} />
</Canvas.Container>
);
}
export default function Canvas({ children }: TCanvas) {
return { children };
}
Canvas.Content = function CanvasContent({ CANVAS_REF, ...restProps }: TCanvasContent) {
return <Content ref={CANVAS_REF} {...restProps} />;
};
https://codesandbox.io/s/dank-dust-vo4lh?file=/src/CanvasContainer.js
Got it worked.
https://codesandbox.io/s/peaceful-jepsen-wh913?file=/src/App.js
The "Style" of the canvas was wrong.
I'm trying to draw an image with d3 from a json file. This image data is then put into a canvas context to actually draw the image. I would like to rotate the resulting image but I cant find how. The code I actually use
$(document).ready(function() {
d3.json("resources/image.json").then(function(image) {
var canvas = d3.select("colorImage")
.append("canvas")
.attr("width", 200)
.attr("height", 200);
var context = canvas.node().getContext("2d");
var image_data = context.createImageData(200, 200);
image_data.data.set(image);
context.rotate(60);
context.putImageData(image_data, 0, 0); // <---- image not rotated
context.fillRect( 100, 100, 20, 20 ); // <--- rectangle rotated
}).catch(function(error){
if (error) throw error;
});
})
putImageData() and getImageData() are not affected by the context's transformation matrix.
To rotate it, the easiest is to create an ImageBitmap from it and to draw that ImageBitmap using drawImage():
(async () => {
const canvas = document.getElementById( 'canvas' );
const context = canvas.getContext( '2d' );
// make some noise
const image = new Uint8Array( Uint32Array.from(
{ length: 200 * 200 },
_ => (Math.random() * 0xFFFFFFFF)
).buffer );
const image_data = context.createImageData( 200, 200 );
image_data.data.set( image );
const center_w = canvas.width / 2;
const center_h = canvas.height / 2;
const bitmap = await createImageBitmap( image_data );
context.translate( center_w, center_h );
context.rotate( 60 );
context.translate( -center_w, -center_h );
context.drawImage( bitmap, center_w-100, center_h-100 ); // <---- image rotated
context.lineWidth = 4;
context.strokeStyle = 'green';
context.strokeRect( center_w-100, center_h-100, 200, 200 ); // <--- rectangle rotated
})().catch( console.error );
<canvas id="canvas" height="300" width="300"></canvas>
And for browsers that do not support createImageBitmap() method, you can monkey-patch this exact use quite easily by using an HTMLCanvasElement:
/* if( !window.createImageBitmap ) { */ // for demo we force monkey-patch
monkeyPatchCreateImageBitmap();
/*} */
(async () => {
const canvas = document.getElementById( 'canvas' );
const context = canvas.getContext( '2d' );
// make some noise
const image = new Uint8Array( Uint32Array.from(
{ length: 200 * 200 },
_ => (Math.random() * 0xFFFFFFFF)
).buffer );
const image_data = context.createImageData( 200, 200 );
image_data.data.set( image );
const center_w = canvas.width / 2;
const center_h = canvas.height / 2;
const bitmap = await createImageBitmap( image_data );
context.translate( center_w, center_h );
context.rotate( 60 );
context.translate( -center_w, -center_h );
context.drawImage( bitmap, center_w-100, center_h-100 ); // <---- image rotated
context.lineWidth = 4;
context.strokeStyle = 'green';
context.strokeRect( center_w-100, center_h-100, 200, 200 ); // <--- rectangle rotated
})().catch( console.error );
// minimalistic version that only accepts ImageData, and no clipping
function monkeyPatchCreateImageBitmap() {
window.createImageBitmap = function( source ) {
if( source instanceof ImageData ) {
const dest = document.createElement( 'canvas' );
dest.width = source.width;
dest.height = source.height;
dest.getContext( '2d' ).putImageData( source, 0, 0 );
return Promise.resolve( dest );
}
};
}
<canvas id="canvas" height="300" width="300"></canvas>
In a React component how can I render an image created using canvas?
componentWillMount () {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
canvas.width = 256
canvas.height = 256
// omitted canvas painting code
const image = context.getImageData(0, 0, canvas.width, canvas.height)
this.setState({image})
}
render() {
return ?
}
I'm not sure you can render it directly in the render method. But you can draw it if you use componentDidMount/componentDidUpdate hooks if you are setting it with state.
class Hi extends React.Component {
state = {};
componentWillMount () {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
canvas.width = 256
canvas.height = 256
context.rect(20,20,150,100);
context.stroke();
const image = context.getImageData(0, 0, canvas.width, canvas.height)
this.setState({image})
}
componentDidMount() {
const context = this.refs.canvas.getContext('2d');
context.putImageData(this.state.image, 0, 0);
}
render() {
return <canvas ref="canvas" />
}
}
ReactDOM.render(<Hi />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Please convert your canvas content to data URI using canvas.toDataURL, then you can render the image with an img tag passing the data URI as src attribute:
<img src={this.state.dataURI} />
class Example extends React.Component {
state = {}
componentWillMount () {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
canvas.width = 256
canvas.height = 256
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 70;
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
context.lineWidth = 5;
context.strokeStyle = '#003300';
context.stroke();
const dataURI = canvas.toDataURL()
this.setState({dataURI})
}
render() {
return <img src={this.state.dataURI} />
}
}
ReactDOM.render(<Example />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>