How to compress Base64/Binary image in Angular6? - javascript

I used Ngx-Webcam for capture image from camera. I want to get both high quality and low quality image from camera
This library return to me Base64 image. It have an option to reduce size imageQuality but I can't use because I need high both quality image and low quality image
let data = webcamImage.imageAsBase64;
const raw = window.atob(data);
const rawLength = raw.length;
const unit8array = new Uint8Array(new ArrayBuffer(rawLength));
for (let i = 0; i < rawLength; i++) {
unit8array[i] = raw.charCodeAt(i);
}
I try to apply https://www.npmjs.com/package/image-conversion for our problem,
let data = webcamImage.imageAsBase64;
const raw = window.atob(data);
let contentType = raw.split(';')[0];
const rawLength = raw.length;
const unit8array = new Uint8Array(new ArrayBuffer(rawLength));
for (let i = 0; i < rawLength; i++) {
unit8array[i] = raw.charCodeAt(i);
}
let blob = new Blob([unit8array], {type: contentType});
imageProcess.compress(blob, 0.4);
But it was not work. I want to find another solution for compress image

I found how to compress image with canvas
compress(webcamImage: WebcamImage, quality: number): Observable<string> {
let _canvas = this.canvas;
let width = webcamImage.width;
let height = webcamImage.height;
_canvas.width = width;
_canvas.height = height;
// paint snapshot image to canvas
const img = new Image();
img.src = webcamImage.imageAsDataUrl;
return Observable.create(observe => {
(img.onload = () => {
const context2d = _canvas.getContext('2d');
context2d.drawImage(img, 0, 0, width, height);
// read canvas content as image
const mimeType: string = webcamImage.mineType;
const dataUrl: string = _canvas.toDataURL(mimeType, quality);
observe.next(dataUrl);
});
});
}

The base64 image was most likely already compressed (jpeg) before it was encoded in base64. You can't further compress such data.
If you need both high and low quality of the image, you should request a high quality (preferrably raw pixels) image from the webcam and transform it into jpeg images with different compression parameters.
If the camera only returns jpeg data, you need to decompress before recompressing with different parameters, which is possible but will consume more time and result in worse quality.
Ngx-webcam is probably a little limiting for this requirement, it might be necessary to look at its code and extend it somewhat to return captured images at different quality levels. There's a captureImageData flag that can be used to retrieve raw image data, but the documentation is a bit thin on how to work with that.

Related

Upload compressed image file from client-side using JavaScript

