I have built an angularJS application, in this application SVG files represent garments that a user chooses. I have a download button which (currently) saves the first SVG as a PNG into a database and I use a view to display this "preview".
The directive I created looks like this:
.directive('kdExport', function () {
return {
restrict: 'A',
scope: {
target: '#kdExport',
team: '='
},
controller: 'ExportImageController',
link: function (scope, element, attrs, controller) {
console.log(scope.team);
// Bind to the onclick event of our button
element.bind('click', function (e) {
// Prevent the default action
e.preventDefault();
// Generate the image
controller.generateImage(scope.target, scope.team, function (preview) {
// Create our url
var url = '/kits/preview/' + preview.id;
// Open a new window
window.open(url, '_blank');
});
});
}
};
})
and the controller looks like this:
.controller('ExportImageController', ['PreviewService', function (service) {
var self = this;
// Function to remove the hidden layers of an SVG document
var removeHidden = function (element) {
// Get the element children
var children = element.children(),
i = children.length;
// If we have any children
if (children.length) {
// For each child
for (i; i >= 0; i--) {
// Get our child
var child = angular.element(children[i - 1]);
// Remove hidden from the child's children
removeHidden(child);
// Finally, if this child has the class "hidden"
if (child.hasClass("hidden")) {
// Remove the child
child.remove();
}
}
}
};
// Public function to generate the image
self.generateImage = function (element, team, onSuccess) {
// Get our SVG
var target = document.getElementById(element),
container = target.getElementsByClassName('svg-document')[0],
clone = container.cloneNode(true);
// Remove hidden layers
removeHidden(angular.element(clone));
// Create our data
var data = clone.innerHTML,
svg = new Blob([data], { type: 'image/svg+xml;charset=utf-8' });
// Get our context
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
// Create our image
var DOMURL = window.URL || window.webkitURL || window,
url = DOMURL.createObjectURL(svg),
img = new Image();
// When the image has loaded
img.onload = function () {
canvas.width = 1000;
canvas.height = 500;
// Draw our image using the context
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, 1000, 500);
DOMURL.revokeObjectURL(url);
// Get our URL as a base64 string
var dataURL = canvas.toDataURL("image/png");
// Create our model
var model = {
teamName: team.name,
sport: team.sport,
data: dataURL
};
// Create our preview
service.create(model).then(function (response) {
// Invoke our success callback
onSuccess(response);
});
}
// Set the URL of the image
img.src = url;
};
}])
This works fine for a single SVG document, but now the client has asked me to do this for multiple SVGs with a title under each one and they want it all in one PNG.
I have not done a lot of work with canvasing, so I am not sure if this can be done.
Does anyone know how I might achieve this?
Ok, so I figured this out myself using promises.
Basically I created a method called drawImage that allowed me to draw an image for each SVG.
To make sure that all images were drawn before I invoke toDataURL I made the function return a promise and once the image loaded I resolved that promise.
Then I just used a $q.all to get the dataURL and save the data to my database.
The methods looked like this:
// Private function for drawing our images
var drawImage = function (canvas, ctx, clone) {
// Defer our promise
var deferred = $q.defer();
// Create our data
var data = clone.innerHTML,
svg = new Blob([data], { type: 'image/svg+xml;charset=utf-8' });
// Create our image
var DOMURL = window.URL || window.webkitURL || window,
url = DOMURL.createObjectURL(svg),
img = new Image();
// When the image has loaded
img.onload = function () {
// Get our location
getNextLocation(canvas.width, canvas.height, img);
// Draw our image using the context (Only draws half the image because I don't want to show the back)
ctx.drawImage(img, 0, 0, img.width / 2, img.height, location.x, location.y, location.width, location.height);
DOMURL.revokeObjectURL(url);
// Resolve our promise
deferred.resolve();
}
// Set the URL of the image
img.src = url;
// Return our promise
return deferred.promise;
};
// Public function to generate the image
self.generateImage = function (element, team, onSuccess) {
// Get our SVG
var target = document.getElementById('totals'),
containers = angular.element(target.getElementsByClassName('svg-document'));
// Get our context
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
// Set our canvas height and width
canvas.width = 2000;
canvas.height = calculateCanvasHeight(containers.length);
// Create our array of promises
var promises = [];
// For each container
for (var i = 0; i < containers.length; i++) {
// Get our container
var container = containers[i],
clone = container.cloneNode(true);
// Remove hidden layers
removeHidden(angular.element(clone));
// Add our promise to the array
promises.push(drawImage(canvas, ctx, clone));
}
// When all promises have resolve
$q.all(promises).then(function () {
// Get our URL as a base64 string
var dataURL = canvas.toDataURL("image/png");
// Create our model
var model = {
teamName: team.name,
sport: team.sport,
data: dataURL
};
// Create our preview
self.create(model).then(function (response) {
// Invoke our success callback
onSuccess(response);
});
})
};
Obviously there is missing code here, but this code answers my issue, the rest just makes my service work :)
Related
I'm writing a small mobile application using Javascript.
I'm making calls to a remote API that responds with some JSON formatted data. Within that data are URLs for images of products i need to display in my app. I've parsed the JSON into a JS array and now have an array of all the URLs for the images. When i first load my app i have a canvas which is used to display the first image from the array. I have two buttons, a previous and a next. I'd like to be able to cycle through the images using these buttons by using the URLs in the array to draw onto the canvas.
Any ideas?
Is something like the following what you're looking for?
// Image URLs
var imageUrls = ['http://emojipedia-us.s3.amazonaws.com/cache/8b/bd/8bbd3405b0a197214e229428c23dbe60.png', 'http://emojipedia-us.s3.amazonaws.com/cache/05/d1/05d1ba284ee1a3bfe4e0f68988baafb9.png', 'http://emojipedia-us.s3.amazonaws.com/cache/99/4c/994c5997c7a509703cc53ec2000bb258.png', 'http://emojipedia-us.s3.amazonaws.com/cache/59/0c/590c832e73a472c416bf9d8bfdd02a4a.png'];
// Keep track of the index of the image URL in the array above
var imageShownIndex = 0;
// Create a canvas
var canvas = document.createElement('canvas');
var canvasContext = canvas.getContext('2d');
canvas.height = 150;
canvas.width = 150;
// Create a button that will load the previous image on the canvas when clicked
var previousButton = document.createElement('button');
previousButton.innerHTML = 'Previous Image';
previousButton.onclick = function () {
// Show images in a cycle, so when you get to the beginning of the array, you loop back to the end
imageShownIndex = (imageShownIndex==0) ? imageUrls.length-1 : imageShownIndex-1;
updateImage();
};
// Do same for the next button
var nextButton = document.createElement('button');
nextButton.innerHTML = 'Next Image';
nextButton.onclick = function () {
imageShownIndex = (imageShownIndex == imageUrls.length-1) ? 0 : imageShownIndex+1;
updateImage();
};
document.body.appendChild(previousButton);
document.body.appendChild(canvas);
document.body.appendChild(nextButton);
// Show the first image without requiring a click
updateImage();
function updateImage() {
// Create the Image object, using the URL from the array as the source
// You could pre-load all the images and store them in the array, rather than loading each image de novo on a click
var img = new Image();
img.src = imageUrls[imageShownIndex];
// Clear the canvas
canvasContext.clearRect(0, 0, 150, 150);
// After the image has loaded, draw the image on the canvas
img.onload = function() {
canvasContext.drawImage(img, 0, 0);
}
}
EDIT: Or if you have HTML elements already made, you can just use JavaScript to control the canvas. E.g.:
<html>
<body>
<button id="previous_button" onclick="goToPreviousImage()">Previous Image</button>
<canvas id="image_canvas" height=150, width=150></canvas>
<button id="next_button" onclick="goToNextImage()">Next Image</button>
</body>
<script>
var imageUrls = ['http://emojipedia-us.s3.amazonaws.com/cache/8b/bd/8bbd3405b0a197214e229428c23dbe60.png', 'http://emojipedia-us.s3.amazonaws.com/cache/05/d1/05d1ba284ee1a3bfe4e0f68988baafb9.png', 'http://emojipedia-us.s3.amazonaws.com/cache/99/4c/994c5997c7a509703cc53ec2000bb258.png', 'http://emojipedia-us.s3.amazonaws.com/cache/59/0c/590c832e73a472c416bf9d8bfdd02a4a.png'];
var imageShownIndex = 0;
var canvas = document.getElementById('image_canvas');
var canvasContext = canvas.getContext('2d');
function goToPreviousImage() {
imageShownIndex = (imageShownIndex==0) ? imageUrls.length-1 : imageShownIndex-1;
updateImage();
};
function goToNextImage() {
imageShownIndex = (imageShownIndex == imageUrls.length-1) ? 0 : imageShownIndex+1;
updateImage();
};
updateImage();
function updateImage() {
var img = new Image();
img.src = imageUrls[imageShownIndex];
canvasContext.clearRect(0, 0, 150, 150);
img.onload = function() {
canvasContext.drawImage(img, 0, 0);
}
}
</script>
</html>
I am trying to generate a pdf report including current map screenshot of ESRI map using JavaScript. Here is my code.
var getCurrentMapScreenShot = function (success, error) {
esriLoader.Config.defaults.io.proxyUrl = myAppSettingsModel.SettingsModel.MapSettings.AGSProxyURL;
esriLoader.Config.defaults.io.alwaysUseProxy = true;
var printTask = new esriLoader.PrintTask("myexportUrl");
var template = new esriLoader.PrintTemplate();
template.exportOptions = {
width: 600,
height: 600,
dpi: 96
};
template.format = "image/png";
template.layout = "MAP_ONLY",
template.preserveScale = true;
template.layoutOptions = {
legendLayers: [], // empty array means no legend
scalebarUnit: "Miles"
};
var params = new esriLoader.PrintParameters();
params.map = map;
params.template = template;
printTask.execute(params, success, error);
}
This function will give you an event that contains a url , Then I am passing this url to get map base64 data.
Here is the function calling.
map.GetCurrentMapScreenShot(function (event) {
var mapScreenShotURL = event.url;
Factory.GetBase64ForImgUrl(mapScreenShotURL,
function (mapImageBase64Encoded) {});
and here is the function that converts url to base64image
function getBase64ForImgUrl(url, callback, outputFormat) {
console.log("#################### Summary Report Image " + url);
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext('2d');
var img = new Image;
img.crossOrigin = 'Anonymous';
img.onload = function () {
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img, 0, 0);
var dataURL = canvas.toDataURL(outputFormat || 'image/png');
callback.call(this, dataURL);
// Clean up
canvas = null;
};
img.src = url;
}
I am getting base64 image data of the map , But the problem is that, I am getting blurred image of the map with no feature layers and no icons..And also map contains some junk texts.
Thanks
I resolved the issue almost, Now I am getting the map image that contains map Icons and layer graphics, But unfortunately still getting some junk texts.
I have changed the code as follows.
template.format = "JPG";
template.layout = "A4 Landscape",
template.preserveScale = false;
template.layoutOptions = {
"legendLayers": [], // empty array means no legend
"scalebarUnit": "Miles"
}
The below is supposed:
loop through a group of img tags
grab each tag's src url
convert it to a base64 encoded string using a HTML 5 canvas
once all images have been converted, the promise should be resolved and the callback called
My problem:
I need to wait for each image to load in the canvas before I can convert it to base64
to solve this I used `img.onload = function(){ ..... return base64Img; };
Unfortunately, calling img.src = element.src; each time through resolves the promise, it doesn't wait for the images to load and call return base64Img; so I end up with an empty array when $.when.apply($, promises).then(function(){}); is called.
How can I change this promise so that it does not resolve until all the images have been loaded and converted?
function accountCreation(){
$('#container').hide(); // hide the display while we save the info as it will take a few seconds, you should do a loading bar or something here instead
var user = Parse.User.current(); // get the currently logged in user object
// loop through each image element
var promises = $('.images').map(function(index, element) {
var src = $(element).attr('src');
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext('2d');
var img = new Image;
img.crossOrigin = 'Anonymous';
img.onload = function(){
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img,0,0);
var base64Img = canvas.toDataURL('image/png');
// Clean up
canvas = null;
return base64Img;
};
img.src = element.src;
});
$.when.apply($, promises).then(function() {
// arguments[0][0] is first result
// arguments[1][0] is second result and so on
for (var i = 0; i < arguments.length; i++) {
var file = new Parse.File("photo.jpg", { base64: arguments[i]}); // this part actually saves the image file to parse
user.set("image" + i, file); // set this image to the corosponding column on our object
}
// save the user object
user.save(null,{
success: function(user) {
$('#message').html('Profile Updated!');
$('#container').show(); // show the screen again, you should just remove your loading bar now if you used one
},
error: function(user, error) {
console.log('Failed to create new object, with error code: ' + error.message);
}
});
});
}
Your array of promises just contains undefined values, as the callback function doesn't return anything. As there are no Deferred objects in the array, the when method doesn't have anything to wait for, so it runs the then callback right away.
Create a Deferred object to return, and resolve it when the image is loaded:
var promises = $('.images').map(function(index, element) {
// created a Deferred object to return
var deferred = $.Deferred();
var src = $(element).attr('src');
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext('2d');
var img = new Image;
img.crossOrigin = 'Anonymous';
img.onload = function(){
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img,0,0);
var base64Img = canvas.toDataURL('image/png');
// Clean up
canvas = null;
// Resolve the Deferred object when image is ready
deferred.resolve(base64Img);
};
img.src = element.src;
// return the Deferred object so that it ends up in the array
return deferred;
});
I tried to create an array of Image to be displayed on a canvas, after each image is loaded. No errors, no draw...
var x=...
var y=...
var canvas = document.getElementById(sCanvasName);
var context = canvas.getContext('2d');
var imageCardObj = [];
//vCards contains the images file names
for (var k=0;k<vCards.length;k++){
imageCardObj[k] = new Image();
var func = function(){
var c = arguments[3];
try{
c.drawImage(arguments[0], arguments[1], arguments[2]);
}catch(e){
alert(e.message)
}
}
imageCardObj[k].onload = func(imageCardObj[k], x, y, context);
imageCardObj[k].src = "res/img/"+vCards[k].trim()+".png";
x+=40;
}
You are calling the func() handler and gives the result to it to the image's onload handler. That won't work so well.. and you cannot pass arguments that way to a handler function.
Try this:
var func = function(){
// "this" will be the current image in here
var c = arguments[3];
try{
c.drawImage(this, x, y); // you need to reference x and y
}catch(e){
alert(e.message)
}
}
imageCardObj[k].onload = func; // only a reference here
If you need different x and y's then you need to maintain those on the side, either in an additional array or use objects to embed the image, its intended x and y and use the url to identify the image in question inside the func() callback.
Also note that load order may vary as the last image loaded could finish before the first one so when you draw the image they may not appear in the same order.
You may want to do this instead:
var files = [url1, url2, url, ...],
images = [],
numOfFiles = files.length,
count = numOfFiles;
// function to load all images in one go
function loadImages() {
// go through array of file names
for(var i = 0; i < numOfFiles; i++) {
// create an image element
var img = document.createElement('img');
// use common loader as we need to count files
img.onload = imageLoaded;
//img.onerror = ... handle errors too ...
//img.onabort = ... handle errors too ...
img.src = files[i];
// push image onto array in the same order as file names
images.push(img);
}
}
function imageLoaded(e) {
// for each successful load we count down
count--;
if (count === 0) draw(); //start when all images are loaded
}
Then you can start the drawing after the images has loaded - the images are now in the same order as the original array:
function draw() {
for(var i = 0, img; img = images[i++];)
ctx.drawImage(img, x, y); // or get x and y from an array
}
Hope this helps!
This is the final (working) version
var x=...
var y=...
var canvas = document.getElementById(sCanvasName);
var context = canvas.getContext('2d');
var imageCardObj = [];
for (var k=0;k<vCards.length;k++){
imageCardObj[k] = new Image();
imageCardObj[k].xxx=x;
var func = function(){
try{
context.drawImage(this, this.xxx, yStreet);
}catch(e){
alert(e.message)
}
}
imageCardObj[k].onload = func;
imageCardObj[k].src = 'res/img/'+vCards[k].trim()+".png";
x +=40;
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!