Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Web and WebWorks Development

Reply
New Contributor
tbh726
Posts: 6
Registered: ‎02-23-2012
My Device: PlayBook
My Carrier: ATT

Re: stringToblog save image problem

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.

Please use plain text.
BlackBerry Development Advisor
twindsor
Posts: 795
Registered: ‎07-15-2008
My Device: Z10
My Carrier: Bell

Re: stringToblog save image problem

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\Utilities\src\blackberry\utils", and change the stringToBlob to the following:

 

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 :smileyhappy:

 

See attached image for my canvas output. I'd say it's as good as a Picasso.

Tim Windsor
Application Development Advisor II
Please use plain text.
Developer
laurentC
Posts: 266
Registered: ‎02-05-2010
My Device: Blackberry Z10
My Carrier: Bouygues Telecom

Re: stringToblog save image problem

Thanks a lot Tim :Clap: I'll give it a try.

 

That's what I call support !

 

 

Twitter : @LaurentKP
My apps
Please use plain text.
New Contributor
tbh726
Posts: 6
Registered: ‎02-23-2012
My Device: PlayBook
My Carrier: ATT

Re: stringToblog save image problem

Tim, Thank you, I will also give it a try today Tim
Please use plain text.
New Contributor
tbh726
Posts: 6
Registered: ‎02-23-2012
My Device: PlayBook
My Carrier: ATT

Re: stringToblog save image problem

Tim,

 

this patch works like a charm, i see an update coming out for my icancolor app...

 

thanks again

 

tim

Please use plain text.
New Contributor
clarose78
Posts: 5
Registered: ‎03-30-2011
My Device: Playbook, Torch
My Carrier: Rogers

Re: stringToblog save image problem

[ Edited ]

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

Please use plain text.
BlackBerry Development Advisor
twindsor
Posts: 795
Registered: ‎07-15-2008
My Device: Z10
My Carrier: Bell

Re: stringToblog save image problem

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.

Tim Windsor
Application Development Advisor II
Please use plain text.
New Contributor
clarose78
Posts: 5
Registered: ‎03-30-2011
My Device: Playbook, Torch
My Carrier: Rogers

Re: stringToblog save image problem

[ Edited ]

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 : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    // 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.appDirs.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

 

  

Please use plain text.
BlackBerry Development Advisor
twindsor
Posts: 795
Registered: ‎07-15-2008
My Device: Z10
My Carrier: Bell

Re: stringToblog save image problem

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?

Tim Windsor
Application Development Advisor II
Please use plain text.
BlackBerry Development Advisor
twindsor
Posts: 795
Registered: ‎07-15-2008
My Device: Z10
My Carrier: Bell

Re: stringToblog save image problem

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 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

  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
}
Tim Windsor
Application Development Advisor II
Please use plain text.