I am trying to compress images on client side using JavaScript on some low bandwidth devices and I'm currently stuck in a limbo using the HTML5 File API. I'm new to this, please bear with me if I'm missing something important.
I have some input tags which should ideally open the mobile camera, capture single image, compress and send files to the backend. Although this can be done with a single input field with multiple uploads enabled but I need the multiple image fields to segregate images based on some categories.
Here's the input boxes:
<input type="file" name="file1" id="file1" capture="camera" accept="image/*">
<input type="file" name="file2" id="file2" capture="camera" accept="image/*">...
Here's the image compression logic:
// Takes upload element id ("file1") and a maxSize to resize, ideally on a change event
window.resizePhotos = function(id, maxSize){
var file = document.getElementById(id).files[0];
// Ensuring it's an image
if(file.type.match(/image.*/)) {
// Loading the image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Resizing the image and keeping its aspect ratio
var canvas = document.createElement("canvas"),
max_size = maxSize,
width = image.width,
height = image.height;
if (width > height) {
if (width > max_size) {
height *= max_size / width;
width = max_size;
}
} else {
if (height > max_size) {
width *= max_size / height;
height = max_size;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext("2d").drawImage(image, 0, 0, width, height);
var dataUrl = canvas.toDataURL("image/jpeg");
var resizedImage = dataURLToBlob(dataUrl);
$.event.trigger({
type: "imageResized",
blob: resizedImage,
url: dataUrl
});
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(file);
}
};
// Function to convert a canvas to a BLOB
var dataURLToBlob = function(dataURL) {
var BASE64_MARKER = ';base64,';
if (dataURL.indexOf(BASE64_MARKER) == -1) {
var parts = dataURL.split(',');
var contentType = parts[0].split(':')[1];
var raw = parts[1];
return new Blob([raw], {type: contentType});
}
var parts = dataURL.split(BASE64_MARKER);
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], {type: contentType});
}
// Handling image resized events
$(document).on("imageResized", function (event) {
if (event.blob && event.url) {
document.getElementById('file1').files[0] = event.url; // --> Tried this, did not work
document.getElementById('file1').files[0].value = (URL || webkitURL).createObjectURL(event.blob); // --> Tried doing this looking at some other answers but did not work
console.log(document.getElementById('file1').files[0]); // Original file is loading fine
console.log(event.url); // Image compression is working correctly and producing the base64 data
}
});
$(window).on("load", function() {
// Resets the value to when navigating away from the page and choosing to upload the same file (extra feature)
$("#file1").on("click touchstart" , function(){
$(this).val("");
});
// Action triggers when user has selected any file
$("#file1").change(function(e) {
resizePhotos("file1", 1024)
});
});
In PHP script, I'd usually try to catch files from the POST request like:
$file1 = $_FILES["file1"]["tmp_name"];
$file2 = $_FILES["file2"]["tmp_name"];
...
But this doesn't work because it looks for the original user selected file at a tmp directory (e.g. the actual temporary file in my case is C:\xampp\tmp\php25CB.tmp )
One thing I've tried is put the input fields outside of the form tags, enabled the click behaviour using a button and created new input field with the modified data within the form like:
var newinput = document.createElement("input");
newinput.type = 'file';
newinput.name = 'file1';
newinput.files[0] = event.url;
document.getElementById('parentdiv').appendChild(newinput);
Needless to say, this had no effect and the PHP script could not identify any file.
Please guide me and suggest any changes required in the JavaScript/PHP script so I can accept the modified file and not the original user uploaded file from the input field.
You can only change a file input value with another list here is how: https://stackoverflow.com/a/52079109/1008999 (also in the example)
Using the FileReader is a waste of time, CPU, Encoding & decoding and RAM...use URL.createObjectURL instead
Don't use canvas.toDataURL... use canvas.toBlob instead
Canvas have bad compression, read earlier comment and see the jsfiddle proff...If you insist on using canvas to try and squeeze the size down then
First try to see if the image is in a reasonable size first
Compare if the pre existing image file.size is smaller than what the canvas.toBlob provides and choose if you want the old or the new one instead.
If resizing the image isn't enough have a look at this solution that change the quality until a desired file size & image aspect have been meet.
Without any testing, this is how i would have refactor your code too:
/**
* #params {File[]} files Array of files to add to the FileList
* #return {FileList}
*/
function fileListItems (files) {
var b = new ClipboardEvent('').clipboardData || new DataTransfer()
for (var i = 0, len = files.length; i<len; i++) b.items.add(files[i])
return b.files
}
// Takes upload element id ("file1") and a maxSize to resize, ideally on a change event
window.resizePhotos = async function resizePhotos (input, maxSize) {
const file = input.files
if (!file || !file.type.match(/image.*/)) return
const image = new Image()
const canvas = document.createElement('canvas')
const max_size = maxSize
image.src = URL.createObjectURL(file)
await image.decode()
let width = image.width
let height = image.height
// Resizing the image and keeping its aspect ratio
if (width > height) {
if (width > max_size) {
height *= max_size / width
width = max_size
}
} else {
if (height > max_size) {
width *= max_size / height
height = max_size
}
}
canvas.width = width
canvas.height = height
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
const resizedImage = await new Promise(rs => canvas.toBlob(rs, 'image/jpeg', 1))
// PS: You might have to disable the event listener as this might case a change event
// and triggering this function over and over in a loop otherwise
input.files = fileListItems([
new File([resizedImage], file.name, { type: resizedImage.type })
])
}
jQuery($ => {
// Resets the value to when navigating away from the page and choosing to upload the same file (extra feature)
$('#file1').on('click touchstart' , function(){
$(this).val('')
})
// Action triggers when user has selected any file
$('#file1').change(function(e) {
resizePhotos(this, 1024)
})
})

