How to get image width and height in a web-worker? - javascript

Here's my code:
// process-image.js (web-worker)
self.addEventListener('message', ev => {
const {id,file} = ev.data;
const reader = new FileReader();
reader.onload = ev => {
const imageFile = new Image(); // <-- error is here
imageFile.src = ev.target.result;
imageFile.onload = () => {
const fit = makeFit(imageFile);
self.postMessage({
id,
file: {
src: ev.target.result,
width: fit.width,
height: fit.height,
}
})
}
};
reader.readAsDataURL(file);
});
This was working fine in the main UI thread, but apparently I don't have access to Image inside of a web-worker. The specific error is:
Uncaught ReferenceError: Image is not defined
at FileReader.reader.onload (process-image.js:12)
Is there another way get the width and height of an image?
I'd like to support as many file types as possible, but just JPG is good enough for now if there's some sort of library that can do this and will run in a web-worker.
Here's the relevant bit from the UI thread:
// some-react-component.js
componentDidMount() {
this.worker = new ImageWorker();
this.worker.addEventListener('message', ev => {
const {id, file} = ev.data;
this.setState(({files}) => {
files = files.update(id, img => ({
...img,
...file,
}))
const withWidths = files.filter(f => f.width);
const avgImgWidth = withWidths.reduce((acc, img) => acc + img.width, 0) / withWidths.size;
return {files, avgImgWidth};
});
});
}
onDrop = ev => {
ev.preventDefault();
Array.prototype.forEach.call(ev.dataTransfer.files, file => {
const id = shortid();
this.worker.postMessage({id, file});
const img = {
id,
width: null,
height: null,
src: null,
name: file.name,
}
this.setState(({files}) => ({files: files.set(id, img)}));
});
}
Only thing worth noting here is that id is just a random UUID, and file is a File. I'm passing the whole thing to the web-worker so that I can do all the processing there.

I think there might be a simpler solution:
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap
I managed to get the size without using FileReader at all:
http://jsfiddle.net/hL1Ljkmv/2/
var response = `self.addEventListener('message', async ev => {
const {id,file} = ev.data;
console.log('received data');
let image = await self.createImageBitmap(file);
console.log(image);
});`;
const blob = new Blob([response], {type: 'application/javascript'});
const worker = new Worker(URL.createObjectURL(blob));
const input = document.querySelector('#file-input');
input.addEventListener('change', (e) => {
worker.postMessage({file: input.files[0], id: 1})
});

I managed to get the width and height of most JPEGs by following this spec.
self.addEventListener('message', ev => {
const {id, file} = ev.data;
const reader = new FileReader();
reader.onload = ev => {
const view = new DataView(reader.result);
let offset = 0;
let header = view.getUint16(offset, false);
if(header !== 0xFFD8) {
throw new Error(`Not a JPEG`);
}
offset += 2;
for(; ;) {
header = view.getUint16(offset, false);
offset += 2;
let length = view.getUint16(offset, false);
if(header === 0xFFC0) break;
offset += length;
}
const height = view.getUint16(offset + 3);
const width = view.getUint16(offset + 5);
const fit = makeFit({width, height});
self.postMessage({
id,
file: {
src: URL.createObjectURL(file),
width: fit.width,
height: fit.height,
}
})
};
reader.readAsArrayBuffer(file);
});
This code is flakey as heck, but surprisingly it works. I'm still looking for a more robust solution.

Related

file gets crashed after resize in front end

