I am trying to get the value of some array elements. It works for the elements [0], [1], [2], [3], but not [4].
function getBase64() {
const urls = ['https://i.imgur.com/egNg7JU.jpg',
'https://i.imgur.com/RLZ7WH1.jpg', 'https://i.imgur.com/qfabBbA.jpg',
'https://i.imgur.com/Zuh1KaX.jpg', 'https://i.imgur.com/yD7X6Q1.jpg'
];
let base64urls = [];
const start = async () => {
await asyncForEach(urls, async (num) => {
await waitFor(50)
toDataURL(num, function(dataURL) {
base64urls.push(dataURL);
});
})
console.log(base64urls);
console.log(base64urls[4]);
}
start()
}
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
const waitFor = (ms) => new Promise(r => setTimeout(r, ms))
toDataURL simply returns the base64 value of an image. Whenever I try console.log(base64urls[4]), it returns 'undefined'. I do get the correct value for the previous elements. Is there some way to restructure this, or perhaps use a different method of waiting for the array to completely populate before checking for the values of its elements?
EDIT
Here is my toDataURL
function toDataURL(src, callback) {
const image = new Image();
image.crossOrigin = 'Anonymous';
image.onload = function () {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = this.naturalHeight;
canvas.width = this.naturalWidth;
context.drawImage(this, 0, 0);
const dataURL = canvas.toDataURL('image/jpeg');
callback(dataURL);
};
image.src = src;
}
It looks like toDataURL is asynchronous and callback-based - either change it so that it returns a Promise and await that Promise, or pass a Promise's resolve into the callback:
async function getBase64() {
const urls = ['https://i.imgur.com/egNg7JU.jpg',
'https://i.imgur.com/RLZ7WH1.jpg', 'https://i.imgur.com/qfabBbA.jpg',
'https://i.imgur.com/Zuh1KaX.jpg', 'https://i.imgur.com/yD7X6Q1.jpg'];
const base64urls = [];
for (const url of urls) {
const dataURL = await new Promise(resolve => toDataURL(url, resolve));
base64urls.push(dataURL);
}
console.log(base64urls);
console.log(base64urls[4]);
}
If you want to change your toDataURL function to return a Promise so you don't have to treat it like a callback:
function toDataURL(src) {
return new Promise(resolve => {
const image = new Image();
image.crossOrigin = 'Anonymous';
image.onload = function () {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = this.naturalHeight;
canvas.width = this.naturalWidth;
context.drawImage(this, 0, 0);
const dataURL = canvas.toDataURL('image/jpeg');
resolve(dataURL);
};
image.src = src;
});
}
and then const dataURL = await toDataURL(url)
You can use promise.all for this kind of situation to wait for the results of your queries
const urls = ['https://i.imgur.com/egNg7JU.jpg',
'https://i.imgur.com/RLZ7WH1.jpg', 'https://i.imgur.com/qfabBbA.jpg',
'https://i.imgur.com/Zuh1KaX.jpg', 'https://i.imgur.com/yD7X6Q1.jpg'];
let base64urls = [];
Promise.all(urls.map(url => fetch(url))).then(res => toBase64DataURL(res)).then(result => {base64urls.push(result.toDataURL());
console.log(base64urls);});
function toBase64DataURL(src) {
return new Promise(resolve => {
const image = new Image();
image.crossOrigin = 'Anonymous';
image.onload = _=> {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = this.naturalHeight;
canvas.width = this.naturalWidth;
context.drawImage(this, 0, 0);
const dataURL = canvas.toDataURL('image/jpeg');
resolve(dataURL);
};
image.src = src;
});
}
Related
I'm using face-api.js Javascript API to develop a web app that user uploads her/his picture and we want to detect faces in the picture.
On the other hand I used VGGface 16 model json formatted to predict that user uploaded image is similar to which celebrity.
following are my javascript codes:
const MODEL_URL = '../faceapi_models'
Promise.all([
faceapi.nets.ssdMobilenetv1.loadFromUri(MODEL_URL),
faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL),
// faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL),
])
.then((val) => {
console.log('val')
})
.catch((err) => {
console.log(err)
})
let model
async function loadModel() {
model = await tf.loadLayersModel('../web_model/vgg_model.json');
}
loadModel()
.then((val) => {
console.log('Model is Loaded');
})
.catch((err) => {
console.log('Model Not Load : ' + err)
})
let croppedImage = null;
const user_pic = document.getElementById('user_pic')
const preview = document.getElementById('preview')
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
window.onload = function() {
canvas.width = preview.width;
canvas.height = preview.height;
ctx.drawImage(preview, 0, 0);
};
preview.onclick = () => user_pic.click()
user_pic.addEventListener('change', () => {
const reader = new FileReader()
reader.onload = (e) => {
const img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
};
img.src = e.target.result;
}
reader.readAsDataURL(user_pic.files[0]);
detectFaces(user_pic.files[0])
})
async function detectFaces(input) {
let imgURL = URL.createObjectURL(input)
const imgElement = new Image()
imgElement.src = imgURL
const results = await faceapi.detectAllFaces(imgElement)
.then(results => {
if (Array.isArray(results) && results.forEach) {
results.forEach(result => {
const { x, y, width, height } = result.box;
const crop = ctx.getImageData(x, y, width, height);
croppedImage = new ImageData(crop.data, width, height);
const input = tf.browser.fromPixels(croppedImage);
const resizedImage = tf.image.resizeBilinear(input, [224, 224]);
const inputTensor = resizedImage.expandDims(0);
const predictions = model.predict(inputTensor).data();
const celebrityIndex = predictions.indexOf(Math.max(...predictions));
console.log(celebrityIndex)
// const celebrityName = celebrityLabels[celebrityIndex];
// Display the results
// const resultDisplay = document.getElementById('result');
// resultDisplay.innerHTML = `Most similar celebrity: ${celebrityName}`;
});
} else {
console.error('Results is not an array or does not have a forEach function.');
}
});
}
I have solve many problem that I have yet but I do not know how to handle this problem and Why did this problem arise?
This is complete erorr :
I found that problem is with x, y, width, height constants that had float values. a simple way is converting those to Int like this :
const {x, y, width, height} = result.box;
const xInt = Math.floor(x);
const yInt = Math.floor(y);
const widthInt = Math.floor(width);
const heightInt = Math.floor(height);
I have this promise based function and im trying to update it to have the same functionality but use RXJS, im a bit new to rxjs and having a lot of trouble.. any help would be really appreciated
public getBase64ImageFromURL(url: string): Promise<any> {
return new Promise((resolve, reject) => {
const img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL('image/png');
resolve(dataURL);
};
img.onerror = error => {
reject(error);
};
img.src = url;
});
}
It's pretty straight forward : create a new Observable and next the same value as the resolve !
public getBase64ImageFromURL(url: string): Observable<string> {
return new Observable<string>((subscriber) => {
const img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL('image/png');
subscriber.next(dataURL);
};
img.onerror = (error) => {
subscriber.error(error);
};
img.src = url;
});
}
I'm trying to make a download button and want the images to be downloadable. However, I'm facing trouble as two of my function toBlob and save functions are being told to be undefined.
What am I doing wrong here? I'm using Vue.
methods: {
async downloadImage(val) {
const blob = await toBlob(src);
save(blob, 'image.jpg');
},
toBlob(src) {
new Promise((res) => {
const img = document.createElement("img");
const c = document.createElement("canvas");
const ctx = c.getContext("2d");
img.onload = ({ target }) => {
c.width = target.naturalWidth;
c.height = target.naturalHeight;
ctx.drawImage(target, 0, 0);
c.toBlob((b) => res(b), "image/jpeg", 0.5);
};
img.crossOrigin = "";
img.src = src;
});
save = (blob, name = "image.jpg") => {
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.target = "_blank";
a.download = name;
a.click();
};
},
}
You can't reference fields defined in an object from that object. I.e., you can't do:
let x = {
y: 0,
z: y + 1,
};
The simplest alternative is to define the reused (or all) fields outside the object:
let y = 0;
let x = {
y,
z: y + 1
};
OR
let y = 0;
let z = y + 1;
let x = {y, z};
You also have some minor syntax errors. E.g., you can't have {x: 3;}.
For your example, this would look like:
let downloadImage = async val => {
const blob = await toBlob(src);
save(blob, 'image.jpg');
};
let toBlob = async src => {
new Promise((res) => {
const img = document.createElement("img");
const c = document.createElement("canvas");
const ctx = c.getContext("2d");
img.onload = ({target}) => {
c.width = target.naturalWidth;
c.height = target.naturalHeight;
ctx.drawImage(target, 0, 0);
c.toBlob((b) => res(b), "image/jpeg", 0.5);
};
img.crossOrigin = "";
img.src = src;
})
};
let save = (blob, name = "image.jpg") => {
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.target = "_blank";
a.download = name;
a.click();
};
let obj = {methods: {downloadImage, toBlob, save}}
To call other methods in the Vue instance you use this.
And you should also be returning the Promise from toBlob
methods: {
async downloadImage(val) {
const blob = await this.toBlob(src);
this.save(blob, 'image.jpg');
},
toBlob(src) {
return new Promise((res) => {
const img = document.createElement("img");
const c = document.createElement("canvas");
const ctx = c.getContext("2d");
img.onload = ({
target
}) => {
c.width = target.naturalWidth;
c.height = target.naturalHeight;
ctx.drawImage(target, 0, 0);
c.toBlob((b) => res(b), "image/jpeg", 0.5);
};
img.crossOrigin = "";
img.src = src;
});
}
save(blob, name = "image.jpg") {
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.target = "_blank";
a.download = name;
a.click();
}
}
}
I am trying to create an image compressor without any third-party library.
Here is my code(it will finally returns the blob of compressed image):
async function CompressImage(File, Quality, FileType) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var img = new Image;
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
canvas.toBlob(function (blob) {
return blob;
}, FileType, Quality);
}
}
img.src = URL.createObjectURL(File);
}
It seems there is nothing wrong with my code while always returns an undefined value.
I found out maybe it is the asynchronous problem. The img.onload and the canvas.toBlob are asynchronous methods.
I think the promise may help this while I don't know how to order these methods run one by one and finally returns a value.
How can I solve this? Thank you.
Use a Promise instead of async, in the end is the same but syntactically are not, in a Promise you get 2 functions as parameters (typically called resolve and reject), these functions can be run inside any callback no problem, but in an async function becomes problematic because there's no way to await for a function that receives a callback (because is not a Promise that you can await).
TLDR;
async functions do not play well executing functions that run callbacks, use a Promise directly so you can run resolve() anywhere, even if is on a callback function.
For example:
function CompressImage(File, Quality, FileType) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var img = new Image;
var prom = new Promise(function(resolve) {
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
canvas.toBlob(function (blob) {
return resolve(blob);
}, FileType, Quality);
}
}
img.src = URL.createObjectURL(File);
});
return prom;
}
That should do the trick, look how I'm creating the promise to resolve it with the callback of canvas.toBlob, if you notice, the function is not async because is returning a Promise directly, but in your code, you can treat it just like an async function.
Another way to do it with a little more updated syntax can be:
const CompressImage = async (File, Quality, FileType) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image;
return await (new Promise(resolve => {
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
canvas.toBlob(resolve, FileType, Quality);
}
}
img.src = URL.createObjectURL(File);
}));
};
And should be the same.
I haven't tested this but if is broken is pretty close.
UPDATE:
Well, after playing with the jsfiddle you provided I have an interesting finding:
$(function () {
$("#FileUploader").change(function () {
StartCompress(this.files[0]);
});
function StartCompress(input) {
$("#beforesize").text(input.size);
CompressFile(input, 0.8).then(blob => $("#afteresize").text(blob.size));
}
function CompressFile(file, quality) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
var prom = new Promise(resolve => {
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
canvas.toBlob(blob => {
resolve(blob)
}, file.type, quality);
}
img.src = URL.createObjectURL(file);
});
return prom;
}
});
I modified a little bit the code so my ocd can be in peace hahaha, but also, the problem is the MIME (FileType), because you are not providing canvas with the file type, is just grabbing it and converting it to png and that's a lossless format, so it will be bigger if you are selecting a jpg image (that allows loss) and basically scaling it to fit the same size.
Try the code now (I'm using file.type to provide the type of the file to canvas.toBlob), It goes to hell with png and I suppose is related to the issue I pointed out.
Hope it helps.
The method below is being used as a parameter for another variable. But the problem here is that it is not returning the file back after it is being compressed.
async CompressImage(imageFile: File): Promise<File> {
return new Promise<File>((resolve, reject) => {
const cReader = new FileReader();
const image = new Image();
cReader.onload = () => {
image.src = cReader.result.toString();
image.onload = () => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
context.drawImage(image, 0, 0);
//width & height initialization
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, width, height);
const convertedFile = canvas.toBlob((blob) => {
const scaledDown = new File([blob], imageFile.name);
});
resolve(convertedFile);
}
};
cReader.readAsDataURL(mediaFile);
});
}