Save to Local File from Blob - javascript

I have a difficult question to you, which i'm struggling on for some time now.
I'm looking for a solution, where i can save a file to the users computer, without the local storage, because local storage has 5MB limit. I want the "Save to file"-dialog, but the data i want to save is only available in javascript and i would like to prevent sending the data back to the server and then send it again.
The use-case is, that the service im working on is saving compressed and encrypted chunks of the users data, so the server has no knowledge whats in those chunks and by sending the data back to the server, this would cause 4 times traffic and the server is receiving the unencrypted data, which would render the whole encryption useless.
I found a javascript function to save the data to the users computer with the "Save to file"-dialog, but the work on this has been discontinued and isnt fully supported. It's this: http://www.w3.org/TR/file-writer-api/
So since i have no window.saveAs, what is the way to save data from a Blob-object without sending everything to the server?
Would be great if i could get a hint, what to search for.
I know that this works, because MEGA is doing it, but i want my own solution :)

Your best option is to use a blob url (which is a special url that points to an object in the browser's memory) :
var myBlob = ...;
var blobUrl = URL.createObjectURL(myBlob);
Now you have the choice to simply redirect to this url (window.location.replace(blobUrl)), or to create a link to it. The second solution allows you to specify a default file name :
var link = document.createElement("a"); // Or maybe get it from the current document
link.href = blobUrl;
link.download = "aDefaultFileName.txt";
link.innerHTML = "Click here to download the file";
document.body.appendChild(link); // Or append it whereever you want

FileSaver.js implements saveAs for certain browsers that don't have it
https://github.com/eligrey/FileSaver.js
Tested with FileSaver.js 1.3.8 tested on Chromium 75 and Firefox 68, neither of which have saveAs.
The working principle seems to be to just create an <a element and click it with JavaScript oh the horrors of the web.
Here is a demo that save a blob generated with canvas.toBlob to your download folder with the chosen name mypng.png:
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
var pixel_size = 1;
function draw() {
console.log("draw");
for (x = 0; x < canvas.width; x += pixel_size) {
for (y = 0; y < canvas.height; y += pixel_size) {
var b = 0.5;
ctx.fillStyle =
"rgba(" +
(x / canvas.width) * 255 + "," +
(y / canvas.height) * 255 + "," +
b * 255 +
",255)"
;
ctx.fillRect(x, y, pixel_size, pixel_size);
}
}
canvas.toBlob(function(blob) {
saveAs(blob, 'mypng.png');
});
}
window.requestAnimationFrame(draw);
<canvas id="my-canvas" width="512" height="512" style="border:1px solid black;"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js"></script>
Here is an animated version that downloads multiple images: Convert HTML5 Canvas Sequence to a Video File
See also:
how to save canvas as png image?
JavaScript: Create and save file

HERE is the direct way.
canvas.toBlob(function(blob){
console.log(typeof(blob)) //let you have 'blob' here
var blobUrl = URL.createObjectURL(blob);
var link = document.createElement("a"); // Or maybe get it from the current document
link.href = blobUrl;
link.download = "image.jpg";
link.innerHTML = "Click here to download the file";
document.body.appendChild(link); // Or append it whereever you want
document.querySelector('a').click() //can add an id to be specific if multiple anchor tag, and use #id
}, 'image/jpeg', 1); // JPEG at 100% quality
spent a while to come upto this solution, comment if this helps.
Thanks to Sebastien C's answer.

this node dependence was more utils fs-web;
npm i fs-web
Usage
import * as fs from 'fs-web';
async processFetch(url, file_path = 'cache-web') {
const fileName = `${file_path}/${url.split('/').reverse()[0]}`;
let cache_blob: Blob;
await fs.readString(fileName).then((blob) => {
cache_blob = blob;
}).catch(() => { });
if (!!cache_blob) {
this.prepareBlob(cache_blob);
console.log('FROM CACHE');
} else {
await fetch(url, {
headers: {},
}).then((response: any) => {
return response.blob();
}).then((blob: Blob) => {
fs.writeFile(fileName, blob).then(() => {
return fs.readString(fileName);
});
this.prepareBlob(blob);
});
}
}

From a file picker or input type=file file chooser, save the filename to local storage:
HTML:
<audio id="player1">Your browser does not support the audio element</audio>
JavaScript:
function picksinglefile() {
var fop = new Windows.Storage.Pickers.FileOpenPicker();
fop.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.musicLibrary;
fop.fileTypeFilter.replaceAll([".mp3", ".wav"]);
fop.pickSingleFileAsync().then(function (file) {
if (file) {
// save the file name to local storage
localStorage.setItem("alarmname$", file.name.toString());
} else {
alert("Operation Cancelled");
}
});
}
Then later in your code, when you want to play the file you selected, use the following, which gets the file using only it's name from the music library. (In the UWP package manifest, set your 'Capabilites' to include 'Music Library'.)
var l = Windows.Storage.KnownFolders.musicLibrary;
var f = localStorage.getItem("alarmname$").toString(); // retrieve file by name
l.getFileAsync(f).then(function (file) {
// storagefile file is available, create URL from it
var s = window.URL.createObjectURL(file);
var x = document.getElementById("player1");
x.setAttribute("src", s);
x.play();
});

Related

Convert local image blob to base64, in PHP

I'm working on an (HTML) form for an internal tool. Users can fill data out about an issue and attach screenshots. This form is then submitted via ajax to PHPMailer to be sent. The issue is with the screenshots. Due to system limitations I'm unable to have the users actually upload the files to the server.
Currently, I'm using HTML5 filereader to select the files. I then convert the image blobs to base64 and send them to PHPMailer, to be converted to attachments. This is actually working great. However, I'm running into file size issues. Specifically a 1000px x 1000px (402KB) test image. The resulting base64 string is over a million characters long and the request is returning 413 (Request Entity Too Large).
I understand that base64 is not an efficient method for transferring large images and I've seen various posts about retrieving / converting image blobs from a database. What I haven't been able to find is info on retrieving a local image blob and converting it to base64.
My image blob URLs look like this:
blob:http://example.com/18960927-e220-4417-93a4-edb608e5b8b3
Is it possible to grab this local image data in PHP and then convert it to base64?
I cannot post much of the source but, the following will give you an idea of how I am using filereader
window.onload=function(){
window.URL = window.URL || window.webkitURL;
var fileSelect = document.getElementById("fileSelect"),
fileElem = document.getElementById("fileElem"),
fileList = document.getElementById("fileList");
fileSelect.addEventListener("click", function (e) {
if (fileElem) {
fileElem.click();
}
e.preventDefault(); // prevent navigation to "#"
}, false);
}
function handleFiles(files) {
if (!files.length) {
fileList.innerHTML = "<p>No files selected!</p>";
} else {
fileList.innerHTML = "";
var list = document.createElement("ul");
fileList.appendChild(list);
for (var i = 0; i < files.length; i++) {
if(files[i].size > 1000000) {
alert(files[i].name + ' is too big. Please resize it and try again.');
} else {
var li = document.createElement("li");
list.appendChild(li);
var img = document.createElement("img");
img.src = window.URL.createObjectURL(files[i]);
img.height = 60;
img.setAttribute("class", "shotzPrev");
img.onload = function() {
window.URL.revokeObjectURL(this.src);
}
li.appendChild(img);
var info = document.createElement("span");
info.innerHTML = files[i].name + "<br>" + files[i].size + " bytes";
li.appendChild(info);
}
}
}
}
You can POST the File object to php
fetch("/path/to/server", {
method: "POST"
body: files[i]
})
.then(response => console.log(response.ok))
.catch(err => console.error(err));
I think its nginx error, please change the client_max_body_size value in nginx.conf file.
for example :
# set client body size to 2M #
client_max_body_size 2M;
PHP configuration (optional)
Your php installation also put limits on upload file size. Edit php.ini and set the following directives.
;This sets the maximum amount of memory in bytes that a script is allowed to allocate
memory_limit = 32M
;The maximum size of an uploaded file.
upload_max_filesize = 2M
;Sets max size of post data allowed. This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize
post_max_size = 3M

Client download of a server generated zip file

Before somebody says, "duplicate", I just want to make sure, that folks know, that I have already reviewed these questions:
1) Uses angular and php, not sure what is happening here (I don't know PHP): Download zip file and trigger "save file" dialog from angular method
2) Can't get this answer to do anything: how to download a zip file using angular
3) This person can already download, which is past the point I'm trying to figure out:
Download external zip file from angular triggered on a button action
4) No answer for this one:
download .zip file from server in nodejs
5) I don't know what language this even is:
https://stackoverflow.com/questions/35596764/zip-file-download-using-angularjs-directive
Given those questions, if this is still a duplicate, I apologize. Here is, yet, another version of this question.
My angular 1.5.X client gives me a list of titles, of which each have an associated file. My Node 4.X/Express 4.X server takes that list, gets the file locations, creates a zip file, using express-zip from npm, and then streams that file back in the response. I then want my client to initiate the browser's "download a file" option.
Here's my client code (Angular 1.5.X):
function bulkdownload(titles){
titles = titles || [];
if ( titles.length > 0 ) {
$http.get('/query/bulkdownload',{
params:{titles:titles},
responseType:'arraybuffer'
})
.then(successCb,errorCb)
.catch(exceptionCb);
}
function successCb(response){
// This is the part I believe I cannot get to work, my code snippet is below
};
function errorCb(error){
alert('Error: ' + JSON.stringify(error));
};
function exceptionCb(ex){
alert('Exception: ' + JSON.stringify(ex));
};
};
Node (4.X) code with express-zip, https://www.npmjs.com/package/express-zip:
router.get('/bulkdownload',function(req,resp){
var titles = req.query.titles || [];
if ( titles.length > 0 ){
utils.getFileLocations(titles).
then(function(files){
let filename = 'zipfile.zip';
// .zip sets Content-Type and Content-disposition
resp.zip(files,filename,console.log);
},
_errorCb)
}
});
Here's my successCb in my client code (Angular 1.5.X):
function successCb(response){
var URL = $window.URL || $window.webkitURL || $window.mozURL || $window.msURL;
if ( URL ) {
var blob = new Blob([response.data],{type:'application/zip'});
var url = URL.createObjectURL(blob);
$window.open(url);
}
};
The "blob" part seems to work fine. Checking it in IE's debugger, it does look like a file stream of octet information. Now, I believe I need to get that blob into the some HTML5 directive, to initiate the "Save File As" from the browser. Maybe? Maybe not?
Since 90%+ of our users are using IE11, I test all of my angular in PhantomJS (Karma) and IE. When I run the code, I get the old "Access is denied" error in an alert window:
Exception: {"description":"Access is denied...<stack trace>}
Suggestions, clarifications, answers, etc. are welcome!
Use this one:
var url="YOUR ZIP URL HERE";
window.open(url, '_blank');
var zip_file_path = "" //put inside "" your path with file.zip
var zip_file_name = "" //put inside "" file name or something
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = zip_file_path;
a.download = zip_file_name;
a.click();
document.body.removeChild(a);
As indicated in this answer, I have used the below Javascript function and now I am able to download the byte[] array content successfully.
Function to convert byte array stream (type of string) to blob object:
var b64toBlob = function(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.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);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
};
An this is how I call this function and save the blob object with FileSaver.js (getting data via Angular.js $http.get):
$http.get("your/api/uri").success(function(data, status, headers, config) {
//Here, data is type of string
var blob = b64toBlob(data, 'application/zip');
var fileName = "download.zip";
saveAs(blob, fileName);
});
Note: I am sending the byte[] array (Java-Server-Side) like this:
byte[] myByteArray = /*generate your zip file and convert into byte array*/ new byte[]();
return new ResponseEntity<byte[]>(myByteArray , headers, HttpStatus.OK);
I updated my bulkdownload method to use $window.open(...) instead of $http.get(...):
function bulkdownload(titles){
titles = titles || [];
if ( titles.length > 0 ) {
var url = '/query/bulkdownload?';
var len = titles.length;
for ( var ii = 0; ii < len; ii++ ) {
url = url + 'titles=' + titles[ii];
if ( ii < len-1 ) {
url = url + '&';
}
}
$window.open(url);
}
};
I have only tested this in IE11.