For SEO optimizaion I'm attemting to low off the size of the files that the user attempts to send (I know I could have some size limitation or something not doing so because of the UX). and I'm doing it in the front-end cause I want to use pre-signed URL method (AWS S3)
process(event: any, imageInputElement: any, maxWidth: number): any {
return new Promise<any>((resolve, reject) => {
try {
const file = event.target.files[0]
console.log('🚀 ~ file: index.vue ~ line 143 ~ process ~ file', file)
const fileSize = file.size
if (fileSize < 100000) return
if (!file) return
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function (event: any) {
const src = event.target.result
const canvas = document.createElement('canvas') as any
const imgElement = document.createElement('img') as any
imgElement.src = src
imageInputElement.src = event.target?.result
console.log(maxWidth)
imageInputElement.onload = function (e: any) {
const scaleSize = maxWidth / e.target.width
canvas.width = maxWidth
canvas.height = e.target.height * scaleSize
const ctx = canvas.getContext('2d')
ctx.drawImage(e.target, 0, 0, canvas.width, canvas.height)
const compressPer = (data: number) => {
const result = 10000000 / data
if (Math.trunc(result) >= 100) {
return 100
} else if (Math.trunc(result) < 1) {
return 1
} else {
return Math.trunc(result)
}
}
const srcEncoded = ctx.canvas.toDataURL(
e.target,
'image/jpeg',
compressPer(fileSize)
)
const result = new File([srcEncoded], `${file.name}`, {
type: 'image/jpeg',
})
console.log(
'🚀 ~ file: index.vue ~ line 186 ~ process ~ result',
result
)
resolve(result)
}
}
} catch (error: any) {
reject(error)
}
})
},
This function gets called every time the user changes a file input.
event: is the default change event that includes the file itself.
imageInputElement: is the element that I want to render the new file in it. and maxWidth is the width that I pass to the function to specify the max width
The actual problem: the file will become visible in the browser and gets uploaded to the s3 bucket but the file is crashed when I want to download it again.
instead of
const file = event.target.files[0]
I should have used
var blobBin = atob(srcEncoded.split(',')[1]);
var array = [];
for(let i = 0; i < blobBin.length; i++) {
array.push(blobBin.charCodeAt(i));
}
const result:any = new Blob([new Uint8Array(array)], {type: 'image/png'});
got my answer from here

tf.browser.fromPixels returns all zeros from img element

I am using tensorflowjs to do some front-end image classification. I am trying to use tf.browser.fromPixels to convert an img element to a tensor. However, I am getting all zeros of shape [160, 160, 3]. I am using the FileReader api to read an image from the file system via the <input type="file"> element. Here's some of the code:
function getFiles(event) {
const files = event.target.files;
let tempStore = [];
for (let i = 0; i < files.length; ++i) {
tempStore.push(files[i]);
}
return tempStore;
}
const imageElement = document.getElementById("upload");
imageElement.addEventListener("change", event => {
const files = getFiles(event);
Promise.all(files.map(loadImg)).then(d => {
console.log("All done !!!", d);
});
});
const loadImg = imgFile => {
return new Promise((resolve, reject) => {
let reader = new FileReader();
let imgEl = document.createElement("img");
reader.onload = async e => {
imgEl.src = e.target.result;
imgEl.setAttribute("width", 160);
imgEl.setAttribute("height", 160);
document.body.append(imgEl);
const fromPixels = tf.browser.fromPixels(imgEl);
resolve(fromPixels);
};
reader.onerror = reject;
reader.readAsDataURL(imgFile);
});
};
The image gets appended to document body properly.
The imageElement is of the form:
<img src="data:image/jpeg;base64,....." width=160 height=160>
You are creating the tensor from the image when the img tag has not yet been loaded. Here is the way to go
imgEl.src = e.target.result;
imgEl.setAttribute("width", 160);
imgEl.setAttribute("height", 160);
document.body.append(imgEl);
im.onload = () => {
// create the tensor after the image has loaded
const fromPixels = tf.browser.fromPixels(imgEl);
resolve(fromPixels);
}

Map function returns an empty array

