Get cropped image through react-image-crop module - javascript

I'm trying to get the cropped image with react-image-crop module (blob). However I've problem with canvas, I guess it needs to be done asynchronously, but I'm not sure and I'm not sure if I've choosen the right approach either.
I've tried to do the same as written in documentation of react-image-crop in https://www.npmjs.com/package/react-image-crop.
I'm also trying to do the same as Stefan in this topic:
Get cropped image via react-image-crop module
state = {
image: '',
crop: {
aspect: 4/3,
x: 10,
y: 10,
width: 80,
height: 80,
},
imgSrc: null
}
getCroppedImg = (image, pixelCrop, fileName) => {
const canvas = document.createElement('canvas');
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(
image,
pixelCrop.x,
pixelCrop.y,
pixelCrop.width,
pixelCrop.height,
0,
0,
pixelCrop.width,
pixelCrop.height
);
// As a blob
return new Promise((resolve, reject) => {
canvas.toBlob(blob => {
blob.name = fileName;
resolve(blob);
}, 'image/jpeg');
});
}
handleImageUpload = e => {
const uploadData = new FormData();
uploadData.append("image", this.state.imgSrc);
service.handleUpload(uploadData)
.then(response => {
this.setState({ image: response.secure_url });
})
.catch(err => {
console.log("Error while uploading the file: ", err);
});
}
handleImagePreview = e => {
this.setState({image: URL.createObjectURL(e.target.files[0]), imgSrc: e.target.files[0]})
}
handleOnCropComplete = (crop, pixelCrop) => {
this.getCroppedImg(this.state.imgSrc, pixelCrop, 'preview.jpg')
.then((res) => {
const blobUrl = URL.createObjectURL(res);
console.log(blobUrl);
})
}
render() {
return(
<>
<input required onChange={this.handleImagePreview} type="file" />
<div className="crop-div">
<ReactCrop
src={this.state.image}
crop={this.state.crop}
onChange={this.handleOnCropChange}
onComplete={this.handleOnCropComplete} />
<button className="submit-btn" onClick={this.handleImageUpload}>Crop the image</button>
</div>
</>
)
}
After resizing the cropped area on the image I'll get this error:
"TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)'"
Image that I'm passing is Image File Object.

A file object isn't in the list expected as stated by the error message, so you have to create an object in the list from the object you have.
In this case we'll use the HTMLImageElement.
getCroppedImg = (imageFile, pixelCrop, fileName) => {
const canvas = document.createElement('canvas');
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
const ctx = canvas.getContext('2d');
var image = new Image();
var promise = new Promise((resolve, reject) => {
image.onload = function(){
ctx.drawImage(
image,
pixelCrop.x,
pixelCrop.y,
pixelCrop.width,
pixelCrop.height,
0,
0,
pixelCrop.width,
pixelCrop.height
);
resolve();
};
image.src = URL.createObjectURL(imageFile);
}).then(function(){
return new Promise((resolve, reject) => {
canvas.toBlob(blob => {
blob.name = fileName;
resolve(blob);
}, 'image/jpeg');
});
});
return promise;
}

Related

Satori is not initialized: expect yoga to be loaded, got undefined #301

Steps to reproduce this error:
Download or clone this Satori example:
https://codesandbox.io/s/cover-image-bf6282?from-embed=&file=/src/index.tsx
Run the example (npm start) and press the Download button in the launched page.
You'll get this error:
Uncaught (in promise) Error: Satori is not initialized: expect yoga to be loaded, got undefined
What could be the problem, and how to fix it?
I think the problem is here:
svg-utils.ts:
export const svgToPngURL = (svg: string) =>
new Promise<string>((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext("2d");
ctx!.drawImage(img, 0, 0);
resolve(canvas.toDataURL("image/png"));
URL.revokeObjectURL(img.src);
};
img.onerror = (e) => {
reject(e);
URL.revokeObjectURL(img.src);
};
img.src = URL.createObjectURL(new Blob([svg], { type: "image/svg+xml" }));
});
export const downloadSvgAsPng = async (svg: string) => {
const pngURL = await svgToPngURL(svg);
try {
const a = document.createElement("a");
a.href = pngURL;
a.download = "Image.png";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
} finally {
URL.revokeObjectURL(pngURL);
}
};

Wait until WebGL has drawn onto screen

