I want to create a function that return an observable of file
My current working code uses a promise
testPromise(): Promise<File> {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
const context = canvas.getContext('2d');
context.drawImage(image, 0, 0, 800, 600);
canvas.toBlob((blob: Blob) => resolve(new File([blob], name)), type, quality);
});
}
Instead of that, I want to return Observable<File>
I tried the following but I cannot pass the toBlob() arguments such as type and quality
testObservable(): Observable<File> {
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
const context = canvas.getContext('2d');
context.drawImage(image, 0, 0, 800, 600);
const toBlob = bindCallback(canvas.toBlob);
return toBlob().pipe(
map((blob: Blob) => {
return new File([blob], name)
})
);
}
Expected behavior
const toBlob = bindCallback(canvas.toBlob);
toBlob(type, quality) // <= Expected 0 arguments, but got 2.
Here I get an error on toBlob Expected 0 arguments, but got 2.
Current behavior
const toBlob = bindCallback(canvas.toBlob);
toBlob() // <= No type or quality arguments
Here is the canvas.toBlob interface according to MDN docs
void canvas.toBlob(callback, mimeType, qualityArgument);
PS: I don't want to convert the Promise to an Observable,but I need to convert the callback directly to an Observable
Any ideas?
I'm afraid bindCallback is not a good choice here because it expects the callback to be the last argument and not the first as what toBlob expects.
I think you'll have to wrap this call with na Observable yourself:
return new Observable(observer => {
canvas.toBlob(result => {
observer.next(result);
observer.complete();
}, type, quality);
});
Related
I want to transfer full control to worker and use off screen canvas. But there is an Image() which is tied to UI, see function makeImg. I am not intending to show the image, it has pure data usage for building a mesh. The highlighted code fully depends on UI. Is it possible (and how exactly) to do it entirely in web worker, without interchanging data with UI until calculations done and the final mesh fully generated, ready to be shown? For instance following bitmap contains the heights:
The page without perspective with full source codes is here.
I am building height terrain using above bitmap as heightmap, code is here on GitHub, in the page heightMap.html. So, I use pixel values being used to generate vertices, calculate normals, texture coordinate. The result is the terrain going to be shown in the page, here shown without texture:
async function readImgHeightMap (src, crossOrigin) {
return new Promise ((resolve, reject) => {
readImg (src, crossOrigin).then ((imgData) => {
let heightmap = [];
//j, row -- z coordinate; i, column -- x coordinate
//imgData.data, height -- y coordinate
for (let j = 0, j0 = 0; j < imgData.height; j++, j0 += imgData.width * 4) {
heightmap[j] = [];
for (let i = 0, i0 = 0; i < imgData.width; i++, i0 += 4)
heightmap[j][i] = imgData.data[j0 + i0];
}
resolve( {data:heightmap, height:imgData.height, width:imgData.width} );
});
});
}
async function readImg (src, crossOrigin) {
return new Promise ( (resolve, reject) => {
makeOffscreenFromImg (src, crossOrigin).then((canvas) => {
let ctx = canvas.getContext("2d");
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height, { colorSpace: "srgb" });
resolve(imgData);
});
});
}
async function makeOffscreenFromImg (src, crossOrigin) {
let img = makeImg(src, crossOrigin);
return new Promise((resolve, reject) => {
img.addEventListener('load', () => {
let cnv = new OffscreenCanvas(img.width, img.height);
cnv.getContext("2d").drawImage(img, 0, 0);
resolve(cnv);
});
img.addEventListener('error', (event) => { console.log(event); reject (event); } );
});
}
function makeImg (src, crossOrigin)
{
let image = new Image ();
let canvas = document.createElement("canvas");
if (crossOrigin) image.crossOrigin = crossOrigin;
image.src = src;
return image;
}
##################
PS: Just in case, to see the crater from different angles, camera can be moved with mouse when pressing SHIFT, or rotated when pressing CTRL. Also click event for permanent animation, or tap if on mobile device.
PS1: Please do not use heightmap images for personal purposes. These have commercial copyright.
Use createImageBitmap from your Worker, passing a Blob you'd have fetched from the image URL:
const resp = await fetch(imageURL);
if (!resp.ok) {
throw "network error";
}
const blob = await resp.blob();
const bmp = await createImageBitmap(blob);
const { width, height } = bmp;
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext("2d");
ctx.drawImage(bmp, 0, 0);
bmp.close();
const imgData = ctx.getImageData(0, 0, width, height);
If required, you could also create the ImageBitmap from the <img> tag in the main thread, and transfer it to your Worker.
React beginner here, I'm getting this error 'Uncaught DOMException: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state.'
i have been stuck with this for few hours now.
That error is showing on drawImage which is inside if statement.
Is there a way to tell it if there is an image then go on and draw it ( ctx.drawImage(img, 0, 0)), but if there isnt any image then dont give any error(dont show any image either), something like what Optional chaining (?.) does ?
English is not my mother language so could be mistakes.
my code:
const CameraCanvas = useRef<any>();
const CameraImage = useRef<any>();
const updatePreviewUrl = () => {
if (currentCam) {
let curSnapshotUrl = state.snapshot?.snapshotUrl.split("?ts=")[0];
let newSnapshotUrl =
`api/cam/${....}/` +
`${curCam.ident}/snapsht`;
if (curSnapshotUrl ....) {
setState({
...
});
}
}
};
const updateCanvas = () => {
updatePreviewUrl();
const canvas = CameraCanvas.current;
const ctx = canvas.getContext("2d");
const img = CameraImage.current;
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill();
if (currentCam) {
ctx.drawImage(img, 0, 0);
}
};
useEffect(() => {
updateCanvas();
const img = CameraImage.current;
img.onload = updateCanvas;
return () => {
img.onload = null;
};
});
I am trying to generate an image using p5.js and p5.js-svg and then save the SVG xml as a dataURL for storage. Here is an example script that I've simplified for this question:
const canvasWidth = 600;
const canvasHeight = 600;
const bgColor = "#fffcf3";
let unencodedDataURL = "";
const sketch = (p) => {
p.setup = () => {
p.createCanvas(canvasWidth, canvasHeight, p.SVG);
p.background(bgColor);
p.noLoop();
}
p.draw = () => {
p.noFill();
p.ellipse(z
canvasWidth / 2,
canvasHeight / 2,
150,
150
);
unencodedDataURL = p.getDataURL();
console.log(`inside: ${unencodedDataURL}`);
}
}
const test = new p5(sketch, document.body);
console.log(`outside: ${unencodedDataURL}`);
The first log statement prints the right dataURL. But of course the second log statement executes before the one inside draw() and I cannot figure out how to capture the dataURL correctly. I'm sure I am missing something in the p5.js or p5.js-svg libraries and there is an easier way. But I am stuck. Anyone have an idea here? Thanks in advance!
Because p5js run asynchronous,
You can add callback or write a Promise to get data after sketch draw, like this
var handleDataCallback = null;
//........
p.ellipse(
canvasWidth / 2,
canvasHeight / 2,
150,
150
);
unencodedDataURL = p.getDataURL();
if (handleDataCallback) handleDataCallback(unencodedDataURL);
//....
//for callback
handleDataCallback = dataURL => {
//todo something with dataURL
}
if you want to write as a function to generate image
function generateImage(param, callback) {
return new Promise(resolve => {
//......
// do something
p.ellipse(
canvasWidth / 2,
canvasHeight / 2,
150,
150
);
var unencodedDataURL = p.getDataURL();
if (callback) callback(unencodedDataURL);
resolve(unencodedDataURL);
// ....
})
}
//use Promise
generateImage(yourParam, null).then(unencodedDataURL=>{
console.log(`outside: ${unencodedDataURL}`);
})
//or callback
generateImage(yourParam, unencodedDataURL=> {
console.log(`outside: ${unencodedDataURL}`);
});
I'm trying to learn JavaScript, making my first game. How I can make all images onload in one function and later draw it in the canvas making my code shorter?
How can I put a lot of images in an array and later us it in a function.
This is my third day of learning JavaScript.
Thanks in advance.
var cvs = document.getElementById('canvas');
var ctx = cvs.getContext('2d');
//load images
var bird = new Image();
var bg = new Image();
var fg = new Image();
var pipeNorth = new Image();
var pipeSouth = new Image();
//images directions
bg.src = "assets/bg.png";
bird.src = "assets/bird.png";
fg.src = "assets/fg.png";
pipeNorth.src = "assets/pipeNorth.png";
pipeSouth.src = "assets/pipeSouth.png";
var heightnum = 80;
var myHeight = pipeSouth.height+heightnum;
var bX = 10;
var bY = 150;
var gravity = 0.5;
// Key Control :D
document.addEventListener("keydown",moveUP)
function moveUP(){
bY -= 20;
}
//pipe coordinates
var pipe = [];
pipe[0] = {
x : cvs.width,
y : 0
}
//draw images
//Background img
bg.onload = function back(){
ctx.drawImage(bg,0,0);
}
//pipe north
pipeNorth.onload = function tubo(){
for(var i = 0; i < pipe.length; i++){
ctx.drawImage(pipeNorth,pipe[i].x,pipe[i].y);
pipe[i].x--;
}
}
pipeSouth.onload = function tuba(){
ctx.drawImage(pipeSouth,pipe[i].x,pipe[i].y+myHeight);
}
bird.onload = function pajaro(){
ctx.drawImage(bird,bX,bY);
bY += gravity;
requestAnimationFrame(pajaro);
}
fg.onload = function flor(){
ctx.drawImage(fg,0,cvs.height - fg.height);
}
moveUP();
back();
tuba();
pajaro();
flor();
This can be done with Promise.all. We'll make a new promise for each image we want to load, resolving when onload is called. Once Promise.all resolves, we can call our initialize function and continue on with the rest of our logic. This avoids race conditions where the main game loop's requestAnimationFrame is called from bird.onload, but it's possible that pipe entities and so forth haven't loaded yet.
Here's a minimal, complete example:
const initialize = images => {
// images are loaded here and we can go about our business
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = 400;
canvas.height = 200;
const ctx = canvas.getContext("2d");
Object.values(images).forEach((e, i) =>
ctx.drawImage(e, i * 100, 0)
);
};
const imageUrls = [
"http://placekitten.com/90/100",
"http://placekitten.com/90/130",
"http://placekitten.com/90/160",
"http://placekitten.com/90/190",
];
Promise.all(imageUrls.map(e =>
new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = e;
})
)).then(initialize);
Notice that I used an array in the above example to store the images. The problem this solves is that the
var foo = ...
var bar = ...
var baz = ...
var qux = ...
foo.src = ...
bar.src = ...
baz.src = ...
qux.src = ...
foo.onload = ...
bar.onload = ...
baz.onload = ...
qux.onload = ...
pattern is extremely difficult to manage and scale. If you decide to add another thing into the game, then the code needs to be re-written to account for it and game logic becomes very wet. Bugs become difficult to spot and eliminate. Also, if we want a specific image, we'd prefer to access it like images.bird rather than images[1], preserving the semantics of the individual variables, but giving us the power to loop through the object and call each entity's render function, for example.
All of this motivates an object to aggregate game entities. Some information we'd like to have per entity might include, for example, the entity's current position, dead/alive status, functions for moving and rendering it, etc.
It's also a nice idea to have some kind of separate raw data object that contains all of the initial game state (this would typically be an external JSON file).
Clearly, this can turn into a significant refactor, but it's a necessary step when the game grows beyond small (and we can incrementally adopt these design ideas). It's generally a good idea to bite the bullet up front.
Here's a proof-of-concept illustrating some of the the musings above. Hopefully this offers some ideas for how you might manage game state and logic.
const entityData = [
{
name: "foo",
path: "http://placekitten.com/80/80",
x: 0,
y: 0
},
{
name: "baz",
path: "http://placekitten.com/80/150",
x: 0,
y: 90
},
{
name: "quux",
path: "http://placekitten.com/100/130",
x: 90,
y: 110
},
{
name: "corge",
path: "http://placekitten.com/200/240",
x: 200,
y: 0
},
{
name: "bar",
path: "http://placekitten.com/100/100",
x: 90,
y: 0
}
/* you can add more properties and functions
(movement, etc) to each entity
... try adding more entities ...
*/
];
const entities = entityData.reduce((a, e) => {
a[e.name] = {...e, image: new Image(), path: e.path};
return a;
}, {});
const initialize = () => {
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = innerWidth;
canvas.height = innerHeight;
const ctx = canvas.getContext("2d");
for (const key of Object.keys(entities)) {
entities[key].alpha = Math.random();
}
(function render () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
Object.values(entities).forEach(e => {
ctx.globalAlpha = Math.abs(Math.sin(e.alpha += 0.005));
ctx.drawImage(e.image, e.x, e.y);
ctx.globalAlpha = 1;
});
requestAnimationFrame(render);
})();
};
Promise.all(Object.values(entities).map(e =>
new Promise((resolve, reject) => {
e.image.onload = () => resolve(e.image);
e.image.onerror = () =>
reject(`${e.path} failed to load`)
;
e.image.src = e.path;
})
))
.then(initialize)
.catch(err => console.error(err))
;
I am developing angular application and trying to decode image with QR code on client side only and facing with next errors.
I have next flow:
User uploads image.
I decode qr code from image.
<input type="file" name="file" id="file" accept="image/*"(change)="qrCodeUploaded($event.target.files)"/>
I have tried next libraries:
https://github.com/zxing-js/library
qrCodeUploaded(files: FileList): void {
const fileReader = new FileReader();
const codeReader = new BrowserQRCodeReader();
fileReader.readAsDataURL(files[0]);
fileReader.onload = (e: any) => {
var image = document.createElement("img");
image.src = e.target.result;
setTimeout(() => codeReader.decodeFromImage(image, e.target.result).then(res => console.log(res)), 100);
};
}
Works for some of qr codes, but issues on mobile. If you will take a photo of QR code with your phone, it will be not decoded. So for mobile you will need to implement video.
https://github.com/cozmo/jsQR
qrCodeUploaded(files: FileList): void {
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(files[0]);
fileReader.onload = (e: any) => {
console.log(new Uint8ClampedArray(e.target.result));
console.log(jsQR(new Uint8ClampedArray(e.target.result), 256, 256));
};
}
I get next error for any QR images I upload:
core.js:15714 ERROR Error: Malformed data passed to binarizer.
at Object.binarize (jsQR.js:408)
at jsQR (jsQR.js:368)
gist link:
https://gist.github.com/er-ant/b5c75c822eb085e70035cf333bb0fb55
Please, tell me what I am doing wrong and propose some solution for QR codes decoding. Open for any thoughts :)
For second library I missed that it expects ImageData and I pass Binary Data.
Thus, we have 3 solutions how to convert Binary Data to ImageData:
Use createImageBitmap Kaiido solution with some updates, as proposed in comments doesn't work.
qrCodeUploadedHandler(files: FileList): void {
const file: File = files[0];
createImageBitmap(files[0])
.then(bmp => {
const canvas = document.createElement('canvas');
const width: number = bmp.width;
const height: number = bmp.height;
canvas.width = bmp.width;
canvas.height = bmp.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(bmp, 0, 0);
const qrCodeImageFormat = ctx.getImageData(0,0,bmp.width,bmp.height);
const qrDecoded = jsQR(qrCodeImageFormat.data, qrCodeImageFormat.width, qrCodeImageFormat.height);
});
}
Get ImageData from canvas.
qrCodeUploadedHandler(files: FileList): void {
const file: File = files[0];
const fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(files[0]);
fileReader.onload = (event: ProgressEvent) => {
const img: HTMLImageElement = new Image();
img.onload = () => {
const canvas: HTMLCanvasElement = document.createElement('canvas');
const width: number = img.width;
const height: number = img.height;
canvas.width = width;
canvas.height = height;
const canvasRenderingContext: CanvasRenderingContext2D = canvas.getContext('2d');
console.log(canvasRenderingContext);
canvasRenderingContext.drawImage(img, 0, 0);
const qrCodeImageFormat: ImageData = canvasRenderingContext.getImageData(0, 0, width, height);
const qrDecoded = jsQR(qrCodeImageFormat.data, qrCodeImageFormat.width, qrCodeImageFormat.height);
canvas.remove();
};
img.onerror = () => console.error('Upload file of image format please.');
img.src = (<any>event.target).result;
}
You can parse images with png.js and jpeg-js libraries for ImageData.
step:
Create a new image object from image file
Create a canvas
Draw the image to the canvas
Get ImageData through the context of canvas
Scan QR code with jsQR.
install:
npm install jsqr --save
code:
// utils/qrcode.js
import jsQR from "jsqr"
export const scanQrCode = (file, callback) => {
const image = new Image()
image.src = file.content
image.addEventListener("load", (e) => {
console.log(
"image on load, image.naturalWidth, image.naturalHeight",
image.naturalWidth,
image.naturalHeight
)
const canvas = document.createElement("canvas") // Creates a canvas object
canvas.width = image.naturalWidth // Assigns image's width to canvas
canvas.height = image.naturalHeight // Assigns image's height to canvas
const context = canvas.getContext("2d") // Creates a contect object
context.imageSmoothingEnabled = false
context.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight) // Draws the image on canvas
const imageData = context.getImageData(0, 0, image.naturalWidth, image.naturalHeight) // Assigns image base64 string in jpeg format to a variable
const code = jsQR(imageData.data, image.naturalWidth, image.naturalHeight)
if (code) {
console.log("Found QR code", code)
callback(code)
}
})
}
use:
scanQrCode(file, (code) => {
console.log(code.data);
});