I was trying to convert a blob to base64, and I found my way around, but while waiting the result from the function displayBase64String the map function in submitOffre returns an empty string even though console.log prints some data.
I'll appreciate any solution
here is my code.
submitOffre = (saleData) => {
debugger ;
var result = base64Service.displayBase64String(saleData);
console.log("========", result);
const rs = result.map(value => value.file); // Doesn't work.
console.log(rs); // rs is empty
}
class Base64Service {
blobToBase64 = (blob, callback) => {
var reader = new FileReader();
var data = '';
reader.onload = function () {
var dataUrl = reader.result;
var base64 = dataUrl.split(',')[1];
callback(base64);
};
reader.readAsDataURL(blob);
}
displayBase64String(formProps) {
const result = [];
const outbut = Object.entries(formProps.imageToUpload).map(([key, value]) => {
this.blobToBase64(value, (data) => {
result.push({ "file": `data:${value.type};base64,${data}` })
})
});
return result;
};
}
export default new Base64Service();
Something like that might help:
I've modified your code a bit, just to show you the basic pattern.
If you're doing more than 1 image at a time, you will need to use Promise.all, to keep track of more than 1 promise at once.
submitOffre = async (saleData) => { // SEE THE async KEYWORD
debugger ;
var result = await blobToBase64(saleData); // SEE THE await KEYWORD
console.log("========", result);
const rs = result.map(value => value.file); // Doesn't work.
console.log(rs); // rs is empty
}
I'll treat as if you were converting only 1 image.
blobToBase64 = (blob, callback) => new Promise((resolve,reject) => {
var reader = new FileReader();
var data = '';
reader.onload = function () {
var dataUrl = reader.result;
var base64 = dataUrl.split(',')[1];
callback(base64);
resolve(base64); // NOTE THE resolve() FUNCTION TO RETURN SOME VALUE TO THE await
};
reader.readAsDataURL(blob);
});

Extract images from JsZip with correct content type

I'm trying to extract pngs from a zip file to upload to server. My problem is that the blobs I load have the wrong content type so the server rejects them.
Can anyone tell me either how to change the content type on a blob, or extract the files as images
Here is how I extract them
JSZip.loadAsync(file)
.then(function (zip) {
const re = /(.jpg|.png|.gif|.ps|.jpeg)$/
const dataFile = /(.json)$/
const promises = Object.keys(zip.files).filter(function (fileName) {
return re.test(fileName.toLowerCase())
}).map(function (fileName) {
const file = zip.files[fileName]
return file.async('blob').then(function (blob) {
return [
fileName,
URL.createObjectURL(blob),
]
})
})
const jsonPromise = Object.keys(zip.files).filter(function (fileName) {
return dataFile.test(fileName.toLowerCase())
}).map(function (fileName) {
const file = zip.files[fileName]
return file.async('string').then(function (data) {
return [
'data',
data,
]
})
})
promises.push(jsonPromise[0])
return Promise.all(promises)
}).then(function (result) {
return result.reduce(function (acc, val) {
acc[val[0]] = val[1]
return acc
}, {})
}).then((result)=>{
const object = JSON.parse(result.data)
for(let i in object.images)
object.images[i].url = result[object.images[i].name]
resolve(object)
}).catch((err)=>{reject(err)})
})
I then get the blob url and try and upload it
fetch(blobURL).then(res => res.blob()).then((blob)=>{
blob.lastModifiedDate = new Date()
blob.name = 'model_' + id + '_' + uuidv1() + '.png'
But this blob is contentType 'plain/text' when it should be a png.
I drew the blob to an image, converted to canvas and uploaded that.. seems like a inelegant solution tho
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const img = new Image()
img.onload = function () {
canvas.width = this.naturalWidth // update canvas size to match image
canvas.height = this.naturalHeight
ctx.drawImage(this, 0, 0)
uploadFromCanvas(canvas,hotspot).then(resolve)
}
img.src = blobURL
})

how to make the code wait inside the following async/await function?

I want to resize an image before uploading it to the server (with Firebase):
api.uploadPhoto = async (file = {}, field = {}) => {
const canvas = document.getElementById('canvas')
const img = document.createElement('img')
const reader = new FileReader()
let fileToUpload
reader.onload = function (e) {
img.src = e.target.result
pica.resize(img, canvas).then(result => {
fileToUpload = pica.toBlob(result, 'image/jpeg', 90))
})
}
reader.readAsDataURL(file)
// run the code below only when reader.onload has finished
return await imageUpload.toFirebase(fileToUpload, field)
}
The problem is that imageUpload.toFirebase is running before reader.onload. How to fix this?
move the upload in to the callback ...
api.uploadPhoto = async (file = {}, field = {}, callback) => {
const canvas = document.getElementById('canvas');
const img = document.createElement('img');
const reader = new FileReader();
let fileToUpload;
reader.onload = function (e) {
img.src = e.target.result;
pica.resize(img, canvas).then(result => {
fileToUpload = pica.toBlob(result, 'image/jpeg', 90));
});
reader.readAsDataURL(file);
callback(await imageUpload.toFirebase(fileToUpload, field));
}
};

Categories