Canvas toDataURL return empty - javascript

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);
}
};
)}
}

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);
}
};

ThreeJS - Cannot load GLB/GLTF file that I exported using GLTFExporter

I use GLTFLoader to import my existing 3D model into the scene. It works fine as I am able to view my model with the texture and animations and all. Next, without modifying anything, I use GLTFExporter to export to both .glb and .gltf by changing the binary option to true and false respectively.
Next, I try to load the exported file with `GLTFLoader' again but and error pop up.
Here is my code to load the file
const { name } = file;
const isFBX = name.match(/\.(fbx)$/);
const isGLB = name.match(/\.(gltf|glb)$/);
const isEitherFBXorGLB = isFBX || isGLB;
if (isEitherFBXorGLB) {
const path = URL.createObjectURL(file);
const loader = isFBX ? new FBXLoader() : new GLTFLoader();
loader.load(
path,
(gltf) => {
console.log(gltf);
if (isFBX) setModel(gltf as Group);
if (isGLB) setModel((gltf as GLTF).scene);
if (gltf.animations) setAnimationClips(gltf.animations);
},
(event) => setModelLoaded((event.loaded / event.total) * 100),
(error) => {
console.error(error);
},
);
}
Here is my code to export
const exporter = new GLTFExporter();
const options = {
binary: false,
animations: animationClips,
};
console.log(options);
(exporter as any).parse(
model,
(result: any) => {
console.log(typeof result, result);
if (result instanceof ArrayBuffer) {
saveArrayBuffer(result, 'newmodel.glb');
} else {
const output = JSON.stringify(result, null, 2);
saveString(output, 'newmodel.gltf');
}
},
(error: any) => console.log(error),
options,
);
const save = (blob: Blob, filename: string) => {
const link = document.createElement('a');
link.style.display = 'none';
document.body.appendChild(link); // Firefox workaround, see #6594
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
};
const saveString = (text: string, filename: string) => {
save(new Blob([text], { type: 'text/plain' }), filename);
};
const saveArrayBuffer = (buffer: any, filename: string) => {
save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
};
I have no clue why it doesn't work.

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);
}
}
};

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.

Get cropped image through react-image-crop module

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;
}

Categories

Resources