Black images from canvas.toDataURL() - javascript

I have created a web application where users can upload images from their filesystem but in few cases I only get black image data on the server side. I have never got this issue in my development environment on lan even with the original images causing the error. This failure occurs overall only sporadic but especially one user is affected. All users should work with the latest firefox. I have read most of the question about this topic and I dont think this is related to a security issue, the jpg/png setting or preserveDrawingBuffer. Because I have never got this problem on lan with a fast PC and the fact that often the last selected images are affected it seems to be a synchronization problem.
To avoid this I work with $.Deferred and Callbacks but may be I missed something. First I load and convert the selected images directly into a list of canvas. The upload is disabled until this is finished. During the upload I just loop through the canvas list and read the data (canvas.toDataURL()) with a $.Deferred and transfer it to the server via ajax.
Get the images to the page:
var file = document.getElementById('fileSelect');
if (file != undefined) {
file.onchange = function (e) {
btnUpload.SetEnabled(false);
loadImgs(e, function () {
btnUpload.SetEnabled(true);
});
};
}
function loadImgs(e, callback) {
for (var t = 0; t < e.target.files.length; t++) {
if ($('.CanvasClass').length <= 20) {
var myTmpImg = new Image();
var myName = e.target.files[t].name;
myTmpImg.src = URL.createObjectURL(e.target.files[t]);
myTmpImg.onload = function () {
var GrenzeX = 1024;
var GrenzeY = 768;
var myFactor = 1;
if (myTmpImg.naturalWidth > GrenzeX || myTmpImg.naturalHeight > GrenzeY) {
if ((GrenzeX / myTmpImg.naturalWidth) < (GrenzeY / myTmpImg.naturalHeight)) {
myFactor = GrenzeX / myTmpImg.naturalWidth;
} else {
myFactor = GrenzeY / myTmpImg.naturalHeight;
}
}
var myCanvas = document.createElement('canvas');
myCanvas.className = "CanvasClass";
myCanvas.height = this.naturalHeight * myFactor;
myCanvas.width = this.naturalWidth * myFactor;
var ctx = myCanvas.getContext("2d");
ctx.drawImage(this, 0, 0, myCanvas.width, myCanvas.height);
$("#myImages").append(myCanvas);
myMetaData.push(new newImgData('', '', myName));
}
}
}
if (callback && typeof callback == "function") {
callback();
}
}
Read and transfer the Data:
$('.CanvasClass').each(function () {
var fd = new FormData();
var base64Data = getBase64Image(this, false);
base64Data.then(function (result) {
//Create Ajax Request
});
});
function getBase64Image(myCanvas, modus) {
var deferred = $.Deferred();
var dataURL = myCanvas.toDataURL("image/jpeg", 0.9);
if (modus === true) {
deferred.resolve(dataURL);
} else {
deferred.resolve(dataURL.replace(/^data:image\/(png|jpeg);base64,/, ""));
}
return deferred.promise();
}
I can also exclude issues during the data transfer because I logged the size of the images on clientside before uploading and compared this to the data at serverside and it was the same.

Related

jQuery File Upload - get size of uploaded images

