putting an image into chrome.storage extension - javascript

I'm currently writing a Chrome extension that needs to save some images into chrome.storage memory. I'm currently making an array of objects:
var AnImage = new Image()
AnImage.src = http://image.image/imageurl.png
var ObjectToSave = { ...,
graph_img : AnImage,
... }
...
AnArray[x] = ObjectToSave
I output with a simple append
document.getElementById("AnElement").appendChild(ObjectToSave.graph_img)
But when I load it from storage and try to append it again, it returns an error
Error in response to storage.get: TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
When I output it through console.log() it evaluates what has been retrieved as an object, but not anything I can recognise. I'm currently using chrome.storage.sync
Is there a way of doing this? There seems to be little help in the way of storing images, and of what exists is old and talks about encoding with base64 for the older storage API. But when I did my initial research there were people claiming that base64 encoding was no longer necessary

After more looking, I found out about the FileReader object that provides a more elegant way over using the canvas method. And as this is chrome specific compatibility is ensured.
function ImageLoader( url ){
var imgxhr = new XMLHttpRequest();
imgxhr.open( "GET", url + "?" + new Date().getTime() );
imgxhr.responseType = "blob";
imgxhr.onload = function (){
if ( imgxhr.status===200 ){
reader.readAsDataURL(imgxhr.response);
}
};
var reader = new FileReader();
reader.onloadend = function () {
document.getElementById( "image" ).src = reader.result;
chrome.storage.local.set( { Image : reader.result } );
};
imgxhr.send();
}
Using chrome.storage.local is needed as it is highly likely that the storage size limit for chrome.storage.sync will be exceeded.

As Marc Guiselin and wOxxOm mentioned in the comments, chrome.storage will serialize the data first, to save image, you could:
Save the image src. chrome.storage.sync.set({ src: img.src });
Save the image dataURL.

Related

JavaScript FileReader Massive Result

I've got a FileReader that lets the user upload a file (image) to my site.
Here's the code that does the reading:
$("input[type='file']").change(function(e) {
var buttonClicked = $(this);
for (var i = 0; i < e.originalEvent.srcElement.files.length; i++) {
var file = e.originalEvent.srcElement.files[i];
var img = document.createElement("img");
var reader = new FileReader();
reader.onloadend = function() {
img.src = reader.result;
console.log(reader.result);
}
reader.readAsDataURL(file);
}
});
All is good and well, until I tried to print out my result. I used this file for example:
When I console.log() the result, it spits out over 95000 characters.
This image in particular is around the same size as the images I will be accepting into my site.
I was hoping to store these images in a database as well, and so I'm wondering how this is going to be possible with image sources that are so extremely long. Is there a way to shorten this or get the image path a different way?
I'm moreso curious as to why they're so long, but if someone has a tip to store these (100s per user, 500+ users) that'd be nice as well!
Thanks-
Store the Files as ... Files.
There are very little use cases where you need the toDataURL() method of the FileReader, so every time you use it, you should ask yourself why you need it.
In your case :
To display the image in the page. Well don't use a FileReader for this, instead create a direct pointer to the file in the form of an url, available only to this session. This can be achieved with the URL.createObjectURL(File_orBlob) method.
To store this image on your server. Don't store a ~37% bigger base64 version, send and store directly the file as a file (multipart). This can be achieved easily with the FormData API.
inp.onchange = function(){
var file = this.files[0];
if(file.type.indexOf('image/') !== 0){
console.warn('not an image');
}
var img = new Image();
img.src = URL.createObjectURL(file);
// this is not needed in this case but still a good habit
img.onload = function(){
URL.revokeObjectURL(this.src);
};
document.body.appendChild(img);
}
// not active but to give you da codez
function sendToServer(){
var file = inp.files[0];
var form = new FormData();
// here 'image' is the parameter name where you'll retrieve the file from in the request
form.append('image', file);
form.append('otherInfo', 'some other infos');
var xhr = new XMLHttpRequest();
xhr.open('post', 'yourServer/uploadPage')
xhr.onload = function(){
console.log('saved');
};
xhr.send(form);
}
<input type="file" id="inp">
And if you need PHP code to retrieve the File form this request :
if ( isset( $_FILES["image"] ) ){
$dir = 'some/dir/';
$blob = file_get_contents($_FILES["image"]['tmp_name']);
file_put_contents($dir.$_FILES["image"]["name"], $blob);
}
You're going to want to upload the files to a server of some sort (a backend that is serving up your javascript), and then from there you'll want to
Validate the file
Store the file on a physical server (or the cloud) somewhere
Add an entry in a database that relates the file path or ID of that upload to the user who just uploaded it (so you can retrieve it later if needed)
So basically, you don't store the image in your database, you store it on a file share/cloud host somewhere, and instead you only store what is needed to download/retrieve the image later.

