Extract exif data/rotate image added to fabric.js canvas - javascript

In my web app users can take photos on their phones from input type file.
Before I used js load image library to automatically detect EXIF data and rotate it if neccessary.
Now I've switched to fabric.js library because I want to allow users to draw on the canvas image before uploading it.
The problem is that I don't know how to get image exif data, or rotate the image when using fabric.js.
My code:
Getting the image from indexed db
var image;
localforage.getItem('photo').then(function(value) {
image = value;
// THIS IS OLD CODE, BEFORE I USER js-load-image to automatically detect exif data
// and rotate if neccessary, then I just appended the result to wrapper
// $("#preview-wrapper").empty();
// if (image) {
// loadImage(
// image,
// function(img) {
// $("#preview-wrapper").append(img);
// },
// {orientation: true} // Options
// );
// }
})
loadFabricOnImageLoad(); // append image to fabric canvas
Fabric.js function
function loadFabricOnImageLoad(){
if(typeof image !== "undefined" && image){
let loaded = false;
if (loaded) {return;}
var c = document.getElementById('myCanvas');
canvas = new fabric.Canvas(c);
canvas.setWidth(width);
canvas.setHeight(500);
canvas.selection = false;
var url = URL.createObjectURL(image);
fabric.Image.fromURL(url, function(img) {
img.set({
left: 0,
right: 0,
top: 0,
});
img.scaleToWidth(width);
canvas.add(img);
canvas.setHeight(img.getScaledHeight());
$("#canvas-loader").hide();
});
fabric.Object.prototype.set({
evented: false
});
canvas.freeDrawingBrush = new fabric['PencilBrush'](canvas);
canvas.freeDrawingBrush.color = 'Red';
canvas.freeDrawingBrush.width = 6;
loaded = true;
} else {
setTimeout(loadFabricOnImageLoad, 250);
}
}

I've managed to do it myself after all by getting the exif data from image file:
localforage.getItem('photo').then(function(value) {
image = value;
// extract the exif data from image file using exif.js
EXIF.getData(image, function() {
let obj = EXIF.getAllTags(this);
imageOrientation = obj.Orientation;
console.log( imageOrientation );
});
})
I had to do a lot of scaling and resizing to get the size of rotated image and canvas correctly:
function loadFabricOnImageLoad(){
if(typeof image !== "undefined" && image){
let loaded = false;
if (loaded) {return;}
var c = document.getElementById('myCanvas');
canvas = new fabric.Canvas(c);
canvas.setWidth(width);
canvas.setHeight(500);
canvas.selection = false;
var url = URL.createObjectURL(image);
fabric.Image.fromURL(url, function(img) {
img.scaleToWidth(canvas.width);
if(imageOrientation == 3 || imageOrientation == 6 || imageOrientation == 8) {
img.scaleToHeight(canvas.height);
canvas.setHeight(img.getScaledHeight());
canvas.add(img);
img.scaleToWidth(canvas.height);
img.scaleToHeight(canvas.width);
img.rotate(getDegreesForOrientation(imageOrientation));
img.center();
} else {
canvas.setHeight(img.getScaledHeight());
canvas.add(img);
}
$("#canvas-loader").hide();
});
// console.log('slika '+JSON.stringify(fabric.Image));
fabric.Object.prototype.set({
evented: false
});
canvas.freeDrawingBrush = new fabric['PencilBrush'](canvas);
canvas.freeDrawingBrush.color = 'Red';
canvas.freeDrawingBrush.width = 6;
loaded = true;
} else {
setTimeout(loadFabricOnImageLoad, 250);
}
}

I know this is already answered but I'd like to suggest different library. exif-js is dead - buggy and no longer maintained, with hundred of unsolved issues.
I would like you to try exifr which i started developing out of frustrations with exif-js.

Related

Passing Video File Input to p5js Canvas

