Javascript: Image becomes twice its size when painted on canvas - javascript

I'm uploading an image file (size 368KB) and reading it as a 64 bit data url. The size of the data url is 501999 bytes. When I now paint it on a canvas and try to find the size of the data url of the canvas, I'm getting a size of 1091506 bytes.
I can expect some increase after painting it on canvas, but an image blowing to twice its size is pretty weird. If I use an image of around 230KB, the image blows up to more than 8-9 times its original size. However, if I use an image of around 50KB, the increase in size is only marginal.
What's the reason?
Stackblitz is here
let uploadElement = document.getElementById('upload')
uploadElement.onchange = onImageChange;
function onImageChange(){
let inputFile = event.target.files[0];
let fileReader = new FileReader();
fileReader.onload = function(event){
let imageAsBase64 = event.target.result;
let initialSize = imageAsBase64.length;
console.log(initialSize);
const tempImage = new Image();// <HTMLImageElement>document.getElementById('test');
tempImage.src = imageAsBase64;
tempImage.onload = () => {
const element = document.createElement('canvas');
element.width = tempImage.width;
element.height = tempImage.height;
const ctx = element.getContext('2d');
ctx.drawImage(tempImage, 0, 0, element.width, element.height);
let endSize = element.toDataURL().length;
console.log(endSize);
}
}
fileReader.readAsDataURL(inputFile);
}
<input type="file" id="upload" name="image"/>

What's the reason?
One of the reasons is: You're trying to upload a jpg image, but when you're trying to make a copied version by:
const tempImage = new Image();
it really makes a new image with png format. You can check by downloading that copied version:
document.body.appendChild(tempImage);
That's why you have different sizes.

Related

create image data in javascript