How to get duration of video when I am using filereader to read the video file?

I am trying to upload a video to server, and on client end. I am reading it using FileReader's readAsBinaryString().
Now, my problem is, I don't know how to read duration of this video file.
If i try reading the file, and assigning the reader's data to a video tag's source, then none of the events associated to the video tag are fired. I need to find the duration of file uploaded on client end.
Can somebody please suggest me something?
You can do something like this for that to work:
read the file as ArrayBuffer (this can be posted directly to server as a binary stream later)
wrap it in a Blob object
create an object URL for the blob
and finally set the url as the video source.
When the video object triggers the loadedmetadata event you should be able to read the duration.
You could use data-uri too, but notice that browsers may apply size limits (as well as other disadvantages) for them which is essential when it comes to video files, and there is a significant encoding/decoding overhead due to the Base-64 process.
Example
Select a video file you know the browser can handle (in production you should of course filter accepted file types based on video.canPlayType()).
The duration will show after the above steps has performed (no error handling included in the example, adjust as needed).
var fileEl = document.querySelector("input");
fileEl.onchange = function(e) {
var file = e.target.files[0], // selected file
mime = file.type, // store mime for later
rd = new FileReader(); // create a FileReader
rd.onload = function(e) { // when file has read:
var blob = new Blob([e.target.result], {type: mime}), // create a blob of buffer
url = (URL || webkitURL).createObjectURL(blob), // create o-URL of blob
video = document.createElement("video"); // create video element
video.preload = "metadata"; // preload setting
video.addEventListener("loadedmetadata", function() { // when enough data loads
document.querySelector("div")
.innerHTML = "Duration: " + video.duration + "s"; // show duration
(URL || webkitURL).revokeObjectURL(url); // clean up
// ... continue from here ...
});
video.src = url; // start video load
};
rd.readAsArrayBuffer(file); // read file object
};
<input type="file"><br><div></div>
you can do something like below, the trick is to use readAsDataURL:
var reader = new FileReader();
reader.onload = function() {
var media = new Audio(reader.result);
media.onloadedmetadata = function(){
media.duration; // this would give duration of the video/audio file
};
};
reader.readAsDataURL(file);
Fiddle Demo

converting canvas to blob using cropper js

