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
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.
Related
I want to display an image that comes from PostgreSql database. My image is saved as bytea in the postgre DB.
So I select my image from a php script :
$img = pg_unescape_bytea($row[7]);
$bs64 = base64_encode($img);
I see that "pg_unescape_bytea" is mandatory with bytea type, and I encode the result in base64.
So, my Javascript client receive a base64 string, and I try to display it in a canvas.
var canvas = document.getElementById('canvasPrev');
var ctx = canvas.getContext('2d');
var imageData = ctx.createImageData(200, 200);
var pixels = imageData.data;
if (obj[i].image != null) {
var data = text2ua(obj[i].image);
var buffer = data;
for (var j = 0; j < buffer.length; j += 4) {
pixels[j] = buffer[j];
pixels[j + 1] = buffer[j + 1];
pixels[j + 2] = buffer[j + 2];
pixels[j + 3] = buffer[j + 3];
}
console.log(pixels);
ctx.putImageData(imageData, 0, 0);
}
And text2ua function :
function text2ua(s) {
var ua = new Uint8Array(s.length);
for (var i = 0; i < s.length; i++) {
ua[i] = s.charCodeAt(i);
}
return ua;
}
I know that my canvas is working because I achieve to display an image by the same way, but that doesn't come from postgre DB.
My result image looks like the original, but the colors aren't good and the image is semi transparent.
The result that I have :
What I want :
(Dimensions are the same in reality)
I am converting image data held in an arrayBuffer into a string and then base64 encoding it using the following:
var base64EncodedImage = arrayBufferToBase64(response);
arrayBufferToBase64 = function(buffer) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa(binary);
}
When I try to add this data to the src attribute of an image tag
this.imageTag.src = 'data:image/jpeg;base64,' + base64EncodedImage;
..it doesn't display the image, due, I assume to some error in the base64 encoding. When I examine the encodedData, at around character 5000 there is an ellipsis (…) which I assume is what's causing the problems. What is the btoa function doing here and how can I fix it? Is it due to bad data in the arrayBuffer or am I doing something wrong in the binary conversion?
I have a .Net (VB/C#) backend on one server and a javascript/jquery front-end on another. I am attempting to pass file data from the backend to the front-end by reading the file in .Net as a byte array, converting it to a base64 string and passing that to the js front-end along with a variable indicating the file type.
Once the data is in the js front-end I attempt to convert the base64 string to a file blob in order to read it with readAsDataURL() so that it can be displayed in an iframe or downloaded by the client.
For testing I am using a pdf file. The process of passing the data works but the file data is not recognized by the pdf viewer. The iframe loads the viewer but I get a message about the file not being recognized as a pdf file.
I have done a tone of searching and have gotten a lot of questions answered by searching stackoverflow but this eludes me.
Any suggestions would be much appreciated. Thank you.
Update:
Here is my VB.Net code:
Sub OnReadFileToViewer(ByVal filePath As String)
Dim okToView As Boolean = False
Dim fileBLOB As String = String.Empty
Dim fileEncoded As String = String.Empty
If (file.Exists(filePath)) Then
Try
Using fRead As FileStream = New FileStream(filePath, FileMode.Open, FileAccess.Read)
Dim bytes() As Byte = New Byte((CType(fRead.Length, Integer)) - 1) {}
Dim numBytesToRead As Integer = CType(fRead.Length, Integer)
Dim numBytesRead As Integer = 0
While (numBytesToRead > 0)
' Read may return anything from 0 to numBytesToRead.
Dim n As Integer = fRead.Read(bytes, numBytesRead, _
numBytesToRead)
' Break when the end of the file is reached.
If (n = 0) Then
Exit While
End If
numBytesRead = (numBytesRead + n)
numBytesToRead = (numBytesToRead - n)
End While
numBytesToRead = bytes.Length
fileEncoded = System.Convert.ToBase64String(bytes)
End Using
okToView = True
Catch ex As Exception
'
End Try
End If
If (okToView) Then
' Code to send data to js fron-end
End If
End Sub
And my js front-end for converting the base64 string to BLOB and the iframe preview:
function PreviewFile(file) {
var fileBlob = b64toBlob(file,'application/pdf','');
var reader = new FileReader();
var fileType = e.target.getAttribute("type");
alert(fileType);
reader.onload = function (e) {
if (fileType)
$("#fileViewerDiv").append('<iframe id="fileVieweriFrame" class="fileViewer"></iframe>'); // + e.target.result + '">');
$("#fileVieweriFrame").attr('src', file); //e.target.result);
$("#fileVieweriFrame").dialog();
}
reader.readAsDataURL(fileBlob);
}
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
The alert(fileType) does display the correct file type - application/pdf.
I have to admit that I lifted the b64toBlob() function from a blog somewhere but I have forgotten which one and have lost the URL to the site. My apologies to the original author. If anyone recognizes the code and knows the author/site please let me know
Edit
b64toBlob() by Jeremy Banks - Based on this SO answer
Read the link:
http://docs.mongodb.org/manual/reference/object-id/
The link says that the ObjectId will have Time, Machine, Process Id & Counter values.
Then, how to parse a ObjectId in JavaScript and get those details?
In node we can make use of buffers to grab integers from a hex string.
.findOne(cond, function(err, doc){
// create a 12 byte buffer by parsing the id
var ctr = 0;
var b = new Buffer(doc._id.str, 'hex');
// read first 4 bytes as an integer
var epoch = b.readUInt32BE(0);
ctr += 4;
// node doesn't have a utility for 'read 3 bytes' so hack it
var machine = new Buffer([0, b[ctr], b[ctr+1], b[ctr+2]]).readUInt32BE(0);
ctr += 3;
// read the 2 byte process
var process = b.readUInt16BE(ctr);
ctr += 2;
// another 3 byte one
var counter = new Buffer([0, b[ctr], b[ctr+1], b[ctr+2]]).readUInt32BE(0);
});
For driver version <2.2 change doc._id.str to doc._id.toHexString().
The potentially simpler technique is to just use parseInt and slice. Because hex digits are half of a byte our offsets are twice as high.
var id = doc._id.str, ctr = 0;
var epoch = parseInt(id.slice(ctr, (ctr+=8)), 16);
var machine = parseInt(id.slice(ctr, (ctr+=6)), 16);
var process = parseInt(id.slice(ctr, (ctr+=4)), 16);
var counter = parseInt(id.slice(ctr, (ctr+=6)), 16);
I'm trying to convert Uint8ClampedArray picked from webp images on canvas using the getImageData method.
I've tried the method found in this thread: How can I create a canvas imageData array from an arrayBuffer representation of a JPG
The output I get is only this part:
AAAAAA==
How to get the full data. Here's how I tried unsuccessful:
function Uint8toBase64(idata) {
this.idata = idata;
var u = new Uint8ClampedArray(4);
for (var i = 0; i < idata.length; i += 4) {
idata[0] = u[i];
idata[1] = u[i + 1];
idata[2] = u[i + 2];
idata[3] = u[i + 3];
}
var str = String.fromCharCode.apply(null, u);
var b64 = btoa(str);
console.log(b64);
}
Thanks in advance,