Convert blob to image file

I am trying to convert an image file captured from an input type=file element without success. Below is my javascript code
var img = document.getElementById('myimage');
var file = document.querySelector('input[type=file]').files[0];
var reader = new FileReader();
reader.addEventListener("load", function () {
var theBlob = reader.result;
theBlob.lastModifiedDate = new Date();
theBlob.name = file;
img.src = theBlob;
var newfile = new File([theBlob], "c:files/myfile.jpg");
}, false);
if (file) {
reader.readAsDataURL(file);
}
The image is displayed properly in the img element but the File command does not create the myfile.jpg image file. I am trying to capture the image and then resizing it using javascript before I upload it to the server. Anyone know why the image file is not being created? Better yet, anyone have code on how to resize an image file on the client and then upload it to a server?
This is how you can get a resized jpeg file from your "myimage" element using Canvas.
I commented every line of code so you could understand what I was doing.
// Set the Width and Height you want your resized image to be
var width = 1920;
var height = 1080;
var canvas = document.createElement('canvas'); // Dynamically Create a Canvas Element
canvas.width = width; // Set the width of the Canvas
canvas.height = height; // Set the height of the Canvas
var ctx = canvas.getContext("2d"); // Get the "context" of the canvas
var img = document.getElementById("myimage"); // The id of your image container
ctx.drawImage(img,0,0,width,height); // Draw your image to the canvas
var jpegFile = canvas.toDataURL("image/jpeg"); // This will save your image as a
//jpeg file in the base64 format.
The javascript variable "jpegFile" now contains your image encoded into a URL://Base64 format. Which looks something like this:
// data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...9oADAMBAAIRAxEAPwD/AD/6AP/Z"
You can put that variable as an image source in HTML code and it will display your image in the browser, or you can upload the Base64 encoded string to the server.
Edit: How to convert the file to a blob (binary file) and upload it to the server
// This function is used to convert base64 encoding to mime type (blob)
function base64ToBlob(base64, mime)
{
mime = mime || '';
var sliceSize = 1024;
var byteChars = window.atob(base64);
var byteArrays = [];
for (var offset = 0, len = byteChars.length; offset < len; offset += sliceSize) {
var slice = byteChars.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, {type: mime});
}
Now clean the base64 up and then pass it into the function above:
var jpegFile64 = jpegFile.replace(/^data:image\/(png|jpeg);base64,/, "");
var jpegBlob = base64ToBlob(jpegFile64, 'image/jpeg');
Now send the "jpegBlob" with ajax
var oReq = new XMLHttpRequest();
oReq.open("POST", url, true);
oReq.onload = function (oEvent) {
// Uploaded.
};
oReq.send(jpegBlob);
You can't manipulate hard drive directly from the browser. What you can do though is create a link that can be downloaded.
To do this change your var newfile = new File([theBlob], "c:files/myfile.jpg"); into:
const a = document.createElement('a');
a.setAttribute('download', "some image name");
a.setAttribute('href', theBlob);
a.click();

Javascript - How to reduce image to specific file size?