I am trying to use Bootstrap to create a prettier interface to upload a video file rather than the p5js "createFileInput()" function. I want the video in p5js so I can overlay animations onto it. For some reason my code is displaying the video twice as soon as I use the function "createVideo" and if I try and hide the DOM that is the second video outside of the canvas it disappears from the p5js canvas. Here is my code:
var view = {
vid: "",
cHeight: 500,
cWidth: 700,
xVid: 0,
yVid: 0,
};
function setup() {
let cnv = createCanvas(view.cWidth, view.cHeight);
cnv.id("p5jsCanvas");
}
function draw() {
if (view.vid) {
// We have an uploaded video so draw it
//background(0);
console.log(view.vid.id());
image(view.vid, view.xVid, view.yVid);
} else {
background(0);
}
}
$("#videoFileUpload").on("change", function() {
var fileName = $(this).val().split("\\").pop();
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
if (this.files && this.files[0]) {
console.log(this.files[0]);
var fileLocation = URL.createObjectURL(this.files[0]);
console.log(fileLocation);
view.vid = createVideo([fileLocation], resizeVid);
view.vid.id("poseVid");
}
});
function resizeVid(){
// If the height is greater than the width use the height to scale and AUTO for the width
if (view.vid.height > view.vid.width){
console.log("height > width");
view.vid.size(AUTO, cHeight);
} else { // Else if the width is greater than or equal to the height use the width to scale and AUTO for the height
view.vid.size(view.cWidth, AUTO);
}
// Reset location of the video
let currW = $("#poseVid").attr('width');
let currH = $("#poseVid").attr('height');
view.xVid = (view.cWidth - currW)/2;
view.yVid = (view.cHeight - currH)/2;
}
I realized that I needed to use hide() on the video element and then provide a way to "play" the element in p5js and then it worked. I also added removing the id if it existed already so that I could upload a different video after one was already uploaded.
var view = {
vid: "",
cHeight: 500,
cWidth: 700,
xVid: 0,
yVid: 0,
};
function setup() {
let cnv = createCanvas(view.cWidth, view.cHeight);
cnv.id("p5jsCanvas");
}
function draw() {
if (view.vid) {
// We have an uploaded video so draw it
//background(0);
console.log(view.vid.id());
image(view.vid, view.xVid, view.yVid);
} else {
background(0);
}
}
$("#videoFileUpload").on("change", function() {
var fileName = $(this).val().split("\\").pop();
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
if (this.files && this.files[0]) {
console.log(this.files[0]);
var fileLocation = URL.createObjectURL(this.files[0]);
console.log(fileLocation);
$("#poseVid").remove(); // ADDED
view.vid = createVideo([fileLocation], resizeVid);
// view.vid.id("poseVid"); // MOVED TO resizeVid function
}
});
// ADDED FUNCTION JUST TO TEST THE PLAYBACK
function mousePressed() {
view.vid.loop(); // set the video to loop and start playing
}
function resizeVid(){
// If the height is greater than the width use the height to scale and AUTO for the width
view.vid.id("poseVid"); //MOVED INTO THIS FUNCTION
$("#poseVid").hide(); //ADDED
if (view.vid.height > view.vid.width){
console.log("height > width");
view.vid.size(AUTO, cHeight);
} else { // Else if the width is greater than or equal to the height use the width to scale and AUTO for the height
view.vid.size(view.cWidth, AUTO);
}
// Reset location of the video
let currW = $("#poseVid").attr('width');
let currH = $("#poseVid").attr('height');
view.xVid = (view.cWidth - currW)/2;
view.yVid = (view.cHeight - currH)/2;
}

How can I check image orientation in Angular before uploading image

I just want to image being upload on my website must portrait or square and I don't want to crop image, image to be uploaded must be portrait or square by default I have searched number of website \ number method use but none worked
know I am trying this method but it also not working because it is running async
if give landscape image in given code it runs if which should not run as
hasError must be true but doesn't because if condition run before the above code could complete so what can I do
let hasError = false;
image = event.target.files[0];
if (image) {
var img = new Image();
img.src = window.URL.createObjectURL(image);
img.onload = function () {
height = img.naturalHeight;
width = img.naturalWidth;
console.log(hasError); // prints false
if (height < width) {
hasError = true; // prints true
console.log(hasError);
}
window.URL.revokeObjectURL(img.src);
}
console.log(hasError); //prints flase
}
if(!hasError){
....
}
I think you should create a function and move your image validation into your function and use a callback to find out the image has error or not.
var fileInput = document.querySelector('input[type="file"]');
var preview = document.getElementById('preview'); //img tag
previewImage(function(hasError){
if (!hasError){
//do something
}
});
function previewImage(callback){
fileInput.addEventListener('change', function(e) {
preview.onload = function() {
height = this.naturalHeight;
width = this.naturalWidth;
if (height < width) {
callback(true);
}
window.URL.revokeObjectURL(this.src);
callback(false);
};
var url = URL.createObjectURL(e.target.files[0]);
preview.setAttribute('src', url);
}, false);
}

Esri Take Map Scrrenshots using Javascript

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"
}

Saving multiple SVGs to canvas with text then getting dataURL

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 :)

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!

Categories

Resources