I am using the jQuery File Upload to upload product images. There are different "types" of images for each product (different size/dimensions).
I have altered the upload template so that a dropdown of possible image types is shown for each file added. Before clicking upload, the user must pick from the dropdown to indicate what image type the file being uploaded is.
Some image types are an exact size, some just have a minimum width. What I want to do is compare the actual image size to the type chosen to validate they are indicating the correct type.
I'm attempting to do this via the "fileuploadsubmit" callback where I have already added a check to make sure an option is selected in the dropdown.
$('#fileupload').bind('fileuploadsubmit', function (e, data) {
$('#uploading_note').removeClass("alert-danger").html("");
var inputs = data.context.find(':input');
if (inputs.filter(function () {
return !this.value && $(this).prop('required');
}).first().focus().length) {
data.context.addClass("alert-danger");
data.context.find('.file_msg').html("Image Type is required!");
data.context.find('button').prop('disabled', false);
return false;
}
var fixed_dimension = 0;
var wt = 0;
var ht = 0;
var image_size = getImageSize(data,function(width, height) {
wt = width;
ht = height;
console.log (wt+'x'+ht);
return [wt,ht];
});
...........
This is the getImageSize function.
function getImageSize(data,callback){
var img = new Image();
img.src = _URL.createObjectURL(data.files[0]);
img.onload = function () {
var w = this.width;
var h = this.height;
console.log(w+'-getImageSize-'+h);
callback (w,h);
};
}
The console.log() in both output correctly but I can't get these numbers outside the functions to use them. I understand it has to do with the functions be asynchronous but I can't figure out how to get around it. Or maybe there is a better jQuery File Upload built in option I'm missing.
I understand the asynchronous function issue, this one was attempt at finding a solution to validating the image size. I have also attempted to do this in the PHP script. It works in that I can prevent the upload of an image but the user experience is poor. So I am still looking for a solution as to how to do image size validation using jQuery File Upload.
Below is an update version of the code.
$('#fileupload').bind('fileuploadsubmit', function (e, data) {
console.log(data);
$('#uploading_note').removeClass("alert-danger").html("");
var inputs = data.context.find(':input');
if (inputs.filter(function () {
return !this.value && $(this).prop('required');
}).first().focus().length) {
console.log(data.context);
data.context.addClass("alert-danger");
data.context.find('.file_msg').html("Image Type is required!");
//$('#uploading_note').addClass("alert-danger").html("Image Type is required!");
data.context.find('button').prop('disabled', false);
return false;
}
var fixed_dimension = 0;
var wt = 0;
var ht = 0;
var image_size = getImageSize(data,function(width, height) {
wt = width;
ht = height;
console.log(wt+'-getImageSize-function'+ht);
switch (inputs[0].value){
case "16x9_background":
case "16x9":
var req_width = 1920;
var req_height = 1080;
fixed_dimension = 1;
break;
case "3x4":
var req_width = 1600;
var req_height = 1200;
fixed_dimension = 1;
break;
case "hires":
var req_width = 1400;
var req_height = 0;
break;
case "lowres":
var req_width = 200;
var req_height = 0;
break;
}
if (fixed_dimension){
if (wt != req_width || ht != req_height){
data.context.addClass("alert-danger");
data.context.find('.file_msg').html("Image dimensions must be exactly "+req_width+"x"+req_height+". This image is "+wt+"x"+ht+". Image not uploaded.");
return false;
}
} else {
if (wt < req_width){
data.context.addClass("alert-danger");
data.context.find('.file_msg').html("Image dimensions wrong!! Width must be "+req_width+", not "+wt+". Image not uploaded.");
console.log(wt+'x'+ht);
return false;
}
}
d = inputs.serializeArray();
d.push({ name: "folder", value: $('#barcode').val() });
d.push({ name: "stock_num", value: $('#stock_num').val() });
data.formData = d;
console.log(wt+"xxxx"+ht);
data.submit(); // SUMBIT THE FORM
});
console.log(wt+"xxxx"+ht);
console.log("imagesize:"+image_size);
return false; //STOP THE DEFAULT SUBMIT
});
What I ended up doing was removing the actual form submit button and replace it with a button (not type submit) that calls the validation code. If it passes, the form is then submitted normally (via data.submit()), if not it returns the error. I had to remove the file level submit buttons (or I found it easier to do that).
$('#fileupload').fileupload({
url: 'process.php',
previewThumbnail: true,
sequentialUploads: true,
acceptFileTypes: /(\.|\/)(jpe?g|pdf|zip)$/i,
maxChunkSize: 10000000,
add: function (e, data) {
$('#start_upload_button').click(function () {
getImageSize3(data).then(function(dimensions) {
image validation... return false if any errors
data.submit() // submit the form
});

Canvas image parsing orientation not in right sequence

i've image uploader which using canvas and trying to get orientation using load-image.all.min.js is fine. but when i choose multiple image orientation parsing function saving data not one by one.
which means if i choose 1 image. it transferring data to 'upload_canvas.php?ori='+ori with correct ori variable.
but when i choose multiple image to upload example 3 images (with orientation 1, 1, 8)
it passing data to server upload_canvas.php?ori=8, upload_canvas.php?ori=8, upload_canvas.php?ori=8. only last ori variable.
maybe orientation parsing function already looped before uploading image data to server one by one.
how to transfer image with correct orientation to server?
below my using code.
document.querySelector('form input[type=file]').addEventListener('change', function(event){
// Read files
var files = event.target.files;
var ori = 1;
// Iterate through files
for (var i = 0; i < files.length; i++) {
// Ensure it's an image
if (files[i].type.match(/image.*/)) {
//Get image orienatation
loadImage.parseMetaData(files[i], function (data) {
if (data.exif) {
ori = data.exif.get('Orientation');
console.log("ori: "+ori);
} else {ori = 1;}
});
// Load image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
canvas.width = image.width;
canvas.height = image.height;
drawImageIOSFix(canvas.getContext('2d'),image, 0, 0, image.width, image.height, 0, 0, width, height);
// Upload image
var xhr = new XMLHttpRequest();
if (xhr.upload) {
// Update progress
xhr.upload.addEventListener('progress', function(event) {
var percent = parseInt(event.loaded / event.total * 100);
progressElement.style.width = percent+'%';
}, false);
// File uploaded / failed
xhr.onreadystatechange = function(event) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
//some code
} else {
imageElement.parentNode.removeChild(imageElement);
}
}
}
xhr.open('post', 'upload_canvas.php?t=' + Math.random()+'&ori='+ori, true);
xhr.send(canvas.toDataURL('image/jpeg'));
}
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(files[i]);
}
}
// Clear files
event.target.value = '';});
Your variable ori is a global variable, that is shared between all images. The code in the .onload functions aren't run immediately, but only after your for() loop has gone through all the images. At this point ori will contain the orientation of the last image.
To fix, move the variable and parseMetaData into the reader.onload function.
...
var reader = new FileReader();
reader.onload = function (readerEvent) {
var ori;
loadImage.parseMetaData(files[i], ...)
var image = new Image();
image.onload = function (imageEvent) {
...
Warning: Not tested!

Drawing application undo button

i have problem with undo button for my drawing application
<input id="undo" type="image" src="images/undo.ico" onclick="cUndo()" width="25" height="25">
var cPushArray = new Array();
var cStep = -1;
var ctx;
// ctx = document.getElementById('myCanvas').getContext("2d");
function cPush() {
cStep++;
if (cStep < cPushArray.length) { cPushArray.length = cStep; }
cPushArray.push(document.getElementById('myCanvas').toDataURL());
}
function cUndo() {
if (cStep > 0) {
cStep--;
var canvasPic = new Image();
canvasPic.src = cPushArray[cStep];
canvasPic.onload = function () { ctx.drawImage(canvasPic, 0, 0); }
}
}
But this doesn't work.Please help
First remark : As #markE underlines, saving with DataURL has a high memory cost. You might consider saving the draw commands + their arguments within an array instead.
Seek for tuts/Stack Overflow post on the topic, out of a few posts you should get some nice ideas.
Anyway, you can go with the dataURL solution in a first time to get your application working (with a limit of 20 undos or like to avoid memory explosion), then you can later improve the undo to reach a higher limit.
I updated my code to handle such a stack limit.
For your issue : onload should be hooked prior to setting the src, but anyway with a DataURL you are not async : the image is built at once, so no need to hook unload.
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");
var historic = [];
var maxHistoricLength = 20; // might be more or less, depending on canvas size...
function saveForUndo() {
historic.push(canvas.toDataURL());
if (historic.length === maxHistoricLength +1) historic.shift();
}
function canUndo() {
return (historic.length !== 0 );
}
function undo() {
if (!canUndo()) return;
var lastDataURL = historic.pop();
var tmpImage = new Image();
tmpImage.src = lastDataURL;
ctx.drawImage(tmpImage, 0, 0);
}

WinJS barcode reader issues(Image not loading in canvas)

Am working on a winjs based barcode reader application. Initially I will capture the image using camera capture API and will pass that file object to a canvas element and read its barcode using ZXing library. But the image passed to the canvas is not getting rendered completely as follows.
Following is my html code
<body>
<p>Decoding test for static images</p>
<canvas id="canvasDecode" height="200" width="200"></canvas>
<h3 id="result"></h3>
<p>Put some content here and leave the text box</p>
<input id="input" class="win-textarea" onchange="generate_barcode()">
<h3 id="content"></h3>
<canvas id="canvasEncode" height="200" width="200"></canvas>
<img class="imageHolder" id="capturedPhoto" alt="image holder" />
</body>
following is my javascript code
(function () {
"use strict";
WinJS.Binding.optimizeBindingReferences = true;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
var dialog = new Windows.Media.Capture.CameraCaptureUI();
var aspectRatio = { width: 1, height: 1 };
dialog.photoSettings.croppedAspectRatio = aspectRatio;
dialog.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo).then(function (file) {
if (file) {
// draw the image
var canvas = document.getElementById('canvasDecode')
var ctx = canvas.getContext('2d');
var img = new Image;
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
}
img.src = URL.createObjectURL(file);
// open a stream from the image
return file.openAsync(Windows.Storage.FileAccessMode.readWrite);
}
})
.then(function (stream) {
if (stream) {
// create a decoder from the image stream
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(stream);
}
})
.done(function (decoder) {
if (decoder) {
// get the raw pixel data from the decoder
decoder.getPixelDataAsync().then(function (pixelDataProvider) {
var rawPixels = pixelDataProvider.detachPixelData();
var pixels, format; // Assign these in the below switch block.
switch (decoder.bitmapPixelFormat) {
case Windows.Graphics.Imaging.BitmapPixelFormat.rgba16:
// Allocate a typed array with the raw pixel data
var pixelBufferView_U8 = new Uint8Array(rawPixels);
// Uint16Array provides a typed view into the raw 8 bit pixel data.
pixels = new Uint16Array(pixelBufferView_U8.buffer);
if (decoder.bitmapAlphaMode == Windows.Graphics.Imaging.BitmapAlphaMode.straight)
format = ZXing.BitmapFormat.rgba32;
else
format = ZXing.BitmapFormat.rgb32;
break;
case Windows.Graphics.Imaging.BitmapPixelFormat.rgba8:
// For 8 bit pixel formats, just use the returned pixel array.
pixels = rawPixels;
if (decoder.bitmapAlphaMode == Windows.Graphics.Imaging.BitmapAlphaMode.straight)
format = ZXing.BitmapFormat.rgba32;
else
format = ZXing.BitmapFormat.rgb32;
break;
case Windows.Graphics.Imaging.BitmapPixelFormat.bgra8:
// For 8 bit pixel formats, just use the returned pixel array.
pixels = rawPixels;
if (decoder.bitmapAlphaMode == Windows.Graphics.Imaging.BitmapAlphaMode.straight)
format = ZXing.BitmapFormat.bgra32;
else
format = ZXing.BitmapFormat.bgr32;
break;
}
// create a barcode reader
var reader = new ZXing.BarcodeReader();
reader.onresultpointfound = function (resultPoint) {
// do something with the resultpoint location
}
// try to decode the raw pixel data
var result = reader.decode(pixels, decoder.pixelWidth, decoder.pixelHeight, format);
// show the result
if (result) {
document.getElementById("result").innerText = result.text;
}
else {
document.getElementById("result").innerText = "no barcode found";
}
});
}
});
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
}
args.setPromise(WinJS.UI.processAll());
}
};
app.oncheckpoint = function (args) {
// TODO: This application is about to be suspended. Save any state
// that needs to persist across suspensions here. You might use the
// WinJS.Application.sessionState object, which is automatically
// saved and restored across suspension. If you need to complete an
// asynchronous operation before your application is suspended, call
// args.setPromise().
};
app.start();
})();
function generate_barcode() {
// get the content which the user puts into the textbox
var content = document.getElementById("input").value;
// create the barcode writer and set some options
var writer = new ZXing.BarcodeWriter();
writer.options = new ZXing.Common.EncodingOptions();
writer.options.height = 200;
writer.options.width = 200;
writer.format = ZXing.BarcodeFormat.qr_CODE;
// encode the content to a byte array with 4 byte per pixel as BGRA
var imagePixelData = writer.write(content);
// draw the pixel data to the canvas
var ctx = document.getElementById('canvasEncode').getContext('2d');
var imageData = ctx.createImageData(imagePixelData.width, imagePixelData.heigth);
var pixel = imagePixelData.pixel
for (var index = 0; index < pixel.length; index++) {
imageData.data[index] = pixel[index];
}
ctx.putImageData(imageData, 0, 0);
}
The same code worked well when I was using the file picker API. Let me knew where I went wrong.
I think that you're running into some problems with asynchronicity here. I applaud your use of chained calls to then(), but there's a hidden problem - assignment to img.src begins an asynchronous operation while the image is loaded. Your code continues on BEFORE the img.onload event has been raised, and so the closure which img.onload reaches into for the img variable (the pointer to the file URL) changes before the image has fully loaded.
Here's some code that worked for me.
// Inside handler for app.activated ...
var dialog = new Windows.Media.Capture.CameraCaptureUI();
var aspectRatio = { width: 1, height: 1 };
dialog.photoSettings.croppedAspectRatio = aspectRatio;
dialog.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.then(function (file) {
// draw the image
var canvas = document.getElementById('canvasDecode')
var ctx = canvas.getContext('2d');
var img = new Image;
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
// open a stream from the image
decodePic(file);
}
img.onerror = function (err) {
WinJS.log && WinJS.log("Error loading image");
}
img.src = URL.createObjectURL(file);
});
And then I moved the file decoding / barcode reading stuff to a new function.
function decodePic(file) {
file.openAsync(Windows.Storage.FileAccessMode.readWrite)
.then(function (stream) {
if (stream) {
// create a decoder from the image stream
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(stream);
}
})
.done(function (decoder) {
if (decoder) {
// get the raw pixel data from the decoder
decoder.getPixelDataAsync().then(function (pixelDataProvider) {
// YOUR BARCODE READING CODE HERE.
});
}
});
}
I hope this helps!