I am encoding my images in Base64 before uploading them to the server. I need to get their file sizes down to a specific size (500kB). I have tried to get the ratio between the original and the desired file size and reducing de height and width accordingly. But it does not work. Any help?
I had the same problem as you, I needed to resize an image before it was uploaded to my server. So my first thought was to calculate the number of pixels to determine the number of bytes. But very quickly I noticed that most image formats use compression. For example, a 1000x1000 picture with only white pixels will only be a fraction of the size of a normal image. So that’s why I think it’s hard to pre-calculate any dimensions into the number of bytes. So I used the binary search algorithm to try and find the percentage that should be used to reduce the width and height. The image will be resized multiple times to find the perfect percentage so it’s not very efficient but I couldn’t think of any other way.
// maxDeviation is the difference that is allowed default: 50kb
// Example: targetFileSizeKb = 500 then result will be between 450kb and 500kb
// increase the deviation to reduce the amount of iterations.
async function resizeImage(dataUrl, targetFileSizeKb, maxDeviation = 50) {
let originalFile = await urltoFile(dataUrl, 'test.png', 'image/png');
if (originalFile.size / 1000 < targetFileSizeKb)
return dataUrl; // File is already smaller
let low = 0.0;
let middle = 0.5;
let high = 1.0;
let result = dataUrl;
let file = originalFile;
while (Math.abs(file.size / 1000 - targetFileSizeKb) > maxDeviation) {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const img = document.createElement('img');
const promise = new Promise((resolve, reject) => {
img.onload = () => resolve();
img.onerror = reject;
});
img.src = dataUrl;
await promise;
canvas.width = Math.round(img.width * middle);
canvas.height = Math.round(img.height * middle);
context.scale(canvas.width / img.width, canvas.height / img.height);
context.drawImage(img, 0, 0);
file = await urltoFile(canvas.toDataURL(), 'test.png', 'image/png');
if (file.size / 1000 < (targetFileSizeKb - maxDeviation)) {
low = middle;
} else if (file.size / 1000 > targetFileSizeKb) {
high = middle;
}
middle = (low + high) / 2;
result = canvas.toDataURL();
}
return result;
}
function urltoFile(url, filename, mimeType) {
return (fetch(url)
.then(function (res) {
return res.arrayBuffer();
})
.then(function (buf) {
return new File([buf], filename, {type: mimeType});
})
);
}
https://jsfiddle.net/qnhmytk4/3/
https://www.npmjs.com/package/browser-image-compression
This package has options for maxSizeMB, seems pretty legit.
Some other users had the same question. Maybe this helps you:
JavaScript reduce the size and quality of image with based64 encoded code
As far as i'm concerned, there is no way to reduce the filesize of an image on the client side using javascript.

Getting a base64 png from a PDF.JS pdf converted to a canvas tag?

I'm trying to get a base64 encoded png value from a canvas that was created by converting a pdf with the PDF.JS library by Mozilla.
So I had a base64 encoded PDF that I converted into a HTML canvas. I now need to convert that HTML canvas into a base64 encoded png value that I can use with a HTML img tag that displays properly.
I tried the HTMLCanvasElement.toDataURL(), however it doesn't display anything. I tried using the same method with some green boxes drawn in canvas and it worked fine, meaning that the base64 encoded pdf converted to canvas just doesn't want to work with that method.
Any other solutions or a workaround for the solution I tried?
**I need to do this inside my JS code with my HTML.
function convertDataURIToBinary() {
var raw = window.atob(BASE64 OF PDF);
var rawLength = raw.length;
var array = new Uint8Array(new ArrayBuffer(rawLength));
for(var i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
return array;
}
var pdfAsArray = convertDataURIToBinary();
PDFJS.workerSrc = "http://mozilla.github.io/pdf.js/build/pdf.worker.js";
var canvas = _el.querySelector(".insertHere");
PDFJS.getDocument(pdfAsArray).then(function getPdfHelloWorld(pdf) {
pdf.getPage(1).then(function getPageHelloWorld(page) {
var scale = 1.5;
var viewport = page.getViewport(scale);
var context = canvas.getContext("2d");
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: context,
viewport: viewport
};
page.render(renderContext);
canvas = _el.querySelector(".insertHere");
var imgSrc = canvas.toDataURL("image/png");
var img = new Image();
img.src = imgSrc;
_el.querySelector(".insertImageLabel").appendChild(img);
});
});
You are not waiting on the promise returned by render() to finish async operation. See https://github.com/mozilla/pdf.js/blob/master/examples/learning/prevnext.html#L76 how to wait on completion:
...
var renderTask = page.render(renderContext);
// Wait for rendering to finish
renderTask.promise.then(function () {
// Page rendered, now take snapshot.
});
...

