Search code examples
javascriptbinaryblobdata-url

Convert Data-URL to valid byte string


I am attempting to allow the user to save images that have been rendered to the canvas. There may be several images and I want the user to be able to download all the images at once as a single tar file. I am trying to write the code to generate this tar file. I have most of this working, but when the tar file is downloaded to my computer at the end I discover that some of the binary data that composes the png files has been corrupted. Here is the relevant code:

var canvBuffer = $('<canvas>').attr('width', canvasWidth).attr('height', canvasHeight);
var ctxBuffer = canvBuffer[0].getContext('2d');

imgData.data.set(renderedFrames[0]);
ctxBuffer.putImageData(imgData,0,0);

var strURI = canvBuffer[0].toDataURL();
var byteString = atob(decodeURIComponent(strURI.substring(strURI.indexOf(',')+1)));

toTar([byteString]);

function toTar(files /* array of blobs to convert */){
        var tar = '';

        for (var i = 0, f = false, chkSumString, totalChkSum, out; i < files.length; i++) {

            f = files[i];
            chkSumString = '';

            var content  = f;


            var name = 'p1.png'.padRight('\0', 100);
            var mode = '0000664'.padRight('\0', 8);
            var uid = (1000).toString(8).padLeft('0', 7).padRight('\0',8);
            var gid = (1000).toString(8).padLeft('0', 7).padRight('\0',8);
            var size = (f.length).toString(8).padLeft('0', 11).padRight('\0',12);

            var mtime = '12123623701'.padRight('\0', 12); // modification time
            var chksum = '        '; // enter all spaces to calculate chksum
            var typeflag = '0';
            var linkname = ''.padRight('\0',100);
            var ustar = 'ustar  \0';
            var uname = 'chris'.padRight('\0', 32);
            var gname = 'chris'.padRight('\0', 32);

            // Construct header with spaces filling in for chksum value
            chkSumString = (name + mode + uid + gid + size + mtime + chksum + typeflag + linkname + ustar + uname + gname).padRight('\0', 512);


            // Calculate chksum for header
            totalChkSum = 0;
            for (var i = 0, ch; i < chkSumString.length; i++){
                ch =  chkSumString.charCodeAt(i);
                totalChkSum += ch;
            }

            // reconstruct header plus content with chksum inserted
            chksum = (totalChkSum).toString(8).padLeft('0', 6) + '\0 ';
            out = (name + mode + uid + gid + size + mtime + chksum + typeflag + linkname + ustar + uname + gname).padRight('\0', 512);
            out += content.padRight('\0', (512 + Math.floor(content.length/512) * 512)); // pad out to a multiple of 512
            out += ''.padRight('\0', 1024); // two 512 blocks to terminate the file
            tar += out;
        }

        var b = new Blob([tar], {'type': 'application/tar'});
        window.location.href =  window.URL.createObjectURL(b);

    }

I am putting a previously rendered frame onto a non-rendered canvas and using the canvases toDataURL() method to an encoded png version of the frame with Base64 encoding. Next I use atob to convert this to a byte string so it can be appended to the contents of the tar file I am generating.

When I view the file in a hex editor my tar header is correct, but the contents of the png are not quite right. The ASCII contents looks normal but binary data is scrambled.

Any help offered would be greatly appreciated. Thanks.

PS

I have attached links to related posts that I have looked at. They have been helpful, but I have not yet seen anything there that fully resolves my issues. Thanks.

Convert Data URI to File then append to FormData, and Data URI to Object URL with createObjectURL in chrome/ff


Solution

  • OK, I have resolved the issue. The problem was the Blob constructor at the end of toTar. passing it a string caused the blob to treat my data as UTF-8 instead of binary, I need to instead pass it arrayBuffer for an array of unsigned integers. Below is my solution

      var byteArray = new Uint8Array(tar.length);
      for (var b = 0; b < tar.length; b++) {
           byteArray[b] = tar.charCodeAt(b);
      }
    
      var b = new Blob([byteArray.buffer], {'type': 'application/tar'});
      window.location.href =  window.URL.createObjectURL(b);
    

    I should rewrite toTar to build the file in Uint8Array and remove the need to convert at the end, but this adequately answers my question and hopefully will help someone else. Thanks.