I switched from using 2D context to using WebGL context. I'm trying to figure out a way to wait until pixels have been drawn onto the screen.
With 2D context I would do something like this
const handlePaintFrame = () => {
const ctx: CanvasRenderingContext2D | any = videoCanvasRef?.current?.getContext("2d");
if (video.current && videoCanvasRef.current && ctx) {
if (chroma?.keyColor) {
drawChromaFrame(ctx, width, height);
} else {
ctx.drawImage(video.current, 0, 0, width, height);
}
const rendered = () => {
setTimeout(() => {
onReady("video");
}, 20);
};
const startRender = () => {
requestAnimationFrame(rendered);
};
const img = new Image();
img.onload = () => {
requestAnimationFrame(startRender);
};
img.onerror = (e: any) => {
console.log("Image error:", { e });
};
img.src = videoCanvasRef.current.toDataURL();
}
};
Currently my draw code for the WebGL context is something like this
const drawTexture = () => {
// render
if (gl && video.current) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, video.current);
drawScene(locationRef.current, buffersRef.current);
}
};
const handlePaintFrame = () => {
if (video.current) {
if (chroma?.keyColor) {
// drawChromaFrame(ctx, width, height);
} else {
drawTexture();
// TODO: We need to wait until texture is drawn
setTimeout(() => {
onReady("video");
}, 100);
}
}
};

Canvas toDataURL return empty