Load file into IMAGE object using Phantom.js

I'm trying to load image and put its data into HTML Image element but without success.
var fs = require("fs");
var content = fs.read('logo.png');
After reading content of the file I have to convert it somehow to Image or just print it to canvas. I was trying to conver binary data to Base64 Data URL with the code I've found on Stack.
function base64encode(binary) {
return btoa(unescape(encodeURIComponent(binary)));
}
var base64Data = 'data:image/png;base64,' +base64encode(content);
console.log(base64Data);
Returned Base64 is not valid Data URL. I was trying few more approaches but without success. Do you know the best (shortest) way to achieve that?
This is a rather ridiculous workaround, but it works. Keep in mind that PhantomJS' (1.x ?) canvas is a bit broken. So the canvas.toDataURL function returns largely inflated encodings. The smallest that I found was ironically image/bmp.
function decodeImage(imagePath, type, callback) {
var page = require('webpage').create();
var htmlFile = imagePath+"_temp.html";
fs.write(htmlFile, '<html><body><img src="'+imagePath+'"></body></html>');
var possibleCallback = type;
type = callback ? type : "image/bmp";
callback = callback || possibleCallback;
page.open(htmlFile, function(){
page.evaluate(function(imagePath, type){
var img = document.querySelector("img");
// the following is copied from http://stackoverflow.com/a/934925
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to
// guess the original format, but be aware the using "image/jpg"
// will re-encode the image.
window.dataURL = canvas.toDataURL(type);
}, imagePath, type);
fs.remove(htmlFile);
var dataUrl = page.evaluate(function(){
return window.dataURL;
});
page.close();
callback(dataUrl, type);
});
}
You can call it like this:
decodeImage('logo.png', 'image/png', function(imgB64Data, type){
//console.log(imgB64Data);
console.log(imgB64Data.length);
phantom.exit();
});
or this
decodeImage('logo.png', function(imgB64Data, type){
//console.log(imgB64Data);
console.log(imgB64Data.length);
phantom.exit();
});
I tried several things. I couldn't figure out the encoding of the file as returned by fs.read. I also tried to dynamically load the file into the about:blank DOM through file://-URLs, but that didn't work. I therefore opted to write a local html file to the disk and open it immediately.