Serialize canvas content to ArrayBuffer and deserialize again

I have two canvases, and I want to pass the content of canvas1, serialize it to an ArrayBuffer, and then load it in canvas2. In the future I will send the canvas1 content to the server, process it, and return it to canvas2, but right now I just want to serialize and deserialize it.
I found this way of getting the canvas info in bytes:
var img1 = context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img1.data.length);
for (var i = 0; i < img1.data.length; i++) {
binary[i] = img1.data[i];
}
And also found this way of set the information to a Image object:
var blob = new Blob( [binary], { type: "image/png" } );
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL( blob );
var img = new Image();
img.src = imageUrl;
But unfortunately it doesn't seem to work.
Which would be the right way of doing this?
The ImageData you get from getImageData() is already using an ArrayBuffer (used by the Uint8ClampedArray view). Just grab it and send it:
var imageData = context.getImageData(x, y, w, h);
var buffer = imageData.data.buffer; // ArrayBuffer
To set it again:
var imageData = context.createImageData(w, h);
imageData.data.set(incomingBuffer);
You probably want to consider some form of byte encoding though (such as f.ex base-64) as any byte value above 127 (ASCII) is subject to character encoding used on a system. Or make sure all steps on the trip uses the same (f.ex. UTF8).
Create an ArrayBuffer and send it into to the Uint8Array constructor, then send the buffer using websockets:
var img1 = context.getImageData(0, 0, 400, 320);
var data=img1.data;
var buffer = new ArrayBuffer(data.length);
var binary = new Uint8Array(buffer);
for (var i=0; i<binary.length; i++) {
binary[i] = data[i];
}
websocket.send(buffer);
[ Previous answer using canvas.toDataURL removed ]
Consider using canvas.toBlob() instead of context.getImageData() if you want compact data rather than a raw ImageData object.
Example:
const imageIn = document.querySelector('#image-in');
const imageOut = document.querySelector('#image-out');
const canvas = document.querySelector('#canvas');
const imageDataByteLen = document.querySelector('#imagedata-byte-length');
const bufferByteLen = document.querySelector('#arraybuffer-byte-length');
const mimeType = 'image/png';
imageIn.addEventListener('load', () => {
// Draw image to canvas.
canvas.width = imageIn.width;
canvas.height = imageIn.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(imageIn, 0, 0);
// Convert canvas to ImageData.
const imageData = ctx.getImageData(0, 0, imageIn.width, imageIn.height);
imageDataByteLen.textContent = imageData.data.byteLength + ' bytes.';
// Convert canvas to Blob, then Blob to ArrayBuffer.
canvas.toBlob((blob) => {
const reader = new FileReader();
reader.addEventListener('loadend', () => {
const arrayBuffer = reader.result;
bufferByteLen.textContent = arrayBuffer.byteLength + ' bytes.';
// Dispay Blob content in an Image.
const blob = new Blob([arrayBuffer], {type: mimeType});
imageOut.src = URL.createObjectURL(blob);
});
reader.readAsArrayBuffer(blob);
}, mimeType);
});
<h1>Canvas ↔ ArrayBuffer</h1>
<h2>1. Source <code><img></code></h2>
<img id="image-in" src="https://ucarecdn.com/a0338bfa-9f88-4ce7-b53f-e6b61000df89/" crossorigin="">
<h2>2. Canvas</h2>
<canvas id="canvas"></canvas>
<h2>3. ImageData</h2>
<p id="imagedata-byte-length"></p>
<h2>4. ArrayBuffer</h2>
<p id="arraybuffer-byte-length"></p>
<h2>5. Final <code><img></code></h2>
<img id="image-out">
JSFiddle: https://jsfiddle.net/donmccurdy/jugzk15b/
Also note that images must be hosted on a service that provides CORS headers, or you'll see errors like "The canvas has been tainted by cross-origin data."

Categories