I have a page which allows you to browse in an image, then draw on it and save both the original and the annotated version. I am leveraging megapix-image.js and exif.js to help in rendering images from multiple mobile devices properly. It works great, except in certain orientations. For example, a vertical photo taken on an iPhone4s is considered orientation 6 by exif and gets flipped accordingly by megapix-image so it's rendered nicely on the canvas. For some reason, when I draw on it afterward, it seems like the drawing is reversed. Mouse and touch both behave the same way. The coordinates look right to me (meaning they match a working horizontal pic and a non-working vertical pic), as does the canvas height and width when megapix-image.js flips it. This leads me to believe it has something to do with the context, but honestly, I am not really sure. I have a JS fiddle of the part of my work that shows the behavior. Just browse in a vertically taken pic from a mobile device or take a pic in vertical format on a mobile device and use it. I think all will show this same behavior.
The final rendering is done like this:
function RenderImage(file2) {
if (typeof file2[0].files[0] != 'undefined') {
EXIF.getData(file2[0].files[0], function () {
orientation = EXIF.getTag(this, "Orientation");
var file = file2[0].files[0];
var mpImg = new MegaPixImage(file);
var resCanvas1 = document.getElementById('annoCanvas');
mpImg.render(resCanvas1, {
maxWidth: 700,
maxHeight: 700,
orientation: orientation
});
});
}
}
But the full jsfiddle is here:
http://jsfiddle.net/awebster28/Tq3qU/6/
Does anyone have any clues for me?
If you look at the lib you are using there is a transformCoordinate function that is used to set the right transform before drawing.
And they don't save/restore the canvas (boooo!!!) so it remains with this transform after-wise.
Solution for you is to do what the lib should do : save the context before the render and restore it after :
function RenderImage(file2) {
// ... same code ...
var mpImg = new MegaPixImage(file);
var eData = EXIF.pretty(this);
// Render resized image into canvas element.
var resCanvas1 = document.getElementById('annoCanvas');
var ctx = resCanvas1.getContext('2d');
ctx.save();
//setting the orientation flips it
mpImg.render(resCanvas1, {
maxWidth: 700,
maxHeight: 700,
orientation: orientation
});
ctx.restore();
//...
}
I ended up fixing this by adding another canvas to my html (named "annoCanvas2"). Then, I updated megapix-image.js to include this function, which draws the contents of the new canvas to a fresh one:
function drawTwin(sourceCanvas)
{
var id = sourceCanvas.id + "2";
var destCanvas = document.getElementById(id);
if (destCanvas !== null) {
var twinCtx = destCanvas.getContext("2d");
destCanvas.width = sourceCanvas.width;
destCanvas.height = sourceCanvas.height;
twinCtx.drawImage(sourceCanvas, 0, 0, sourceCanvas.width, sourceCanvas.height);
}
}
Then, just after the first is rotated and flipped and rendered, I rendered the resulting canvas to my "twin". Then I had a nice canvas, with my updated image that I could then draw on and also save!
var tagName = target.tagName.toLowerCase();
if (tagName === 'img') {
target.src = renderImageToDataURL(this.srcImage, opt, doSquash);
} else if (tagName === 'canvas') {
renderImageToCanvas(this.srcImage, target, opt, doSquash);
//------I added this-----------
drawTwin(target);
}
I was glad to have it fixed so I met my deadline, but I am still not sure why I had to do this. If anyone out there can explain it, I'd love to know why.
Related
I'm trying to pan-zoom with the mouse a <div> that contains a PDF document.
I'm using PDF.js and panzoom libraries (but other working alternatives are welcome)
The snippet below basically does this task, but unfortunately, after the <div> is panzoomed, the PDF is not re-rendered, then its image gets blurry (pan the PDF with the mouse and zoom it with the mouse-wheel):
HTML
<script src="https://mozilla.github.io/pdf.js/build/pdf.js"></script>
<script src="https://cdn.jsdelivr.net/npm/#panzoom/panzoom#4.3.2/dist/panzoom.min.js"></script>
<div id="panzoom-container">
<canvas id="the-canvas"></canvas>
</div>
<style>
#the-canvas {
border: 1px solid black;
direction: ltr;
}
</style>
JAVASCRIPT
// Loaded via <script> tag, create shortcut to access PDF.js exports.
var pdfjsLib = window['pdfjs-dist/build/pdf'];
// The workerSrc property shall be specified.
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://mozilla.github.io/pdf.js/build/pdf.worker.js';
var pdfDoc = null,
canvas = document.getElementById('the-canvas'),
ctx = canvas.getContext('2d');
var container = document.getElementById("panzoom-container")
function renderPage(desiredHeight) {
pdfDoc.getPage(1).then(function(page) {
var viewport = page.getViewport({ scale: 1, });
var scale = desiredHeight / viewport.height;
var scaledViewport = page.getViewport({ scale: scale, });
canvas.height = scaledViewport.height;
canvas.width = scaledViewport.width;
// Render PDF page into canvas context
var renderContext = {
canvasContext: ctx,
viewport: scaledViewport
};
page.render(renderContext);
});
}
// Get the PDF
var url = 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf';
pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
pdfDoc = pdfDoc_;
// Initial/first page rendering
renderPage(250);
});
var panzoom = Panzoom(container, {
setTransform: (container, { scale, x, y }) => {
// here I can re-render the page, but I don't know how
// renderPage(container.getBoundingClientRect().height);
panzoom.setStyle('transform', `scale(${scale}) translate(${x}px, ${y}px)`)
}
})
container.addEventListener('wheel', zoomWithWheel)
function zoomWithWheel(event) {
panzoom.zoomWithWheel(event)
}
https://jsfiddle.net/9rkh7o0e/5/
I think that the procedure is correct, but unfortunately I'm stuck into two issues that must be fixed (then I ask for your help):
I don't know how to re-render correctly the PDF after the panzoom happened.
consecutive renderings have to be queued in order to make this work properly (so that the PDF doesn't blink when panzooming)
How could I fix 1 and 2 ? I could not find any tool for doing that on a PDF, apart the panzoom lib.
Thanks
After some days of attempts, I solved the problem.
The tricky part was to scale down the contained canvas after the container has been scaled, with a transform-origin CSS attribute set:
canvas.style.transform = "scale("+1/panzoom.getScale()+")"
Here is a fiddle of a PDF that can pan-zoomed. Each new render is done after a small delay (here it is set to 250ms) which corresponds to a sort of "wheel stop event".
function zoomWithWheel(event) {
panzoom.zoomWithWheel(event)
clearTimeout(wheelTimeoutHandler);
wheelTimeoutHandler = setTimeout(function() {
canvas.style.transform = "scale("+1/panzoom.getScale()+")"
if (pdfDoc)
renderPage(panzoom.getScale());
}, wheelTimeout)
}
https://jsfiddle.net/7tmk0z42/
I am trying to work on image processing with fabric js.I am dealing with very huge images hence I have to save copy of canvases after image processing so that next time it can be shown faster with jquery's id.show. But I want to render the images on the exact location. I am using canvas.zoomToPoint and canvas.relativePan to zoom and pan the image but after I do zoom + pan and then apply image processing to show hidden canvas and apply hiddencanvas.zoomToPoint and hiddencanvas.relativePan on hidden canvas, it doesn't render the image on the exact location where I left the older canvas. Am I doing any mistake. Here's a supporting Fiddle .However, the fiddle renders a image by uploading and if you zoom and pan and click on invert, the inverted image doesn't move there Panning code : ` ``var panning = false;
canvas.on('mouse:up', function (e) {
panning = false;
});
canvas.on('mouse:down', function (e) {
panning = true;
});
canvas.on('mouse:move', function(e) {
if (panning && e && e.e) {
var x = e.offsetX, y = e.offsetY;
var delta = new fabric.Point(e.e.movementX, e.e.movementY);
canvas.relativePan(delta);
//Above statement pan's the image but what to save to server in order to render the image on the exact panned location ?
}
});
`` whereas this is zoom code : canvas.zoomToPoint({ x: x, y: y }, newZoom);
I found the answer , it was a very silly mistake .
Every canvas has a Viewport transform .
So, we just need to get canvas.viewportTransform and then we can get the ScaleX, scaleY, left , top as [scaleX, 0,0, scaleY, left,top] .
Hope , it will help someone .
I'm trying to write some JS which will convert an image to grayscale, and convert the blacks to a colour (pink in this case). The effect I want to achieve is illustrated below (left is the original, right is the desired result):
I've tried using CamanJS and I've got something close-ish using a greyscale filter and a solid pink overlay with a blending mode of mulitply. The JS for this looks like:
$(function () {
var $images = $('.make-pink');
$images.each(function () {
createPinkImage( $(this) );
});
});
function createPinkImage ( $image ) {
var $canvas = $('<canvas />');
var canvas = $canvas[0];
var imgSrc = $image.attr('src');
var canvasWidth = $image.parent().width();
var canvasHeight = $image.parent().height();
var pink = '#d65fb3';
$canvas
.attr('width',canvasWidth)
.attr('height',canvasHeight)
.appendTo($('#canvas-container'));
var image = new Image();
image.src = imgSrc;
image.onload = function() {
Caman( canvas, imgSrc, function () {
this.greyscale();
this.newLayer(function () {
this.fillColor(pink);
this.setBlendingMode('multiply');
this.opacity(70);
});
this.exposure(40)
this.render();
});
}
}
I've tried to implement this in a jsfiddle but it doesn't produce a result. Here's the link anyway: http://jsfiddle.net/303L2xL4/1/
And here's a screenshot of the result:
As you can see, it's not quite there!
Any help would be really great. I'm not committed to using CarmanJS is there's a better solution with another library or even just vanilla JS
This is because black colors will produce black result as black is 0 and as you know anything multiplied with 0 will remain 0 (dark greys will produce close to black values and so forth).
To solve this you will have to add a brightness effect to the gray-scale image before setting its color (and potentially a slight contrast adjustment as well) to force the values up from 0.
You could also push the pixels through a RGB-HSL/HSV conversion, adjust brightness (L/V) and then convert back to RGB. This is in principle the same as a linear brightness but with typically better result/quality in the end.
I'm trying to build a transform manager for KineticJS that would build a bounding box and allow users to scale, move, and rotate an image on their canvas. I'm getting tripped up with the logic for the anchor points.
http://jsfiddle.net/mharrisn/whK2M/
I just want to allow a user to scale their image proportionally from any corner, and also rotate as the hold-drag an anchor point.
Can anyone help point me in the right direction?
Thank you!
Here is a proof of concept of a rotational control I've made:
http://codepen.io/ArtemGr/pen/ociAD
While the control is dragged around, the dragBoundFunc is used to rotate the content alongside it:
controlGroup.setDragBoundFunc (function (pos) {
var groupPos = group.getPosition()
var rotation = degrees (angle (groupPos.x, groupPos.y, pos.x, pos.y))
status.setText ('x: ' + pos.x + '; y: ' + pos.y + '; rotation: ' + rotation); layer.draw()
group.setRotationDeg (rotation); layer.draw()
return pos
})
I am doing the same thing, and I've posted a question which is allmoast the same, but I found a link where you have the resize and move tool ready developed. So I have used the same. It does not contain the rotate tool however, but this can be a good start for you too, it is very simple and logical. Here is the link: http://www.html5canvastutorials.com/labs/html5-canvas-drag-and-drop-resize-and-invert-images/
I will come back with the rotation tool as well if I manage to get it working perfectly.
I hope I am not late yet for posting this code snippet that I made. I had the same problem with you guys dealing with this kind of task. Its been 3 days since I tried so many workarounds to mimic the fabricjs framework capability when dealing with images and objects. I could use Fabricjs though but it seems that Kineticjs is more faster/consistent to deal with html5.
Luckily, we already have existing plugin/tool that we could easily implement together with kineticjs and this is jQuery Transform tool. SUPER THANKS TO THE AUTHOR OF THIS! Just search this on google and download it.
I hope the code below that I created would help lots of developers out there who is pulling their hair off to solve this kind of assignment.
$(function() {
//Declare components STAGE, LAYER and TEXT
var _stage = null;
var _layer = null;
var simpleText = null;
_stage = new Kinetic.Stage({
container: 'canvas',
width: 640,
height: 480
});
_layer = new Kinetic.Layer();
simpleText = new Kinetic.Text({
x: 60,
y: 55,
text: 'Simple Text',
fontSize: 30,
fontFamily: 'Calbiri',
draggable: false,
name:'objectInCanvas',
id:'objectCanvas',
fill: 'green'
});
//ADD LAYER AND TEXT ON STAGE
_layer.add(simpleText);
_stage.add(_layer);
_stage.draw();
//Add onclick event listener to the Stage to remove and add transform tool to the object
_stage.on('click', function(evt) {
//Remove all objects' transform tool inside the stage
removeTransformToolSelection();
// get the shape that was clicked on
ishape = evt.targetNode;
//Add and show again the transform tool to the selected object and update the stage layer
$(ishape).transformTool('show');
ishape.getParent().moveToTop();
_layer.draw();
});
function removeTransformToolSelection(){
//Search all objects inside the stage or layer who has the name of "objectInCanvas" using jQuery iterator and hide the transform tool.
$.each(_stage.find('.objectInCanvas'), function( i, child ) {
$(child).transformTool('hide');
});
}
//Event listener/Callback when selecting image using file upload element
function handleFileSelect(evt) {
//Remove all objects' transform tool inside the stage
removeTransformToolSelection();
//Create image object for selected file
var imageObj = new Image();
imageObj.onload = function() {
var myImage = new Kinetic.Image({
x: 0,
y: 0,
image: imageObj,
name:'objectInCanvas',
draggable:false,
id:'id_'
});
//Add to layer and add transform tool
_layer.add(myImage);
$(myImage).transformTool();
_layer.draw();
}
//Adding source to Image object.
var f = document.getElementById('files').files[0];
var name = f.name;
var url = window.URL;
var src = url.createObjectURL(f);
imageObj.src = src;
}
//Attach event listener to FILE element
document.getElementById('files').addEventListener('change', handleFileSelect, false);
});
I've been working on adapting arbor.js to use images.
However, being a relative JS noob what I have is totally un-optimised.
As far as I can tell, the way I've set it up is recreating the image object for every image and every frame, resulting in tons of flicker.
Can anyone suggest a way to move the new Image() stuff out of the redraw function into the initiation? As far as I know this is a basic OOP issue, but totally stuck.
Thanks!
Pastebin of where I'm up to on the output script
Current status.
Apologies all! There's a few steps. I'll highlight the key stages, the rest is from the tutorial.
First, add the relevant information to your JSON, for example:
nodes:{
innovation:{ 'color':colour.darkblue,
'shape':'dot',
'radius':30,
'image': 'innovation.png',
'image_w':130,
'image_h':24,
'alpha':1 },
participation:{ 'color':colour.purple,
'shape':'dot',
'radius':40,
'image':'participation.png',
'image_w':130,
'image_h':24,
'alpha':1 },
...
Cache all your images when the thing loads.
init:function(system){
// Normal initialisation
particleSystem = system
particleSystem.screenSize(canvas.width, canvas.height)
particleSystem.screenPadding(25, 50)
that.initMouseHandling()
// Preload all images into the node object
particleSystem.eachNode(function(node, pt) {
if(node.data.image) {
node.data.imageob = new Image()
node.data.imageob.src = imagepath + node.data.image
}
})
...
Then, for moving the images themselves...
particleSystem.eachNode(function(node, pt){
...
// Image info from JSON
var imageob = node.data.imageob
var imageH = node.data.image_h
var imageW = node.data.image_w
...
// Draw the object
if (node.data.shape=='dot'){
// Check if it's a dot
gfx.oval(pt.x-w/2, pt.y-w/2, w,w, {fill:ctx.fillStyle, alpha:node.data.alpha})
nodeBoxes[node.name] = [pt.x-w/2, pt.y-w/2, w,w]
// Does it have an image?
if (imageob){
// Images are drawn from cache
ctx.drawImage(imageob, pt.x-(imageW/2), pt.y+radius/2, imageW, imageH)
}
}else {
// If none of the above, draw a rectangle
gfx.rect(pt.x-w/2, pt.y-10, w,20, 4, {fill:ctx.fillStyle, alpha:node.data.alpha})
nodeBoxes[node.name] = [pt.x-w/2, pt.y-11, w, 22]
}
...