Using PDFkit in browser, inserting an image from a link

Is there a simple way to get an image from a url to put in a PDFKit pdf?
I have a PDF being automatically generated in-browser. There's an image I want included, to which I have a URL. The catch is that I'm generating the PDF in-browser. Since I have the URL available from the internet, it seems like there should be an easy way to turn that image into something readable by PDFKit.
Is there a way for Javascript to turn an image URL into a buffer readable by PDFKit?
What I want is what you'd like the following command to do:
doc.image('http://upload.wikimedia.org/wikipedia/commons/0/0c/Cow_female_black_white.jpg')
Thanks in advance. The solutions I found online have your server take in the link, and respond with a buffer. Is this the only way? Or is there a way all in-browser with no http posting?
This is a pretty old question but I'll add my notes since it's the first suggestion when looking for "pdfkit browser image" on Google.
I based my solution on the data uri option supported by PDFKit:
Just pass an image path, buffer, or data uri with base64 encoded data
to the image method along with some optional arguments.
So after a quick look around I found the general approach to get a data uri from an image URL was using canvas, like in this post. Putting it together in PDFKit's interactive browser demo:
function getDataUri(url, callback) {
var image = new Image();
image.crossOrigin = 'anonymous'
image.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size
canvas.getContext('2d').drawImage(this, 0, 0);
// // Get raw image data
// callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));
// ... or get as Data URI
callback(canvas.toDataURL('image/png'));
};
image.src = url;
}
// Usage
getDataUri('http://pdfkit.org/docs/img/14.png', function(dataUri) {
// create a document and pipe to a blob
var doc = new PDFDocument();
var stream = doc.pipe(blobStream());
doc.image(dataUri, 150, 200, {
width: 300
});
// end and display the document in the iframe to the right
doc.end();
stream.on('finish', function() {
iframe.src = stream.toBlobURL('application/pdf');
});
});
I retrieve the image via AJAX as a base64-encoded string, then use the following code to convert the base64-encoded string into a usable buffer:
var data = atob(base64);
var buffer = [];
for (var i = 0; i < data.length; ++i)
buffer.push(data.charCodeAt(i));
buffer._isBuffer = true;
buffer.readUInt16BE = function(offset, noAssert) {
var len = this.length;
if (offset >= len) return;
var val = this[offset] << 8;
if (offset + 1 < len)
val |= this[offset + 1];
return val;
};
pdf.image(buffer);
See also https://github.com/devongovett/pdfkit/issues/354#issuecomment-68666894, where the same issue is discussed as applied to fonts.
I'll weigh my 2 cents on the issue as I just spent a good deal of time getting it to work. It's a medley of answers I've found googling the issue.
var doc = new PDFDocument();
var stream = doc.pipe(blobStream());
var files = {
img1: {
url: 'http://upload.wikimedia.org/wikipedia/commons/0/0c/Cow_female_black_white.jpg',
}
};
Use the above object at a place to store all of the images and other files needed in the pdf.
var filesLoaded = 0;
//helper function to get 'files' object with base64 data
function loadedFile(xhr) {
for (var file in files) {
if (files[file].url === xhr.responseURL) {
var unit8 = new Uint8Array(xhr.response);
var raw = String.fromCharCode.apply(null,unit8);
var b64=btoa(raw);
var dataURI="data:image/jpeg;base64,"+b64;
files[file].data = dataURI;
}
}
filesLoaded += 1;
//Only create pdf after all files have been loaded
if (filesLoaded == Object.keys(files).length) {
showPDF();
}
}
//Initiate xhr requests
for (var file in files) {
files[file].xhr = new XMLHttpRequest();
files[file].xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
loadedFile(this);
}
};
files[file].xhr.responseType = 'arraybuffer';
files[file].xhr.open('GET', files[file].url);
files[file].xhr.send(null);
}
function showPDF() {
doc.image(files.img1.data, 100, 200, {fit: [80, 80]});
doc.end()
}
//IFFE that will download pdf on load
var saveData = (function () {
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
return function (blob, fileName) {
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
};
}());
stream.on('finish', function() {
var blob = stream.toBlob('application/pdf');
saveData(blob, 'aa.pdf');
});
The biggest issue I came across was getting the info from the arraybuffer type to a string with base64 data. I hope this helps!
Here is the js fiddle where most of the xhr code came from.
I did it using NPM package axios to get a base64 encoded buffer:
on the project folder:
npm i axios
code:
var axios = require('axios');
let image = await axios.get("url", {responseType: 'arraybuffer'});
doc.image(image.data, 12, h, {
width: 570,
align: 'center',
valign: 'center'
});