I am trying to export SVG element as image blob using Canvas. When i try to invoke canvas.toDataURL("image/png") it returns empty string. I am not able to figure out why this happens.
const svg: Node = document.getElementById("flowchart-container").children[0];
var icanvas = document.getElementById("stage");
const ctx: CanvasRenderingContext2D = icanvas.getContext("2d");
var width = (svg as HTMLElement).clientWidth;
var height = (svg as HTMLElement).clientHeight;
const image: HTMLImageElement = document.createElement("img");
image.crossOrigin = "anonymous";
const imageBlob: Blob = new Blob(
[new XMLSerializer().serializeToString(svg)],
{
type: "image/svg+xml",
}
);
const blobUrl: string = URL.createObjectURL(imageBlob);
image.src = blobUrl ;
image.onload = (): void => {
//ctx.clearRect(0, 0, width, height);
ctx.drawImage(image, 0, 0, width, height);
window.URL.revokeObjectURL(blobUrl);
try {
const imageData: string = icanvas.toDataURL("image/png");
const imageFileToBeExported: Blob | File = this.dataURLtoFile(
imageData,
"flowchart.png"
);
promiseResolve(imageFileToBeExported);
} catch (error) {
console.log('Failed to Create Image');
console.log(error);
promiseReject(error);
}
};
Above code goes inside the createSVG method. Caller is the display method.
public createSVG(
promiseResolve: (data: File | Blob) => void,
promiseReject: (error: unknown) => void
): void {
//Code for Creating File
}
public async getImage() :Promise<any>{
let imageBlob: File | Blob;
try {
imageBlob = await generateImageAsynchronously();
} catch (error) {
console.log('Failed to create Image');
}
}
public generateImageAsynchronously(): Promise<File | Blob> {
return new Promise(
(
resolver: (data: File | Blob) => void,
reject: (error: unknown) => void
): void => {
this.createSVG(resolver, reject);
}
);
}
Finally the call invoked from below method
public display(){
this.getImage().then(response => {
console.log('Resolving Promise');
this.imageBlob = response;
);
}
this is a common issue when using .onload, basically you're returning the promise before the data is processed.
You can try wrapping it in a async function like this
async function waitForloading(){
let waitForLoad = await processImage()
return waitForLoad
}
function processImage(){
return new Promise(function(promiseResolve,promiseReject){
const svg: Node = document.getElementById("flowchart-container").children[0];
var icanvas = document.getElementById("stage");
const ctx: CanvasRenderingContext2D = icanvas.getContext("2d");
var width = (svg as HTMLElement).clientWidth;
var height = (svg as HTMLElement).clientHeight;
const image: HTMLImageElement = document.createElement("img");
image.crossOrigin = "anonymous";
const imageBlob: Blob = new Blob(
[new XMLSerializer().serializeToString(svg)],
{
type: "image/svg+xml",
}
);
const blobUrl: string = URL.createObjectURL(imageBlob);
image.src = blobUrl ;
image.onload = (): void => {
//ctx.clearRect(0, 0, width, height);
ctx.drawImage(image, 0, 0, width, height);
window.URL.revokeObjectURL(blobUrl);
try {
const imageData: string = icanvas.toDataURL("image/png");
const imageFileToBeExported: Blob | File = this.dataURLtoFile(
imageData,
"flowchart.png"
);
promiseResolve(imageFileToBeExported);
} catch (error) {
console.log('Failed to Create Image');
console.log(error);
promiseReject(error);
}
};
)}
}

How can i return the resized image from filereader

I've created a function that takes care of resizing a image, now i want to return the resized image from the function.
I'm working with react and know i can fix the problem by using the state but i don't like this solution..
Tried returning every different scope, but i still get a undefined response. Also when i return the reader itself, i get the old data.
Interface
interface IHTMLInputEvent extends Event {
target: HTMLInputElement & EventTarget;
}
the resize function
function resizeImage(file: IHTMLInputEvent, width: number) {
const fileName = file.target.files[0].name;
const reader = new FileReader();
reader.readAsDataURL(file.target.files[0]);
reader.onload = (event) => {
const img = new Image();
img.src = event.target.result.toString();
img.onload = () => {
const elem = document.createElement('canvas');
const scaleFactor = width / img.width;
elem.width = width;
elem.height = img.height * scaleFactor;
const ctx = elem.getContext('2d');
ctx.drawImage(img, 0, 0, width, img.height * scaleFactor);
ctx.canvas.toBlob((blob) => {
return new File([blob], fileName, { type: 'image/jpeg', lastModified: Date.now() });
}, 'image/jpeg', 1);
};
};
}
function for handling the upload(so far)
function handleUpload(e: IHTMLInputEvent) {
const resizedImage = resizeImage(e, 600);
}
input field
<input
className={classes.inputForUpload}
accept='image/*'
type='file'
ref={uploadImage}
onChange={(e: IHTMLInputEvent) => handleUpload(e)}
/>
I would like to return the new created image.
You can solve this using Promise,
function resizeImage(file: IHTMLInputEvent, width: number) {
return new Promise((resolve, reject) => {
const fileName = file.target.files[0].name;
const reader = new FileReader();
reader.readAsDataURL(file.target.files[0]);
reader.onload = (event) => {
const img = new Image();
img.src = event.target.result.toString();
img.onload = () => {
const elem = document.createElement('canvas');
const scaleFactor = width / img.width;
elem.width = width;
elem.height = img.height * scaleFactor;
const ctx = elem.getContext('2d');
ctx.drawImage(img, 0, 0, width, img.height * scaleFactor);
ctx.canvas.toBlob((blob) => {
resolve(new File([blob], fileName, {
type: 'image/jpeg',
lastModified: Date.now()
}));
}, 'image/jpeg', 1);
};
};
});
}
With async, await ,
async function handleUpload(e: IHTMLInputEvent) {
const resizedImage = await resizeImage(e, 600);
// do you suff here
}
JSX,
<input
className={classes.inputForUpload}
accept='image/*'
type='file'
ref={uploadImage}
onChange={async (e: IHTMLInputEvent) => await handleUpload(e)}
/>
May I ask why you don't like the solution of using state? This seems like a pretty standard use case.
Your state could look something like this:
state = {
imageDescription: '',
imageUrl: null
};
Your action handler would simply setState upon success like so:
img.onload = () => {
...
this.setState({ imageDescription: fileName, imageSrc: img.src })
};
Finally your render function would look something like this:
render() {
const { imageDescription, imageUrl } = this.state;
return (
<Fragment>
<input
className={classes.inputForUpload}
accept='image/*'
type='file'
ref={uploadImage}
onChange={(e: IHTMLInputEvent) => handleUpload(e)}
/>
<img src={imageUrl} alt={imageDescription} />
</Fragment>
)
}
P.S. you can delete handleUpload and call resizeImage directly.

nodejs canvas: Image given has not completed loading

i'm getting this error:
ctx.drawImage(avatar, 50, 50 );
^
Error: Image given has not completed loading
at Request.request.get
when trying to do this:
app.get('/test', (req, res) => {
let Canvas = require('canvas')
, Image = Canvas.Image
, canvas = new Canvas(500, 500)
, ctx = canvas.getContext('2d');
let request = require('request');
let avatar = new Image;
request.get('http://s.4pda.to/pKugVJ7TmgG1Tg5z0SsVLfRCUqTAz2oqz1lz2z2co.jpg', (err, res) => {
avatar.src = res;
ctx.drawImage(avatar, 50, 50 );
});
ctx.font = '30px Impact';
ctx.rotate(.1);
ctx.fillText("Awesome!", 50, 100);
let te = ctx.measureText('Awesome!');
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
ctx.beginPath();
ctx.stroke();
res.setHeader('Content-Type', 'image/png');
canvas.pngStream().pipe(res)
});
i'm using node 8.3.0, node-canvas 1.6.6, mac os x 10.12.6.
I have already tried to reinstall and rebuild cairo, jpg/png libs, pixman, node-canvas but it didn't help me.
For anyone researching this, see the comment by LinusU on 13 August 2017, here:
https://github.com/Automattic/node-canvas/issues/959
Cribbing from there, he suggests a method such as this to load an image, and wait for its loading:
function loadImage (url) {
return new Promise((resolve, reject) => {
const img = new Canvas.Image()
img.onload = () => resolve(img)
img.onerror = () => reject(new Error('Failed to load image'))
request.get(url, (err, res) => {
if (err) return reject(err)
img.src = res.body
})
})
}
It worked nicely for me.

Categories

Resources