I want to create a black (or white, I really don't care the "colour") 1x1px image in javascript, I tried doing it with var img = new Image(1,1) but I don't know how assigng the source of the image with img.src to be all black (I don't want to use a file, I want to be generated with JS code).
There is a way to do that?
You can also use base64 encoded data in the image.src.
var image = new Image(10, 10);
image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
document.body.appendChild(image);
I found it:
var image = document.createElement('canvas').getContext('2d').getImageData(0,0,1,1);
I'll let question open to know if there is another way to do it.
Sources I used:
CanvasRenderingContext2D.createImageData()
CanvasRenderingContext2D.getImageData()
Similarly you can perform the image manipulation in canvas, then pass a data URI containing a representation of the image over to the HTML image for actual rendering.
const imgWidth = 1;
const canvas = document.createElement("canvas");
canvas.width = imgWidth;
canvas.height = imgWidth;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.fillRect(0,0,imgWidth,imgWidth);
const url = canvas.toDataURL();
const newImg = document.createElement("img");
newImg.src = url;
document.body.insertBefore(newImg, null);

Cropping data:image/png;base64 for large print screens

I'm trying to crop large print screens added in contenteditable by ctrl+v. Have tried several ways but I can't get it to work. The last methode I tried is the drawImage that's why I left it inside the code. Think i'm on the wright direction with it? Or do I need a totally other technique?
I want to use the data:image/png;base64 to store it inside the database. But with very large print screens it's to large to store in the database. Any suggestions? Thanks in advance!
Edit: I just noticed that I need to use blob field in database instead of text. This fixed the issue to store larger screenshots in database. But still do people have any advice for the use of blob and store it in database? I think cropping large screenshots would still be an improvement?
document.onpaste = function(pasteEvent) {
var item = pasteEvent.clipboardData.items[0];
if (item.type.indexOf("image") === 0) {
//pasteEvent.preventDefault();
//alert('You can\'t copy & paste images or screenshots yet.');
var blob = item.getAsFile();
var reader = new FileReader();
var size = pasteEvent.clipboardData.files[0].size;
alert(size);
//var canvas = reader;
//var context = canvas.getContext('2d');
//canvas.width = canvas.height = 64;
//context.drawImage(img, 64,64, 64, 64, 0, 0, 64, 64);
//mod.src = canvas.toDataURL();
reader.onload = function(event) {
document.getElementById("container").src = event.target.result;
};
reader.readAsDataURL(blob);
}
if (item.type.indexOf("text") === 0) {
pasteEvent.preventDefault();
const text = pasteEvent.clipboardData.getData('text');
document.execCommand("insertHTML", false, text);
}
}
I guess these lines of code will work
var canvas = reader;
var context = canvas.getContext('2d');
context.drawImage(img, 0,0, canvas.width, canvas.height);
mod.src = canvas.toDataURL();
Thanks

Why canvas doubles the size of an image?

I was trying to compress an image from file input using canvas.
I read answers here, some say canvas makes the file larger, some use it to compress images, and since I am new to this :
function compressImg(img){
var canvas=document.createElement("canvas");
var ctx=canvas.getContext("2d");
canvas.width=img.width/2;
canvas.height=img.height/2;
ctx.drawImage(img,0,0,canvas.width,canvas.height);
document.getElementById("imagePreview0").src = canvas.toDataURL();
const data = ctx.canvas.toDataURL(img, "image/jpeg", 0.1);
console.log("size2",data.length);
}
var img = new Image();
img.onload = function(){
console.log("size1",file.length);
compressImg(img);
};
img.src = file;
for an image that the mac says 1.7M , i get
size1 2,318,839
size2 4,702,282.
So - can you really compress an image ?
I just need to reduce file size to under 2M .
Change the below line:
const data = ctx.canvas.toDataURL(img, "image/jpeg", 0.1);
To:
const data = canvas.toDataURL("image/jpeg", 0.1);

Reduce size of JSON serialized by fabric.js after image resize

An image is loaded to the fabric canvas using file input and FileReader. We use scaleToWidth and scaleToHeight to have large photos fit to the canvas.
When I use choose a large 3.2MB jpeg the image is nicely resized to 1MB which is what we want. We then prepare the data for storage on local indexed db;
canvas.toJSON(); // 4.2MB
canvas.toDataURL(); // 1MB
It seems that the toJSON method stores the original jpeg. Can we reduce the jpeg prior to serialization ?
I'd prefer to serialize to JSON so we can use other excellent Fabric features in the future.
we have this figured by ;
loading the photo to Fabric.js canvas
then exporting it (which reduces the image to the canvas dimensions) and
reloading reduced image data back on the canvas and
removing the original full size photo.
Now the fabric.js canvas data is nicely reduced for storage in local indexeddb;
// camera image // 3.2 MB
canvas.toJSON(); // 1 MB
canvas.toDataURL(); // 1 MB
javascript
var reader = new FileReader();
reader.onload = function (event) {
var img = new Image();
var opts = {};
img.onload = function () {
var imgInstance = new fabric.Image(img, opts);
if (imgInstance.getWidth() > canvas.getWidth()) {
imgInstance.scaleToWidth(canvas.getWidth());
}
if (imgInstance.getHeight() > canvas.getHeight()) {
imgInstance.scaleToHeight(canvas.getHeight());
}
canvas.add(imgInstance);
canvas.renderAll();
img = null;
/* now that the image is loaded reduce it's size
so the original large image is not stored */
/* assumes photo is object 0, need to code a function to
find the index otherwise */
var photoObjectIx = 0;
var originalPhotoObject = canvas.getObjects()[photoObjectIx];
var nimg = new Image();
nimg.onload = function () {
var imgInstance = new fabric.Image(nimg, { selectable: false });
canvas.remove(originalPhotoObject);
canvas.add(imgInstance);
canvas.renderAll();
nimg = null;
};
nimg.src = originalPhotoObject.toDataURL();
}
img.src = event.target.result;
}
reader.readAsDataURL(e.target.files[0]);
I personally compress big json data and then decompress it on the server...
Deflate in JS - nice script to gzdeflate (compress) JSON.
and then... in PHP:
<?php
$json = gzinflate($HTTP_RAW_POST_DATA);
?>

HTML5 - resize image and keep EXIF in resized image

How can I resize an image (using an HTML5 canvas element) and keep the EXIF information from the original image? I can extract EXIF info from from original image but I don't know how to copy it to the resized image.
This is how I retrieve the resized image data to send to the server-side code:
canvas.toDataURL("image/jpeg", 0.7);
For EXIF retrieval, I'm using the exif.js library.
Working solution: ExifRestorer.js
Usage with HTML5 image resize:
function dataURItoBlob(dataURI)
{
var binary = atob(dataURI.split(',')[1]);
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}
And main code, taken as part of HTML5 resizer from this page: https://github.com/josefrichter/resize/blob/master/public/preprocess.js (but slightly modified)
var reader = new FileReader();
//reader.readAsArrayBuffer(file); //load data ... old version
reader.readAsDataURL(file); //load data ... new version
reader.onload = function (event) {
// blob stuff
//var blob = new Blob([event.target.result]); // create blob... old version
var blob = dataURItoBlob(event.target.result); // create blob...new version
window.URL = window.URL || window.webkitURL;
var blobURL = window.URL.createObjectURL(blob); // and get it's URL
// helper Image object
var image = new Image();
image.src = blobURL;
image.onload = function() {
// have to wait till it's loaded
var resized = ResizeImage(image); // send it to canvas
resized = ExifRestorer.restore(event.target.result, resized); //<= EXIF
var newinput = document.createElement("input");
newinput.type = 'hidden';
newinput.name = 'html5_images[]';
newinput.value = resized; // put result from canvas into new hidden input
form.appendChild(newinput);
};
};
You can use copyExif.js.
This module is more efficient than Martin's solution and uses only Blob and ArrayBuffer without Base64 encoder/decoder.
Besides, there is no need to use exif.js if you only want to keep EXIF. Just copy the entire APP1 marker from the original JPEG to the destination canvas blob and it would just work. It is also how copyExif.js does.
Usage
demo: https://codepen.io/tonytonyjan/project/editor/XEkOkv
<input type="file" id="file" accept="image/jpeg" />
import copyExif from "./copyExif.js";
document.getElementById("file").onchange = async ({ target: { files } }) => {
const file = files[0],
canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d");
ctx.drawImage(await blobToImage(file), 0, 0, canvas.width, canvas.height);
canvas.toBlob(
async blob =>
document.body.appendChild(await blobToImage(await copyExif(file, blob))),
"image/jpeg"
);
};
const blobToImage = blob => {
return new Promise(resolve => {
const reader = new FileReader(),
image = new Image();
image.onload = () => resolve(image);
reader.onload = ({ target: { result: dataURL } }) => (image.src = dataURL);
reader.readAsDataURL(blob);
});
};
It looks my code is used in 'ExifRestorer.js'...
I've try resizing image by canvas. And I felt that resized image is bad quality. If you felt so, too, try my code. My code resizes JPEG by bilinear interpolation. Of course it doesn't lose exif.
https://github.com/hMatoba/JavaScript-MinifyJpegAsync
function post(data) {
var req = new XMLHttpRequest();
req.open("POST", "/jpeg", false);
req.setRequestHeader('Content-Type', 'image/jpeg');
req.send(data.buffer);
}
function handleFileSelect(evt) {
var files = evt.target.files;
for (var i = 0, f; f = files[i]; i++){
var reader = new FileReader();
reader.onloadend = function(e){
MinifyJpegAsync.minify(e.target.result, 1280, post);
};
reader.readAsDataURL(f);
}
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
Canvas generates images with 20 bytes header (before jpeg data segments start). You can slice head with exif segments from original file and replace first 20 bytes in resized one.

Categories

Resources