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>
Related
Solved Had to render the images globally outside of the draw method. Here's a link to the github if you find this question and are wondering what the solution looks like in the full code. Its a bit too long to post here as an update. github canvas orbs
back to the original question:
I'm trying to render custom Class objects inside an HTML canvas. Doesn't work with class, but the the same data works without class.
Here's the code:
import './styles/index.css';
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
var window_height = window.innerHeight;
var window_width = window.innerWidth;
canvas.width = 500;
canvas.height = 400;
canvas.style.background = "#232a2e"
const convertSVG = (svgid) => {
const svg = document.getElementById(svgid);
const xml = new XMLSerializer().serializeToString(svg);
const svg64 = btoa(xml);
const b64Start = 'data:image/svg+xml;base64, ';
return b64Start + svg64;
}
class Orb {
constructor(xpos, ypos, radius, speed, image) {
this.xpos = xpos;
this.ypos = ypos;
this.radius = radius;
this.speed = speed;
this.image = convertSVG(image);
}
draw(context) {
const img = new Image();
img.onload = function() {
context.save();
context.beginPath();
context.arc(this.xpos, this.ypos, this.radius, 0, Math.PI * 2, false);
context.clip();
context.drawImage(img, (this.xpos - this.radius), (this.ypos - this.radius), 64, 64);
context.restore();
}
img.src = this.image;
}
}
const myOrb = new Orb(150, 150, 30, 1, 'javascript-icon');
myOrb.draw(context);
console.log(myOrb);
const img = new Image();
img.onload = function() {
context.save();
context.beginPath();
context.arc(300, 300, 30, 0, Math.PI * 2, false);
context.clip();
context.drawImage(img, (300-32), (300-32), 64, 64);
context.restore();
}
img.src = convertSVG('javascript-icon');
Currently it's only displaying the object I draw explicitly at the bottom of the code, and not the object of class Orb.
Here's a screen cap of the canvas:
current canvas render
EDIT: Additionally, I can generate a class object and draw the orb based on that. Like so:
const myOrb = new Orb(300, 300, 30, 1, 'javascript-icon');
const myOrb2 = new Orb(150, 150, 30, 1, 'java-icon');
const img = new Image();
img.onload = function() {
context.save();
context.beginPath();
context.arc(myOrb.xpos, myOrb.ypos, myOrb.radius, 0, Math.PI * 2, false);
context.clip();
context.drawImage(img, (myOrb.xpos-myOrb.radius-2), (myOrb.ypos-myOrb.radius-2), 64, 64);
context.restore();
}
img.src = myOrb.imageSRC;
console.log(myOrb);
console.log(myOrb2);
myOrb2.draw(context);
myOrb renders, but myOrb2 which is drawn with the method of the class is not rendered.
Here's an approach to take. Load the images globally and call draw when image is loaded.
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
var window_height = window.innerHeight;
var window_width = window.innerWidth;
canvas.width = 500;
canvas.height = 400;
canvas.style.background = "#232a2e"
const img1 = new Image();
img1.src = 'https://cdn.iconscout.com/icon/free/png-256/javascript-2752148-2284965.png';
const img2 = new Image();
img2.src = 'https://iconape.com/wp-content/png_logo_vector/cib-javascript.png'
class Orb {
constructor(xpos, ypos, radius, speed, image) {
this.xpos = xpos;
this.ypos = ypos;
this.radius = radius;
this.speed = speed;
this.image = image;
}
draw(context) {
context.save();
context.beginPath();
context.arc(this.xpos, this.ypos, this.radius, 0, Math.PI * 2, false);
context.clip();
context.drawImage(this.image, (this.xpos - this.radius), (this.ypos - this.radius), 64, 64);
context.restore();
}
}
const myOrb = new Orb(150, 150, 30, 1, img1);
const myOrb2 = new Orb(350, 150, 30, 1, img2);
window.onload = function() {
myOrb.draw(context);
myOrb2.draw(context);
}
<canvas id="canvas"></canvas>
I am able to draw lines around a circle. I have the basic implementation of the AudioContext API setup.
The problem I am facing is when calling lineTo the line will only grow but not shrink. I am inspired by this https://www.kkhaydarov.com/audio-visualizer/. I am translating this code over into https://codesandbox.io/s/hungry-tereshkova-1pf0c?runonclick=1&file=/src/Visualizer.js:713-725 which is a React.js version.
If you run that code you will see the music play, then the bars will grow once, and then they stick. They refuse to shrink then grow to the beat.
I am not sure where I am going wrong or what I am missing in that code. It seems pretty similar to the other code in the example.
Here is the full code for the Visualizer component.
import React, { useEffect, useRef } from "react";
let frequencyArray = [];
let analyser;
const Visualizer = () => {
const canvasRef = useRef(null);
const requestRef = useRef(null);
useEffect(() => {
initAudio();
requestRef.current = requestAnimationFrame(drawCanvas);
return () => cancelAnimationFrame(requestRef.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const initAudio = () => {
const audio = new Audio();
audio.src =
"https://s3.us-west-2.amazonaws.com/storycreator.uploads/ck9kpb5ss0xf90132mgf8z893?client_id=d8976b195733c213f3ead34a2d95d1c1";
audio.crossOrigin = "anonymous";
audio.load();
const context = new (window.AudioContext || window.webkitAudioContext)();
analyser = context.createAnalyser();
const source = context.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(context.destination);
frequencyArray = new Uint8Array(analyser.frequencyBinCount);
audio.play();
};
// draw the whole thing
const drawCanvas = () => {
if (canvasRef.current) {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const radius = 200;
const bars = 100;
drawCircle(canvas, ctx, radius);
analyser.getByteFrequencyData(frequencyArray);
for (var i = 0; i < bars; i++) {
const height = frequencyArray[i] * 0.3;
drawLine(
{
i,
bars,
height,
radius
},
canvas,
ctx
);
}
requestRef.current = requestAnimationFrame(drawCanvas);
}
};
// draw the main circle
const drawCircle = (canvas, ctx, radius) => {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
ctx.save();
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = "white";
ctx.fill();
ctx.strokeStyle = "#dddddd";
ctx.lineWidth = 5;
ctx.stroke();
ctx.restore();
};
// dray lines around the circle
const drawLine = (opts, canvas, ctx) => {
const { i, radius, bars, height } = opts;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const lineWidth = 10;
const rads = (Math.PI * 2) / bars;
const x = centerX + Math.cos(rads * i) * (radius + lineWidth);
const y = centerY + Math.sin(rads * i) * (radius + lineWidth);
const endX = centerX + Math.cos(rads * i) * (radius + height);
const endY = centerY + Math.sin(rads * i) * (radius + height);
// draw the bar
ctx.strokeStyle = "#ddd";
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(endX, endY);
ctx.stroke();
};
return (
<canvas
ref={canvasRef}
style={{ background: "#f5f5f5" }}
width={window.innerWidth}
height={window.innerHeight}
/>
);
};
export default Visualizer;
You just missed a clearRect in your code...
Without that we see the lines grow only because any following shorter line does not overwrite the previous one, they are still getting drawn just we do not see it.
here is the working code:
https://codesandbox.io/s/dry-cdn-ghu4m?file=/src/Visualizer.js:1247-1276
I hardcoded a ctx.clearRect(0,0, 1000,1000) just to show you that it works, but you should use the canvas dimensions there, everything else looks good.
Only recommendation will be to somehow move:
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
somewhere global outside the drawCanvas function,
those do not change on every run, will be nice to set them just once.
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
I want to make the context inside the canvas to rotate on button click, I'm pretty sure that I have to use onclick, but I don't know where to put it or what logic do I have to write inside it. Can anyone help me out?
I tried using jquery on click but that does not work:
HTML:
<input type="file" id="fileInp" onchange="readImage(this)">
<button type="button" class="rotate">Rotate</button>
<div>
<canvas id="canvasImg" style="max-width:100%; border:solid;" width="100%" height="100%"></canvas>
</div>
Javascript:
function readImage(input) {
const canvas = document.getElementById('canvasImg');
const context = canvas.getContext("2d");
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
context.clearRect(0, 0, canvas.width, canvas.height);
if (input.value !== '') {
imgSrc = window.URL.createObjectURL(input.files[0]);
}
const img = new Image();
img.onload = function() {
context.drawImage(img, 0, 0);
}
img.src = imgSrc;
}
jQuery('.rotate').on('click', function() {
degree = 90;
drawRotate(degree);
})
function drawRotate(degree) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(degrees * Math.PI / 180);
context.drawImage(image, -image.width / 2, -image.width / 2);
context.restore();
}
ok so there are a few problems in your code..
in the drawRotate function you use variables such as image, context, canvas which is never has declared..
you call the function param degree but use it as degreeS
you translate the context forward, but never return him back
maybe there was more and i forget..
shortly, please look at the attached code
<input type="file" id="fileInp" onchange="readImage(this)">
<button type="button" class="rotate" onclick="drawRotate(90)">Rotate</button>
<div>
<canvas id="canvasImg" style="max-width:100%; border:solid;" width="100%" height="100%"></canvas>
</div>
<script>
const canvas = document.getElementById('canvasImg');
const context = canvas.getContext("2d");
const input = document.getElementById('fileInp');
function readImage(input) {
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
context.clearRect(0, 0, canvas.width, canvas.height);
let imgSrc;
if (input.value !== '') {
imgSrc = window.URL.createObjectURL(input.files[0]);
}
const img = new Image();
img.onload = function() {
context.translate(canvas.width / 2, canvas.height / 2);
context.drawImage(img, -img.width / 2, -img.height / 2, img.width, img.height);
context.translate(-canvas.width / 2, -canvas.height / 2);
}
img.src = imgSrc;
}
function drawRotate(degree) {
let imgSrc;
if (input.value !== '') {
imgSrc = window.URL.createObjectURL(input.files[0]);
}
const img = new Image();
img.onload = function() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(degree * Math.PI / 180);
context.drawImage(img, -img.width / 2, -img.height / 2, img.width, img.height);
context.translate(-canvas.width / 2, -canvas.height / 2);
}
img.src = imgSrc;
}
</script>
play around rotate(-180deg) for continous rotation
function readImage(input) {
const canvas = document.getElementById('canvasImg');
const context = canvas.getContext("2d");
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
context.clearRect(0, 0, canvas.width, canvas.height);
if (input.value !== '') {
imgSrc = window.URL.createObjectURL(input.files[0]);
}
const img = new Image();
img.onload = function() {
context.drawImage(img, 0, 0);
}
img.src = imgSrc;
}
jQuery('.rotate').on('click', function() {
$("#canvasImg").css({'transform': 'rotate(-180deg)'});
})
function drawRotate(degree) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(degrees * Math.PI / 180);
context.drawImage(image, -image.width / 2, -image.width / 2);
context.restore();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="file" id="fileInp" onchange="readImage(this)">
<button type="button" class="rotate">Rotate</button>
<div>
<canvas id="canvasImg" style="max-width:100%; border:solid;" width="100%" height="100%"></canvas>
</div>
I know how to draw in Angular1 Ionic1.
In my html:
<img ng-src="{{image}}" style="width: 100%">
<canvas id="tempCanvas"></canvas>
In my controller
var startimg="img/face.jpg";
$scope.image=startimg;
var canvas = document.getElementById('tempCanvas');
var context = canvas.getContext('2d');
var source = new Image();
source.src = startimg;
canvas.width = source.width;
canvas.height = source.height;
console.log(canvas);
context.drawImage(source,0,0);
context.font = "100px impact";
textWidth = context.measureText($scope.frase).width;
if (textWidth > canvas.offsetWidth) {
context.font = "40px impact";
}
context.textAlign = 'center';
context.fillStyle = 'white';
context.fillText('HELLO WORLD',canvas.width/2,canvas.height*0.8);
var imgURI = canvas.toDataURL();
$timeout( function(){
$scope.image = imgURI;
}, 200);
This code would definitely draw HELLO WORLD text on the top of face image.
However, in Ionic2/Angular2, it seems doesn't work. I can't even get the document.getElementById('tempCanvas') to work.
Please help.
Thanks.
You can write the following:
#Component({
selector: 'my-app',
template: `<h1>Angular 2 Systemjs start</h1>
<img [src]="image">
<canvas #layout></canvas>
`
})
export class App {
#ViewChild('layout') canvasRef;
image = 'http://upload.wikimedia.org/wikipedia/commons/4/4a/Logo_2013_Google.png';
ngAfterViewInit() {
let canvas = this.canvasRef.nativeElement;
let context = canvas.getContext('2d');
let source = new Image();
source.crossOrigin = 'Anonymous';
source.onload = () => {
canvas.height = source.height;
canvas.width = source.width;
context.drawImage(source, 0, 0);
context.font = "100px impact";
context.textAlign = 'center';
context.fillStyle = 'black';
context.fillText('HELLO WORLD', canvas.width / 2, canvas.height * 0.8);
this.image = canvas.toDataURL();
};
source.src = this.image;
}
}
See also the plunkr https://plnkr.co/edit/qAGyQVqbo3IGSFzC0DcQ?p=preview
Or you can use custom directive like this:
#Directive({
selector: '[draw-text]'
})
class ImageDrawTextDirective {
loaded = false;
#Input() text: String;
#HostListener('load', ['$event.target'])
onLoad(img) {
if(this.loaded) {
return;
}
this.loaded = true;
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
canvas.height = img.height;
canvas.width = img.width;
context.drawImage(img, 0, 0);
context.font = "100px impact";
context.textAlign = 'center';
context.fillStyle = 'black';
context.fillText(this.text, canvas.width / 2, canvas.height * 0.8);
img.src = canvas.toDataURL();
}
}
See plunkr for this case https://plnkr.co/edit/BrbAoBlLcbDcx8iDXACv?p=preview