3

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

Community
  • 1
  • 1
Chris Wininger
  • 651
  • 8
  • 13
  • did you try sending a parameter `image/png` like in http://stackoverflow.com/questions/923885/capture-html-canvas-as-gif-jpg-png-pdf – akonsu Apr 13 '13 at 23:07
  • I had not because png is supposed to be the default and as noted above window.location.href = dataURI; saves a valid png file. Just to be sure; however, I just tried it. No luck. Thanks. – Chris Wininger Apr 13 '13 at 23:20
  • do you mean that `canvas.toDataURL` has a bug and returns a corrupted image, or what do you mean? If I understand code correctly, the first line sets the width and height of the canvas element. When this is done, the canvas is erased. – akonsu Apr 14 '13 at 02:30
  • No, there is nothing wrong with toDataURL, that works correctly. I am just using the canvas as a handy way to produce a png compressed image. The problem is you receive the image in a base64 encoded string. Normally you would then set this string as the src of a link or img element and the browser handles decoding it. In my case; however, I want to decode the base64 string in the JavaScript. I need it as a bytestring to I build it package it into a tar file. – Chris Wininger Apr 14 '13 at 03:21
  • what happens if you do not use `decodeURIComponent`? – akonsu Apr 14 '13 at 04:07
  • The decodeURIComponent actually doesn't make a difference.That was just there because I had tried that hoping it might help, but no difference with or without. – Chris Wininger Apr 14 '13 at 04:37
  • I hope in your `toTar`, the inner loop that uses `i` does not override the current `i` of the outer loop. Did you try to generate a tar with text files? Does it work? Basically, I do not know why it is not working but I am curious to find out too. – akonsu Apr 14 '13 at 04:56
  • I should correct that issue with i, but yes, it does work with a text file. In the case of the png I am getting a tar file out, but the png file inside the tar reads as corrupted. When I look at it in hex editor and compare what's there against what is supposed to be there it looks close but there are strange discrepancies. I wish I could show this, but not quite sure how to do that here. – Chris Wininger Apr 14 '13 at 05:24
  • what is the length of `chksum` variable when you reconstruct the header in `toTar`? – akonsu Apr 14 '13 at 08:01
  • I have found the issue (see my answer below). Thanks for your help. – Chris Wininger Apr 14 '13 at 23:24

1 Answers1

0

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.

Chris Wininger
  • 651
  • 8
  • 13