03-19-2012 04:09 PM
Hi Tim,
It creates a file in the location, it just is not a image file. from everything i have read or seen, the function i have at the top of this thread is the correct way to save the binary (image). Again all i want to do is save the canvas in my app to the file system (prefer the photos area). I am using a Phonegap app call the web works. The app works great and is out in app world (icancolor) i now just want to give my users the ability to save thier art work.
03-20-2012 04:29 PM
Kaigen had some good input, and that atob() method is pretty great for getting the actual binary data. However, the problem then shifts from getting the right data to trying to save it. If you follow that path you must use the regular HTML5 File API, not the WebWorks extension (blackberry.io.saveFile).
The stringToBlob and blobToString methods need considerably more documentation and I'll be forwarding all my results to the docs team to review and update them.
It's critical to understand that HTML5 Blobs and WebWorks Blobs are not the same. That's not obvious and we need to clear that up. The WebWorks Blob is actually an identifier for an object held in ActionScript. These objects are only created by the blackberry.io and blackberry.util methods.
Secondly, the stringToBlob and blobToString methods refer to these Blob's stored in ActionScript. That's why they use the Blob id's. The data values passed in and out of both methods are always Strings, not binary data. That Data is viewed as "raw" though, so when you call stringToBlob(myimagedata, 'Base64') it's actually doing the opposite of what I expected it to do: It takes your myimagedata and encodes it to Base64. The irony being that the Blobs are not really Binary objects at all. All we are doing is working with encoding's of String data.
So, how do we fix this? Besides the docs, I'm going to suggest a small hack that I will be submitting to our WebWorks Github repo as well. Our stringToBlob method just needs to handle taking Base64 data and decoding it to regular binary. Then the saveFile method will work as is.
Since we can already pass in an encoding to stringToBlob, let's make it support one more: 'binary'. If you edit your Utilities.as file in "C:\Program Files\Research In Motion\BlackBerry WebWorks SDK for TabletOS 2.2.0.5\bbwp\ext\blackberry.utils\src\Air\Utilitie
public function stringToBlob(data:String, encoding:String):Object
{
var characterSet:String = translateEncodingIntoASCharacterSet(encoding);
var ba:ByteArray = new ByteArray();
if (characterSet == "base64")
{
var base64Encoder:Base64Encoder = new Base64Encoder();
base64Encoder.encode(data);
ba.writeUTFBytes(base64Encoder.toString());
}
else if (characterSet == "binary")
{
var base64Decoder:Base64Decoder = new Base64Decoder();
base64Decoder.decode(data);
ba = base64Decoder.toByteArray();
}
else
{
ba.writeMultiByte(data, characterSet);
}
var bm:BlobManager = getBlobManager();
var blobId:String = bm.createBlob(ba).id;
return new WebWorksReturnValue(blobId).jsonObject;
}
Then you can call code like this:
var myimagedata = canvas.toDataURL();
myimagedata = myimagedata.replace('data:image/png;base64,', '');
var blobdata = blackberry.utils.stringToBlob(myimagedata, 'binary');
var numRand = Math.floor(Math.random()*9999999);
var thePathnName = "file:///accounts/1000/shared/photos/iCanColor" + numRand.toString() +".png";
console.log(thePathnName);
blackberry.io.file.saveFile(thePathnName, blobdata);
That will get you going right now, while we update the SDK with the new extension.
One final note, I used the sketchPad sample app as my test, and it doesn't paint the background of the canvas (just uses the default white), so my PNG was transparent. Threw me off for a second ![]()
See attached image for my canvas output. I'd say it's as good as a Picasso.
03-21-2012 03:43 AM
03-21-2012 08:44 AM
03-22-2012 04:32 PM
Tim,
this patch works like a charm, i see an update coming out for my icancolor app...
thanks again
tim
03-29-2012 10:45 PM - edited 03-29-2012 10:47 PM
Hi,
I'm creating a Playbook WebWorks application which needs to fetch a file on a server using the XMLHttpRequest object and then persist that file locally on the Playbook in order to possibly open it using the file open() method in the webworks API (assuming the user has the right file viewer installed). The file can be binary (Word doc, PDF, etc). I tried the extension you provided, but it does work. I mean, the file gets persisted, but it's only 1KB and not a Word doc or PDF.
Thanks in advance for your help
03-30-2012 10:24 AM
How is the file you fetch encoded? Are you actually getting binary data back in your variable?
The stringToBlob method takes an encoded String value. If you have binary data that you want to save as a binary file, try converting it to a base64 String first with btoa(value), then pass that String into the stringToBlob method with 'binary' as the encoding. Then save that Blob in the file system.
It's kind of round about to go binary to String to binary to filesystem, but what you might really be looking for is a blobToBlob method, as odd as that sounds, and we haven't got one in there right now.
03-30-2012 03:34 PM - edited 03-30-2012 03:39 PM
Hi Tim,
Thanks the quick reply.
The file I am fetching (in my tests) is a Word document, so I assume it to be binary. I tried using the btoa method, but it fails, so I tried the Base64.encode method here:
var Base64 = {
// private property
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw xyz0123456789+/=",
// public method for encoding
encode : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = Base64._utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
}
return output;
},
// public method for decoding
decode : function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
output = Base64._utf8_decode(output);
return output;
},
// private method for UTF-8 encoding
_utf8_encode : function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
},
// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = c1 = c2 = 0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
}
}But with this method, when I save the file, it's actually almost twice the size of the original Word doc and it does not get opened by Word. When I look at both file contents in Notepad, I see that the content is almost the same...
Here is the code I have:
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.overrideMimeType("application/octet-stream; charset=x-user-defined");
xhr.send(null);
var data = Base64.encode(xhr.response);
var blob_data = blackberry.utils.stringToBlob(data, 'binary');
blackberry.io.file.saveFile(blackberry.io.dir.appD irs.shared.documents.path + '/testPersist1.doc', blob_data);
I tried converting the response value of the XMLHttpRequest object into a byte array and but there is no byteArrayToBlob method. Is there any way to add new methods in the actionscript files and have them available in the Webworks Javascript SDK ? I tried to do that and also add a method in the utilities_dispatcher to map it , but it didn't work.
I see that there is a "blobFromBytes" method in the FileExtension.as, but it's private.
Any ideas ?
Christian
03-30-2012 05:17 PM
Did you just create a pointer to blackberry.io.saveFile? re:
common.util.OSUtil.saveFile('testPersist1.doc', blob_data);
you say btoa() failed. What was the actual result from that?
What is the xhr.response data? How does that compare with the actual file binary?
Have you tried comparing your base64 encoding with another encoding to be sure that it's getting the right result? Or putting it on the filesystem and using readFile to open it as bas64 to be sure teh data is matching at all the transition points?
04-04-2012 11:35 AM
So this is a bit off thread now, but if anyone does come here, I'd like them to find the answer.
If you are doing an XHR call to get a binary file, set your responseType to 'arraybuffer'. Then, use this excellent code from here: https://gist.github.com/958841, to convert the arraybuffer to a base64 encoded String.
Then just pass that String in to the patched stringToBlob(encoded, 'binary') method and save the file.
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if(request.readyState == 4) {
if(request.status == 200){
var response = request.response;
var encoded = base64ArrayBuffer(response);
var blob_data = blackberry.utils.stringToBlob(encoded, "binary");
var filename = blackberry.io.dir.appDirs.shared.documents.path + "/attachment.doc";
if (blackberry.io.file.exists(filename)) {
blackberry.io.file.deleteFile(filename);
}
blackberry.io.file.saveFile(filename, blob_data);
blackberry.io.file.open(filename);
}
}
}
request.open("GET", attachmentURL, true);
request.responseType = "arraybuffer";
request.send(null);
// Credit to Jon Leighton: https://github.com/jonleighton
// Pulled from https://gist.github.com/958841
// Converts an ArrayBuffer directly to base64, without any intermediate 'convert to string then
// use window.btoa' step. According to my tests, this appears to be a faster approach:
// http://jsperf.com/encoding-xhr-image-data/5
function base64ArrayBuffer(arrayBuffer) {
var base64 = ''
var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw xyz0123456789+/'
var bytes = new Uint8Array(arrayBuffer)
var byteLength = bytes.byteLength
var byteRemainder = byteLength % 3
var mainLength = byteLength - byteRemainder
var a, b, c, d
var chunk
// Main loop deals with bytes in chunks of 3
for (var i = 0; i < mainLength; i = i + 3) {
// Combine the three bytes into a single integer
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
// Use bitmasks to extract 6-bit segments from the triplet
a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12
c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6
d = chunk & 63 // 63 = 2^6 - 1
// Convert the raw binary segments to the appropriate ASCII encoding
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
}
// Deal with the remaining bytes and padding
if (byteRemainder == 1) {
chunk = bytes[mainLength]
a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4 // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + '=='
} else if (byteRemainder == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2 // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + '='
}
return base64
}