How to add undo-functionality to HTML5 Canvas?

I have a Sketching app done in all HTML5 and Javascript, and I was wondering how I would create an Undo Button, so you could undo the last thing you drew. Any idea?
You have to store all modifications in a datastructure. Then you can delete the latest modification if the user wants to undo it. Then you repaint all drawing operations from your datastructure again.
On http://arthurclemens.github.io/Javascript-Undo-Manager/ I have a working example of undo with a canvas element. When you make a modification, you feed the undo manager the undo and redo methods. Tracking of the position in the undo stack is done automatically. Source code is at Github.
The other option, if you need to manipulate objects is to convert your canvas to SVG using a library that preserves the Canvas API preventing a rewrite.
At least one such library exists at this time (November 2011):
SVGKit
Once you have SVG, it is much easier to remove objects and much more without the need to redraw the entire canvas.
Here is a solution that works for me. I have tried it in the latest versions of Firefox and Chrome and it works really well in those two browsers.
var isFirefox = typeof InstallTrigger !== 'undefined';
var ctx = document.getElementById('myCanvas').getContext("2d");
var CanvasLogBook = function() {
this.index = 0;
this.logs = [];
this.logDrawing();
};
CanvasLogBook.prototype.sliceAndPush = function(imageObject) {
var array;
if (this.index == this.logs.length-1) {
this.logs.push(imageObject);
array = this.logs;
} else {
var tempArray = this.logs.slice(0, this.index+1);
tempArray.push(imageObject);
array = tempArray;
}
if (array.length > 1) {
this.index++;
}
return array;
};
CanvasLogBook.prototype.logDrawing = function() {
if (isFirefox) {
var image = new Image();
image.src = document.getElementById('myCanvas').toDataURL();
this.logs = this.sliceAndPush(image);
} else {
var imageData = document.getElementById('myCanvas').toDataURL();
this.logs = this.sliceAndPush(imageData);
}
};
CanvasLogBook.prototype.undo = function() {
ctx.clearRect(0, 0, $('#myCanvas').width(), $('#myCanvas').height());
if (this.index > 0) {
this.index--;
this.showLogAtIndex(this.index);
}
};
CanvasLogBook.prototype.redo = function() {
if (this.index < this.logs.length-1) {
ctx.clearRect(0, 0, $('#myCanvas').width(), $('#myCanvas').height());
this.index++;
this.showLogAtIndex(this.index);
}
};
CanvasLogBook.prototype.showLogAtIndex = function(index) {
ctx.clearRect(0, 0, $('#myCanvas').width(), $('#myCanvas').height());
if (isFirefox) {
var image = this.logs[index];
ctx.drawImage(image, 0, 0);
} else {
var image = new Image();
image.src = this.logs[index];
ctx.drawImage(image, 0, 0);
}
};
var canvasLogBook = new CanvasLogBook();
So every time you draw any thing you will there after run function canvasLogBook.logDrawing() to store a snapshot of the canvas and then you can call canvasLogBook.undo() to undo and canvasLogBook.redo() to redo.

Categories

Resources