Can't convert iPhone heic photo to jpg - javascript

I'm trying to convert heic to jpg. I use the official library on js. The problem is that I can decode a heic photo from the repository (callback returns an array of data), but I can't decode heic photos taken on the iPhone (In this case, the callback doesn't return anything at all).
Has anyone tried to convert a heic photo, that was made on an iPhone to jpg?
var reader = new HEIFReader('test/1.heic');
var decoder = new HevcDecoder();
var imgData = new ImageProvider(reader, decoder);
reader.requestFileInfo(function(payload) {
if (payload.success !== true) {
console.error("Could not read file:", url);
} else {
var fileInfo = payload;
console.log("FileInfo contents:", fileInfo);
if (fileInfo.rootLevelMetaBoxProperties) {
var masterContextId = fileInfo.rootLevelMetaBoxProperties.contextId;
var masterItemIds = [];
var imageFeaturesMap = fileInfo.rootLevelMetaBoxProperties.imageFeaturesMap;
for (i in imageFeaturesMap) {
if (imageFeaturesMap.hasOwnProperty(i) && imageFeaturesMap[i].isMasterImage === true) {
masterItemIds.push(parseInt(i));
}
}
console.log("Master images in the file:", masterItemIds);
imgData.requestImageData(masterContextId, masterItemIds, function(data) {
console.log(data);
});
}
}
});

Related

JavaScript - file to bytes array

I'm trying to get a script to work, which is called sfo.js.
The repo mentions only this usage:
keys = parse_sfo(Some_ArrayBuffer);
console.log(keys['TITLE']);
Looking into the sfo.js, parse_sfo has the sfoBytes argument.
From this I've concluded the sfoBytes argument needs to be an arraybuffer of file bytes.
I've tried to make a script that parses the SFO file into a array of bytes:
<script src="sfo.js"></script>
<script>
function stringToArrayBuffer(str) {
var buf = [];
for (var i=0, strLen=str.length; i<strLen; i++) {
buf[i] = str.charCodeAt(i);
}
console.log(buf);
return buf;
}
function testing(url) {
var request = new XMLHttpRequest();
request.open('GET', url, false);
request.send(null);
if (request.status === 200) {
console.log(request.response);
var response = request.response;
var array = stringToArrayBuffer(response);
return array;
} else {
alert('Error!');
}
}
var data = testing('param.sfo');
var sfo = parse_sfo(data);
</script>
That throws an error in the console:
Uncaught RangeError: byte length of Uint32Array should be a multiple of 4 at new Uint32Array (<anonymous>)
at readUint32At (sfo.js:20)
at parse_sfo (sfo.js:113)
at (index):29
I'm pretty sure I'm doing something wrong. Does anybody understand how I can make the script work properly?
I have a sample file for param.sfo: https://filebin.net/gghosrp6u93jn7y8 (if linking to a download is not allowed please let me know)
Ok, finally the working example.
I've found a small param.sfo file in the internet.
Notes:
there are 2 versions of file reader: local and remote
in the snippet below you can test both (I've added my param.sfo as an external link to test 'remote'). To test 'local' you just need to select any sfo file from you PC.
as a result I show all the keys, not only 'TITLE' (as it is in your question). You can then select desired key
function getSfoLocal(callback) {
// for now I use local file for testing
document.querySelector('input').addEventListener('change', function() {
var reader = new FileReader();
reader.onload = function() {
var arrayBuffer = this.result;
var array = new Uint8Array(arrayBuffer);
// var binaryString = String.fromCharCode.apply(null, array);
if (typeof callback === 'function') callback(array);
}
reader.readAsArrayBuffer(this.files[0]);
}, false);
}
function getSfoRemote(url, callback) {
var concatArrayBuffers = function(buffer1, buffer2) {
if (!buffer1) {
return buffer2;
} else if (!buffer2) {
return buffer1;
}
var tmp = new Uint8Array(buffer1.length + buffer2.length);
tmp.set(buffer1, 0);
tmp.set(buffer2, buffer1.byteLength);
return tmp.buffer;
};
fetch(url).then(res => {
const reader = res.body.getReader();
let charsReceived = 0;
let result = new Uint8Array;
reader.read().then(function processText({
done,
value
}) {
// Result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (done) {
if (typeof callback === 'function') callback(result);
return result;
}
// value for fetch streams is a Uint8Array
charsReceived += value.length;
const chunk = value;
result = concatArrayBuffers(result, chunk);
// Read some more, and call this function again
return reader.read().then(processText);
});
});
}
function getSfo(type, url, callback) {
if (type === 'local') getSfoLocal(callback);
if (type === 'remote') getSfoRemote(url, callback);
}
getSfo('local', null, (data) => {
keys = parse_sfo(data);
console.log('LOCAL =', keys);
});
function goremote() {
getSfo('remote', 'https://srv-file9.gofile.io/download/Y0gVfw/PARAM.SFO', (data) => {
keys = parse_sfo(data);
console.log('REMOTE =', keys);
});
}
div { padding: 4px; }
<!--script src="https://rawcdn.githack.com/KuromeSan/sfo.js/c7aa8209785cc5a39c4231e683f6a2d1b1e91153/sfo.js" for="release"></script-->
<script src="https://raw.githack.com/KuromeSan/sfo.js/master/sfo.js" for="develop"></script>
<div>Local version: <input type="file" /></div>
<div>Remote version: <button onclick="goremote()">Go remote</button></div>
P.S. It seems that gofile.io service that I've used for remote example is not visible sometimes. If you have permanent link to param.sfo just let me know. But now I have to visit my file, and only after that it becomes visible.
You are using some 20 years old JavaScript.
Please use fetch add TextEncoder for your own sanity.
fetch(url).then(res => res.json().then(data => {
const encoder = new TextEncoder()
const bytes = encoder.encode(data)
parse_sfo(data)
})
sfoBytes should be the actual array buffer of the sfo file. It's simpler than what you are trying, you shouldn't need to convert the request response with stringToArrayBuffer since you can get an array buffer from XMLHttpRequest. Also, the SFO file isn't a text file, it's binary, so conversion from string wouldn't work anyway.
Changing your request to get a arraybuffer response type should do it like this:
function testing(url) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = "arraybuffer";
request.send(null);
if (request.status === 200) {
console.log(request.response);
var response = request.response;
var sfo = parse_sfo(response);
} else {
alert('Error!');
}
}

Is there a way to save multiple jsPDF outputs into an single ZIP using jsZIP?

I use jsPDF to generate multiple PDFs.
Is there a way for me to save all the PDFs as a single ZIP through jsZIP?
For anyone coming here to find a more direct answer to this question, I have this example:
var zip = new JSZip();
angular.forEach ($scope.students, function (student) {
//The createFile() function returns a jsPDF
var doc = createFile(student);
if (typeof doc !== 'undefined') {
try {
zip.file(student.name + '.pdf', doc.output('blob'));
}
catch {
console.error('Something went wrong!');
}
}
});
zip.generateAsync({type:'blob'}).then(function(content) {
saveAs(content, 'reports.zip');
});
Basically, zip.file() adds a new file to the zip with (name, data). You add the jsPDF document as a blob by calling doc.output('blob'). Don't forget to add '.pdf' at the end of the name.
Then you generate a zip file by calling the zip.generateAsync() function and save it when it finishes generating.
In this example I used jsPDF, JSZip and FileSaver.js
Does this help?
https://gist.github.com/noelvo/4502eea719f83270c8e9
var zip = new JSZip();
var count = 0;
var zipFilename = "zipFilename.zip";
var urls = [
'http://image-url-1',
'http://image-url-2',
'http://image-url-3'
];
urls.forEach(function(url){
var filename = "filename";
// loading a file and add it in a zip file
JSZipUtils.getBinaryContent(url, function (err, data) {
if(err) {
throw err; // or handle the error
}
zip.file(filename, data, {binary:true});
count++;
if (count == urls.length) {
var zipFile = zip.generate({type: "blob"});
saveAs(zipFile, zipFilename);
}
});
});

File uploaded does not upload if the file name has parenthesis ()

I have a FileUpload control where I upload PDF files and they get saved to a folder, the file path gets saved to the database.
The problem is when I upload a file which contains parenthesis () as part of the file name, it returns undefined. This only happens if the file name has parenthesis () , if it does not have parenthesis () it uploads fine.
This is my code
var filePaths;
function UploadFile() {
var fileUpload = document.getElementById("fuPDFupload");
var regex = new RegExp("([a-zA-Z0-9\s_\\.\-:])+(.jpg|.png|.pdf)$");
if (regex.test(fileUpload.value.toLowerCase())) {
//Check whether HTML5 is supported.
if (typeof (fileUpload.files) != "undefined") {
//Initiate the FileReader object.
var reader = new FileReader();
//Read the contents of Image File.
reader.readAsDataURL(fileUpload.files[0]);
reader.onload = function (e) {
//Initiate the JavaScript Image object.
var image = new Image();
//Set the Base64 string return from FileReader as source.
image.src = e.target.result;
var fileUpload = $("#fuPDFupload").get(0);
var files = fileUpload.files;
var data = new FormData();
for (var i = 0; i < files.length; i++) {
data.append(files[i].name, files[i]);
}
$.ajax({
url: "FileUploadHandler.ashx",
type: "POST",
data: data,
contentType: false,
processData: false,
success: function (result) {
filePaths = result;
//Save to DB
UpdateSchedule();
},
error: function (err) {
}
});
return true;
};
} else {
alert("This browser does not support HTML5.");
return false;
}
} else {
return false;
}
}
FileUploadHandler Code:
public class FileUploadHandler : IHttpHandler {
public void ProcessRequest(HttpContext context)
{
if (context.Request.Files.Count > 0)
{
string filePaths = Guid.NewGuid().ToString() + ".pdf";
HttpPostedFile file = context.Request.Files[0];
string path = context.Server.MapPath("~/QfrencyInvoices/" + filePaths);
file.SaveAs(path);
context.Response.ContentType = "text/plain";
context.Response.Write(filePaths);
}
}
public bool IsReusable {
get {
return false;
}
}
}
I believe that the problem might be happening because the Regex expression is incorrect but I have not been able to fix it.
Please assist me how I can upload files that have parenthesis () as part of the file name. Thank you.
Just leave next regex new RegExp("(\.(jpg|png|pdf)$", "i");. It checks that filename has extension jpg, png or pdf. Text case does not matter so "i" was added as the second parameter.
You can learn regular expressions on https://regexone.com/

Save base64 encoded image to Firebase Storage

Using firebase 3.0.x, is it possible to save a base64 encoded image to the new Firebase Storage service?
I am using canvas to resize images in the browser prior to uploading them, and output them as a base64 jpeg. I know that the Storage api can accept Blobs, but IE9 support is needed for my current project.
You only need to use the putString function without converting the BASE64 to blob.
firebase.storage().ref('/your/path/here').child('file_name')
.putString(your_base64_image, ‘base64’, {contentType:’image/jpg’});
Make sure to pass the metadata {contentType:’image/jpg’} as the third parameter (optional) to the function putString in order for you to retrieve the data in an image format.
or simply put:
uploadTask = firebase.storage().ref('/your/path/here').child('file_name').putString(image, 'base64', {contentType:'image/jpg'});
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, // or 'state_changed'
function(snapshot) {
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
switch (snapshot.state) {
case firebase.storage.TaskState.PAUSED: // or 'paused'
console.log('Upload is paused');
break;
case firebase.storage.TaskState.RUNNING: // or 'running'
console.log('Upload is running');
break;
}
}, function(error) {
console.log(error);
}, function() {
// Upload completed successfully, now we can get the download URL
var downloadURL = uploadTask.snapshot.downloadURL;
});
You can then use the downloadURL to save to firebase.database() and/or to put as an src to an <img> tag.
The latest version of the Firebase SDK supports base64 image uploads. Simply use the putString method from Firebase Storage.
https://firebase.google.com/docs/reference/js/firebase.storage
One small caveat is that sometimes you'll have a base64 String with unnecessary whitespace. For example, I've found that the cordova Camera plugin returns base64 with unnecessary whitespace. The Storage SDK will fail to upload this because JavaScript can't perform it's native atob function - something the Firebase JS does under the hood. You'll have to strip the whitespace - see DOM Exception 5 INVALID CHARACTER error on valid base64 image string in javascript
Yes, it's possible now. You should use Firebase Storage new method called putString. You may read spec here.
So, Firebase spec says that you have now two methods to store Base64 string and Base64url string:
// Base64 formatted string
var message = '5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB';
ref.putString(message, 'base64').then(function(snapshot) {
console.log('Uploaded a base64 string!');
});
// Base64url formatted string
var message = '5b6p5Y-344GX44G-44GX44Gf77yB44GK44KB44Gn44Go44GG77yB';
ref.putString(message, 'base64url').then(function(snapshot) {
console.log('Uploaded a base64url string!');
})
From my experience, using putString(message, 'base64url') constantly returns Error about bad formated Base64 string code: "storage/invalid-format", message: "Firebase Storage: String does not match format 'base64': Invalid character found". The solution is to cut off beginning of string data:image/jpeg;base64, and use first method instead putString(message, 'base64'). Then it works.
If you use canvas.toBlob() you'll get the byte[] that you need to pass into Firebase Storage.
Quick example:
function save() {
var canvas = document.getElementById("canvas");
canvas.toBlob(blob => {
var storage = firebase.app().storage().ref();
var name = id + "/" + (new Date()).getTime() + ".png";
var f = storage.child("drawings/" + name);
var task = f.put(blob);
task.on('state_changed', function(snapshot) {
}, function(error) {
console.error("Unable to save image.");
console.error(error);
}, function() {
var url = task.snapshot.downloadURL;
console.log("Saved to " + url);
var db = firebase.database();
var chats = db.ref().child("chats");
chats.child(id).child("drawingURL").set(url);
});
});
};
Otherwise you'll have to convert the base64 yourself, for example with atob().
Here are two values I use to help with support on .toBlob() it is less performant however, it gets the job done.
This method takes in the base64 string, the content type (IE: image/png) and a callback for when the blob has been constructed using atob()
var b64_to_blob = function(b64_data, content_type, callback) {
content_type = content_type || '';
var slice_size = 512;
var byte_characters = atob(b64_data);
var byte_arrays = [];
for(var offset = 0; offset < byte_characters.length; offset += slice_size) {
var slice = byte_characters.slice(offset, offset + slice_size);
var byte_numbers = new Array(slice.length);
for(var i = 0; i < slice.length; i++) {
byte_numbers[i] = slice.charCodeAt(i);
}
var byte_array = new Uint8Array(byte_numbers);
byte_arrays.push(byte_array);
}
var blob = new Blob(byte_arrays, {type: content_type});
callback(blob);
};
I use this method to get the base64 value of a direct link when necessary, In my case it's to download a users Facebook photo when they register on my application.
var image_link_to_b64 = function(url, content_type, callback) {
var image = new Image();
image.crossOrigin = 'Anonymous';
image.onload = function() {
var canvas = document.createElement('CANVAS');
var context = canvas.getContext('2d');
var data_url;
canvas.height = this.height;
canvas.width = this.width;
context.drawImage(this, 0, 0);
data_url = canvas.toDataURL(content_type);
data_url = data_url.substr(22);
callback(data_url);
canvas = null;
};
image.src = url;
};
Below is how it looks when adding the information to firebase storage
$fileService.image_link_to_b64(facebook_info.photoURL + '?width=512&height=512', 'image/png', function(b64) {
$fileService.b64_to_blob(b64, 'image/png', function(blob) {
$fileService.upload_user_photo(blob, 'image/png', function(url) {
// Firebase storage download url here
}
}
}
Incase you're wondering upload_user_photo simply uploads to firebase storage:
var upload_user_photo = function(data, blob, callback) {
var upload_task = user_photo_reference.child('user_photo').put(data);
upload_task.on('state_changed', function(snapshot) {
}, function(error) {
alert('error: ', error);
}, function() {
callback(upload_task.snapshot.downloadURL);
});
};]
For IE9 see this polyfill: https://github.com/eligrey/Blob.js/blob/master/Blob.js
This solution works for me using the Google Cloud Storage API.
But it should work also with the Firebase one by replacing the file.save with the ref put method.
const file = storage.file(file_path_in_gs)
const contents = new Uint8Array(Buffer.from(base64ImgStr, 'base64'))
file.save(contents,
{
contentType: img_type,
metadata: {
metadata: {
contentType: img_type,
firebaseStorageDownloadTokens: uuid()
}
}
}
, () => { })
With Firebase SDK 9 for web (as of May 2022)
import { getStorage, ref, uploadString } from "firebase/storage";
const storage = getStorage();
const storageRef = ref(storage, 'some-child');
// Data URL string
const message4 = 'data:text/plain;base64,5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB';
uploadString(storageRef, message4, 'data_url').then((snapshot) => {
console.log('Uploaded a data_url string!');
});

Saving Base64 Buffer as an image to the Disk

in a Node.js app, i send an image via Socket.io to the server. in the server, i get it like this and i wanted to save it on the disk. it save's but i cant open that image and image viewer says it's damaged! file size is correct but i can't open it. is there anything that i miss in the code?
socket.on('newImage', function (data) {
var img = data.image;
var coded_img = new Buffer(img, 'base64');
fs.writeFile("out.png", coded_img,'base64', function(err) {
if (err) throw err;
console.log('File saved.')
});
});
I solved it, With this function, i recovered main image data:
decodeBase64Image = function(dataString) {
var matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/),
response = {};
if (matches.length !== 3) {
return new Error('Invalid input string');
}
response.type = matches[1];
response.data = new Buffer(matches[2], 'base64');
return response;
};

Categories