Save captured png as arraybuffer

I'm trying to save an image to dropbox, and having trouble getting the convertion correct. I have an img (captured using this sample) and I want to store it to dropbox that accepts an ArrayBuffer (sample here)
This is the code I found that should to the two conversions, first to a base64, then into a ArrayBuffer
function getBase64Image(img) {
// Create an empty canvas element
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to
// guess the original format, but be aware the using "image/jpg"
// will re-encode the image.
var dataURL = canvas.toDataURL("image/png");
return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}
function base64ToArrayBuffer(string_base64) {
var binary_string = window.atob(string_base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
var ascii = binary_string.charCodeAt(i);
bytes[i] = ascii;
}
return bytes.buffer;
}
Saving is started like this
var img = $('#show-picture')[0];
var data = base64ToArrayBuffer( getBase64Image(img));
dropbox.client.writeFile(moment().format('YYYYMMDD-HH-mm-ss')+'.png', data, function (error, stat) {
if (error) {
return dropbax.handleError(error);
}
// The image has been succesfully written.
});
Problem is that I get a corrupted file saved, and is a bit confused on what's wrong.
*EDIT *
Here's the link to the original file
https://www.dropbox.com/s/ekyhvu2t6d8ldh3/original.PNG and here to the corrupted. https://www.dropbox.com/s/f0oevj1z33brpur/20131219-22-23-14.png
I'm using this version of the dropbox.js: //cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.10.2/dropbox.min.js
As you can see the corrupted is slighty bigger 23,3KB vs 32,6 KB
Thanks for any help
Larsi
Moving my comment to an answer, since it seems that this works in the latest Datastore JS SDK but perhaps not in dropbox.js 0.10.2.
What browser and what version of the Dropbox library? And what's wrong with the image that's saved? (I assume by "corrupted" you mean that it won't open in whatever tool you're using... any more hints? Is the file size reasonable?) I just did a very similar test (toDataURL, atob, and Uint8Array) with Chrome on OS X and dropbox.com/static/api/dropbox-datastores-1.0-latest.js, and it seems to work.

Categories

Resources