I have created an application using cropper.js for cropping an images. The application is working and the image is cropping, after that I am trying to send the cropped image as blob to the server side for storing,
As per the cropper.js documentation we can use canvas.toDataURL to get a Data URL, or use canvas.toBlob to get a blob and upload it to server with FormData. when I tried canvas.toDataURL() I am getting the base64 string, but actually I need to send the file as blob so I tried with canvas.toBlob() but I am getting Uncaught TypeError: canvas.toBlob is not a function in chrome and TypeError: Not enough arguments to HTMLCanvasElement.toBlob. in Firefox
Can anyone please tell me some solution for this
My code is like this
var canvas = $image.cropper("getCroppedCanvas", undefined);
var formData = new FormData();
formData.append('mainImage', $("#inputImage")[0].files[0]);
formData.append('croppedImage', canvas.toBlob());
The method toBlob is asynchronous and require two arguments, the callback function and image type (there is optional third parameter for quality):
void canvas.toBlob(callback, type, encoderOptions);
Example
if (typeof canvas.toBlob !== "undefined") {
canvas.toBlob(function(blob) {
// send the blob to server etc.
...
}, "image/jpeg", 0.75);
}
else if (typeof canvas.msToBlob !== "undefined") {
var blob = canvas.msToBlob()
// send blob
}
else {
// manually convert Data-URI to Blob (if no polyfill)
}
Not all browsers supports it (IE needs prefix, msToBlob, and it works differently than the standard) and Chrome needs a polyfill.
Update (to OP's edit, now removed) The main reason why the cropped image is larger is because the original is JPEG, the new is PNG. You can change this by using toDataURL:
var uri = canvas.toDataURL("image/jpeg", 0.7); // last=quality
before passing it to the manual data-uri to Blob. I would recommend using the polyfill as if the browser supports toBlob() it will be many times faster and use less memory overhead than going by encoding a data-uri.
The proper use: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
you have to pass the callback and use the blob object within callback. toBlob() does not returns the blob rather it accepts a callback which provides blob as parameter.
var canvas = document.getElementById("canvas");
canvas.toBlob(function(blob) {
var newImg = document.createElement("img"),
url = URL.createObjectURL(blob);
newImg.onload = function() {
// no longer need to read the blob so it's revoked
URL.revokeObjectURL(url);
};
newImg.src = url;
document.body.appendChild(newImg);
});

How do I save and restore a File object in local storage

I have an HTML5/javscript app which uses
<input type="file" accept="image/*;capture=camera" onchange="gotPhoto(this)">
to capture a camera image. Because my app wants to be runnable offline, how do I save the File (https://developer.mozilla.org/en-US/docs/Web/API/File) object in local storage, such that it can be retrieved later for an ajax upload?
I'm grabbing the file object from the using ...
function gotPhoto(element) {
var file = element.files[0];
//I want to save 'file' to local storage here :-(
}
I can Stringify the object and save it, but when I restore it, it is no longer recognised as a File object, and thus can't be used to grab the file content.
I have a feeling it can't be done, but am open to suggestions.
FWIW My workaround is to read the file contents at store time and save the full contents to local storage. This works, but quickly consumes local storage since each file is a 1MB plus photograph.
You cannot serialize file API object.
Not that it helps with the specific problem, but ...
Although I haven't used this, if you look at the article it seems that there are ways (although not supported yet by most browsers) to store the offline image data to some files so as to restore them afterward when the user is online (and not to use localStorage)
Convert it to base64 and then save it.
function gotPhoto(element) {
var file = element.files[0];
var reader = new FileReader()
reader.onload = function(base64) {
localStorage["file"] = base64;
}
reader.readAsDataURL(file);
}
// Saved to localstorage
function getPhoto() {
var base64 = localStorage["file"];
var base64Parts = base64.split(",");
var fileFormat = base64Parts[0].split(";")[1];
var fileContent = base64Parts[1];
var file = new File([fileContent], "file name here", {type: fileFormat});
return file;
}
// Retreived file object
Here is a workaround that I got working with the code below. I'm aware with your edit you talked about localStorage but I wanted to share how I actually implemented that workaround. I like to put the functions on body so that even if the class is added afterwards via AJAX the "change" command will still trigger the event.
See my example here: http://jsfiddle.net/x11joex11/9g8NN/
If you run the JSFiddle example twice you will see it remembers the image.
My approach does use jQuery. This approach also demonstrates the image is actually there to prove it worked.
HTML:
<input class="classhere" type="file" name="logo" id="logo" />
<div class="imagearea"></div>
JS:
$(document).ready(function(){
//You might want to do if check to see if localstorage set for theImage here
var img = new Image();
img.src = localStorage.theImage;
$('.imagearea').html(img);
$("body").on("change",".classhere",function(){
//Equivalent of getElementById
var fileInput = $(this)[0];//returns a HTML DOM object by putting the [0] since it's really an associative array.
var file = fileInput.files[0]; //there is only '1' file since they are not multiple type.
var reader = new FileReader();
reader.onload = function(e) {
// Create a new image.
var img = new Image();
img.src = reader.result;
localStorage.theImage = reader.result; //stores the image to localStorage
$(".imagearea").html(img);
}
reader.readAsDataURL(file);//attempts to read the file in question.
});
});
This approach uses the HTML5 File System API's to read the image and put it into a new javascript img object. The key here is readAsDataURL. If you use chrome inspector you will notice the images are stored in base64 encoding.
The reader is Asynchronous, this is why it uses the callback function onload. So make sure any important code that requires the image is inside the onLoad or else you may get unexpected results.
You could use this lib:
https://github.com/carlo/jquery-base64
then do something similar to this:
//Set file
var baseFile = $.base64.encode(fileObject);
window.localStorage.setItem("file",basefile);
//get file
var outFile = window.localStorage.getItem("file");
an other solution would be using json (I prefer this method)
using: http://code.google.com/p/jquery-json/
//Set file
window.localStorage.setItem("file",$.toJSON(fileobject));
//get file
var outFile = $.evalJSON(window.localStorage.getItem("file"));
I don't think that there is a direct way to Stringify and then deserialize the string object into the object of your interest. But as a work around you can store the image paths in your local storage and load the images by retrieving the URL for the images. Advantages would be, you will never run out of storage space and you can store 1000 times more files there.. Saving an image or any other file as a string in local storage is never a wise decision..
create an object on the global scope
exp: var attmap = new Object();
after you are done with file selection, put your files in attmap variable as below,
attmap[file.name] = attachmentBody;
JSON.stringify(attmap)
Then you can send it to controller via input hidden or etc. and use it after deserializing.
(Map<String, String>)JSON.deserialize(attachments, Map<String,String>.class);
You can create your files with those values in a for loop or etc.
EncodingUtil.base64Decode(CurrentMapValue);
FYI:This solution will also cover multiple file selection
You could do something like this:
// fileObj = new File(); from file input
const buffer = Buffer.from(await new Response(fileObj).arrayBuffer());
const dataUrl = `data:${fileObj.type};base64,${buffer.toString("base64")}`;
localStorage.setItem('dataUrl', dataUrl);
then you can do:
document.getElementById('image').src = localStorage.getItem('dataUrl');

Phonegap FileReader readAsText returns null but readAsDataURL works

I'm using Phonegap to download an archive, unzip it, then read the files. It's all working until I try and read the files as text. If I use readAsDataURL() then I get a whole heap of stuff logged to the console.
function( file ) {
console.log(file);
var reader = new FileReader();
reader.onloadend = function( evt ) {
console.log( evt.target.result );
};
reader.readAsDataURL( file );
}
If I use readAsText() I get null. The files range from 300KB to 1.4MB, but all files return null in the console.
reader.readAsText( file );
Why would one function return something and the other be null? Is there a limit on the text size it can read?
This is the file object that I'm logging before creating reader, that I'm applying the functions to (I've shortened the file name):
{
"name":"categories.json",
"fullPath":"/var/mobile/.../Documents/data/file.json",
"type":null,
"lastModifiedDate":1380535318000,
"size":382456
}
And this is the evt object for readAsText():
{
"type":"loadend",
"bubbles":false,
"cancelBubble":false,
"cancelable":false,
"lengthComputable":false,
"loaded":0,
"total":0,
"target":{
"fileName":"/var/mobile/.../Documents/data/file.json",
"readyState":2,
"result":"null",
"error":null,
"onloadstart":null,
"onprogress":null,
"onload":null,
"onerror":null,
"onabort":null
}
}
UPDATE: I've seen in the W3C spec for the File API that result would only be set to null if an error had occured. But I tried adding a reader.onerror() function, but that wasn't getting called.
If an error occurs during reading the blob parameter, set readyState
to DONE and set result to null. Proceed to the error steps.
http://www.w3.org/TR/FileAPI/#dfn-readAsText
You may have been grabbing the fileEntry instead of a fileObject. Assuming file was actually fileEntry, try this:
var
fileEntry = file, //for example clarity - assumes file from OP's file param
reader = new FileReader()
;
fileEntry.file( doSomethingWithFileObject );//gets the fileObject passed to it
function doSomethingWithFileObject(fileObject){
reader.onloadend = function(e){
doSomething(e.target.result); //assumes doSomething defined elsewhere
}
var fileAsText = reader.readAsText(fileObject);
}
Definitely an API that screams for cruft reduction.

Categories