Html canvas element's getImageData doesn't match content of Fabricjs canvas - javascript

I added a fabric Rect at (80, 80) and then called getImageData at that location. However, the data I get from that location doesn't match the Rect I placed there. Instead, I find the data around (60, 60).
var canvas = new fabric.Canvas('fabric-canvas');
canvas.setHeight(800);
canvas.setWidth(800);
var rect = new fabric.Rect({
left: 80,
top: 80,
fill: 'red',
width: 20,
height: 20
});
canvas.add(rect);
// This doesn't work
let data = canvas.getContext('2d').getImageData(80, 80, 20, 20);
canvas.getContext('2d').putImageData(data, 0, 0);
// This "works"
data = canvas.getContext('2d').getImageData(60, 60, 20, 20);
canvas.getContext('2d').putImageData(data, 100, 0);
Here's a fiddle demo: https://jsfiddle.net/2wxdb8ua/13/
How might this be possible?

The problem is certainly caused by some zooming, either due to your monitor being an high-res monitor (a.k.a retina), or browser/OS zoom being applied on the page.
You can either make the calculation yourself world_coord * window.devicePixelRatio:
var canvas = new fabric.Canvas('fabric-canvas');
canvas.setHeight(800);
canvas.setWidth(800);
var rect = new fabric.Rect({
left: 80,
top: 80,
fill: 'red',
width: 20,
height: 20
});
canvas.add(rect);
let ctx = canvas.getContext('2d').getImageData(...[80, 80, 20, 20].map(fixCoords));
canvas.getContext('2d').putImageData(ctx, ...[0, 0].map(fixCoords));
function fixCoords( coord ) {
return coord * window.devicePixelRatio;
}
canvas#fabric-canvas {
background: white;
border: 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.1/fabric.min.js"></script>
<canvas id='fabric-canvas'></canvas>
Or disable fabric's auto-scaling by using the enableRetinaScaling parameter of the initOptionObject:
var canvas = new fabric.Canvas('fabric-canvas', {
enableRetinaScaling: false
});
canvas.setHeight(800);
canvas.setWidth(800);
var rect = new fabric.Rect({
left: 80,
top: 80,
fill: 'red',
width: 20,
height: 20
});
canvas.add(rect);
let ctx = canvas.getContext('2d').getImageData(80, 80, 20, 20);
canvas.getContext('2d').putImageData(ctx, 0, 0);
canvas#fabric-canvas {
background: white;
border: 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.1/fabric.min.js"></script>
<canvas id='fabric-canvas'></canvas>
But note that with the latter solution, your canvas will certainly become blurry on high-res monitors.

Related

How can I delete a fabric canvas arrow?

I created an arrow using Fabric.js group functionality by grouping Rectangle and Triangle.
Unfortunately I am not able to delete it as a group.
I figured out that getActiveObject() for a single object works fine. Instead, while debugging getActiveGroup() gives error.
var canvas = new fabric.Canvas('canvas');
canvas.setHeight(window.innerHeight * 0.75);
canvas.setWidth(window.innerWidth * 0.75);
//-------------------------Group - Rectangle and Triangle----------------------------------------------
window.addArrow = function() {
var rect = new fabric.Rect({
left: 0,
top: 0,
stroke: 'red',
fill: 'red',
width: 1,
height: 50,
});
rect.hasRotatingPoint = true;
canvas.add(rect);
var triangle = new fabric.Triangle({
width: 10,
height: 10,
fill: 'red',
left: -4,
top: -10
});
var group = new fabric.Group([rect, triangle], {
left: 150,
top: 100,
angle: 90
});
canvas.add(group);
}
//-------------------------Group Delete----------------------------------------------
window.deleteObject = function() {
return canvas.getActiveObject() == null ? canvas.getActiveGroup() : canvas.getActiveObject();
}
function getActiveGroup() {
canvas.getActiveGroup().forEachObject(function(o) {
canvas.remove(o)
});
}
function getActiveObject() {
canvas.remove(canvas.getActiveObject());
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.min.js"></script>
<canvas id="canvas" width="800" height="600" style="border:1px solid red;"></canvas>
<button onClick="addArrow()">Arrow</button>
<button onClick="deleteObject()">Delete</button>
Because you didn't set any active object you cannot use getActiveObject.
In this case you may use getObjects:
canvas.getObjects('group') // in order to get the group
canvas.getObjects('rect') // in order to get the rectangle
var canvas = new fabric.Canvas('canvas');
canvas.setHeight(window.innerHeight * 0.75);
canvas.setWidth(window.innerWidth * 0.75);
//-------------------------Group - Rectangle and Triangle----------------------------------------------
window.addArrow = function() {
var rect = new fabric.Rect({
left: 0,
top: 0,
stroke: 'red',
fill: 'red',
width: 1,
height: 50,
});
rect.hasRotatingPoint = true;
canvas.add(rect);
var triangle = new fabric.Triangle({
width: 10,
height: 10,
fill: 'red',
left: -4,
top: -10
});
var group = new fabric.Group([rect, triangle], {
left: 150,
top: 100,
angle: 90
});
canvas.add(group);
}
//-------------------------Group Delete----------------------------------------------
window.deleteObject = function() {
// remove group....
canvas.getObjects('group').forEach(function(ele,idx) {
canvas.remove(ele);
});
canvas.getObjects('rect').forEach(function(ele,idx) {
canvas.remove(ele);
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.min.js"></script>
<canvas id="canvas" width="800" height="600" style="border:1px solid red;"></canvas>
<button onClick="addArrow()">Arrow</button>
<button onClick="deleteObject()">Delete</button>
use sub classing in fabric.js for creating arrow object so that single object is only needed. So handling will be easier.
[http://fabricjs.com/fabric-intro-part-3#subclassing][1]

How can i able to move image beneath rectangle in canvas?

I could select the background image from inside rectangle, but not able to move.
To move the image, i should go outside of the rectangle.
I know that the rectangle is created over the image, I'm looking for any option to move the image as it is selectable from inside rectangle.
My canvas screenshot
This is how i set the image and rectangle.
changeImage(innercanvasHeight, innercanvasWidth) {
const base_image = new Image();
base_image.crossOrigin = 'Anonymous';
base_image.src = 'assets/images/1wal.jpg';
fabric.Image.fromURL(base_image.src, (myImg) => {
const img1 = myImg.set({left: 160, top: 80, width: 600, height:
400, id: 'wallpaper'});
this.FabriCanvas.add(img1).setActiveObject(img1);
const hiddenImg = document.createElement('img');
hiddenImg.src = this.FabriCanvas.getActiveObject().toDataURL();
hiddenImg.id = 'target';
hiddenImg.style.display = 'none';
document.body.appendChild(hiddenImg);
this.innerCanvas(innercanvasHeight, innercanvasWidth);
});
innerCanvas(height, width) {
this.innercanvas = this.FabriCanvas.add(new fabric.Rect({
left: 160,
top: 80,
id: 'innerCan',
fill: 'transparent',
stroke: '#fff',
strokeWidth: 1,
width: width,
height: height,
selectable: false
}));
this.FabriCanvas.renderAll();
}
Use preserveObjectStacking so it wont come up while dragging, and use perPixelTargetFind to click through the object if it is transparent.
DEMO
var canvas = new fabric.Canvas('canvas',{
preserveObjectStacking: true
});
var image = new fabric.Image('');
var rect = new fabric.Rect({
left: 160,
top: 80,
id: 'innerCan',
fill: 'transparent',
stroke: '#fff',
strokeWidth: 1,
width: 100,
height: 100,
selectable: false,
perPixelTargetFind : true
});
canvas.add(image,rect);
image.setSrc('//fabricjs.com/assets/pug.jpg',function(img){
img.set({ scaleX:canvas.width/img.width,scaleY: canvas.height/img.height});
img.setCoords();
canvas.renderAll();
})
canvas{
border: 2px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id='canvas' width=300 height=300></canvas>

FabricJS FlipX object

I am having trouble flipping or mirroring the object horizontally when the object itself is clicked on the FabricJS canvas.
I came close but it was mirroring the object when it was being resized too, which I didn't want.
I would guess I need to add the 'flipX: true' attribute to object on the first click and on the next click remove that attribute and so on with each click. Or maybe that is over complicating it and it can be done much easier with a flipX function I do not know.
I did find a Fiddle that flipped the object, but it was onclick of a button not the object itself.
I am struggling to solve this :\
My Fiddle
HTML:
<canvas id="canvas" width="400" height="300"></canvas>
JS:
var canvas = this.__canvas = new fabric.Canvas('canvas');
canvas.on('object:selected', function() {
toggle('flipX');
});
// create a rectangle
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 100,
height: 50,
angle: 20,
fill: 'red'
});
canvas.add(rect);
canvas.renderAll();
You could accomplish that in the following way ...
var canvas = this.__canvas = new fabric.Canvas('canvas');
// mouse event
canvas.on('mouse:down', function(e) {
if (e.target) {
if (!e.target.__corner) {
e.target.toggle('flipX');
canvas.renderAll();
}
e.target.__corner = null;
}
});
// create a rectangle
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 100,
height: 50,
angle: 20,
});
// set gradient (for demonstration)
rect.setGradient('fill', {
type: 'linear',
x1: -rect.width / 2,
y1: 0,
x2: rect.width / 2,
y2: 0,
colorStops: {
0: '#ffe47b',
1: 'rgb(111,154,211)'
}
});
canvas.add(rect);
rect.set('flipX', true);
canvas.renderAll();
body{margin:10px 0 0 0;overflow:hidden}canvas{border:1px solid #ccc}
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
<canvas id="canvas" width="208" height="208"></canvas>
Same from above, different case.
Apply flip to background image
fabric.Image.fromURL('../' +ImageUrl, function (img02) {
Backcanvas.setBackgroundImage(img02, Backcanvas.renderAll.bind(Backcanvas), {
backgroundImageStretch: false,
top: 0,
left: 0,
originX: 'left',
originY: 'top',
flipY:'true'
});
Backcanvas.renderAll();
Backcanvas.backgroundImage.setCoords();
canvas.renderAll();
Backcanvas.renderAll();
}, { crossOrigin: 'anonymous' });

How do I select and drag a different object than the one clicked in fabricjs?

I draw multiple objects on a canvas, but the top one has transparency. So you can see images behind it. It is unselectable. I want to be able to click on that image and then programatically select an image behind it and when I drag the mouse, move that one (not the front, trasnparent image).
I tried this code but it doesn't work:
function onSelect(event)
{
var activeObject = canvas.getActiveObject();
var newActive = canvas.getObjects()[ 0 ];
//Do nothing
if ( activeObject === newActive ) return;
//Switch
canvas.setActiveObject( newActive );
}
//Add listener
canvas.on( "object:selected", onSelect );
This appears to select the right object, but it won't drag it.
have you tried just setting the selectable and evented property to false? here is an example with a blue square over two other squares. you can only interact with the 2 objects below the blue square and not the blue square at all.
var canvas = new fabric.Canvas("c", { preserveObjectStacking: true });
canvas
.add(new fabric.Rect({
top: 0,
left: 0,
width: 100,
height: 100,
fill: "green"
}))
.add(new fabric.Rect({
top: 50,
left: 50,
width: 100,
height: 100,
fill: "red"
}))
.add(new fabric.Rect({
top: 0,
left: 0,
width: 400,
height: 300,
opacity: 0.5,
fill: "blue",
selectable: false,
evented: false,
}))
.renderAll();
canvas { border: 1px solid black; }
<canvas id="c" width="400" height="300"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.min.js"></script>

How to select covered objects via mouse in fabricJS?

I'm trying to develop a way to select objects that are layered below and (totally) covered by other objects. One idea is to select the top object and then via doubleclick walk downwards through the layers. This is what I got at the moment:
var canvas = new fabric.Canvas("c");
fabric.util.addListener(canvas.upperCanvasEl, "dblclick", function (e) {
var _canvas = canvas;
var _mouse = _canvas.getPointer(e);
var _active = _canvas.getActiveObject();
if (e.target) {
var _targets = _canvas.getObjects().filter(function (_obj) {
return _obj.containsPoint(_mouse);
});
//console.warn(_targets);
for (var _i=0, _max=_targets.length; _i<_max; _i+=1) {
//check if target is currently active
if (_targets[_i] == _active) {
//then select the one on the layer below
_targets[_i-1] && _canvas.setActiveObject(_targets[_i-1]);
break;
}
}
}
});
canvas
.add(new fabric.Rect({
top: 25,
left: 25,
width: 100,
height: 100,
fill: "red"
}))
.add(new fabric.Rect({
top: 50,
left: 50,
width: 100,
height: 100,
fill: "green"
}))
.add(new fabric.Rect({
top: 75,
left: 75,
width: 100,
height: 100,
fill: "blue"
}))
.renderAll();
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.min.js"></script>
<canvas id="c" width="300" height="200"></canvas>
As you can see, trying to select the red rectangle from within the blue one is not working. I'm only able to select the green or the blue. I guess that after the first doubleclick worked (green is selected), clicking again just selects blue so the following doubleclick is only able to get green again.
Is there a way around this? Any other ideas?
After some time I finally was able to solve that by myself. Clicking on an object brings it to the top. On double-clicking I try to get the object one layer behind the current object. On another dblclick I get the one behind and so on. Works great for me and also allows for the selection of fully covered objects without the need to move others.
var canvas = new fabric.Canvas("c");
canvas.on("object:selected", function (e) {
if (e.target) {
e.target.bringToFront();
this.renderAll();
}
});
var _prevActive = 0;
var _layer = 0;
//
fabric.util.addListener(canvas.upperCanvasEl, "dblclick", function (e) {
var _canvas = canvas;
//current mouse position
var _mouse = _canvas.getPointer(e);
//active object (that has been selected on click)
var _active = _canvas.getActiveObject();
//possible dblclick targets (objects that share mousepointer)
var _targets = _canvas.getObjects().filter(function (_obj) {
return _obj.containsPoint(_mouse) && !_canvas.isTargetTransparent(_obj, _mouse.x, _mouse.y);
});
_canvas.deactivateAll();
//new top layer target
if (_prevActive !== _active) {
//try to go one layer below current target
_layer = Math.max(_targets.length-2, 0);
}
//top layer target is same as before
else {
//try to go one more layer down
_layer = --_layer < 0 ? Math.max(_targets.length-2, 0) : _layer;
}
//get obj on current layer
var _obj = _targets[_layer];
if (_obj) {
_prevActive = _obj;
_obj.bringToFront();
_canvas.setActiveObject(_obj).renderAll();
}
});
//create something to play with
canvas
//fully covered rect is selectable with dblclicks
.add(new fabric.Rect({
top: 75,
left: 75,
width: 50,
height: 50,
fill: "black",
stroke: "black",
globalCompositeOperation: "xor",
perPixelTargetFind: true
}))
.add(new fabric.Circle({
top: 25,
left: 25,
radius: 50,
fill: "rgba(255,0,0,.5)",
stroke: "black",
perPixelTargetFind: true
}))
.add(new fabric.Circle({
top: 50,
left: 50,
radius: 50,
fill: "rgba(0,255,0,.5)",
stroke: "black",
perPixelTargetFind: true
}))
.add(new fabric.Circle({
top: 75,
left: 75,
radius: 50,
fill: "rgba(0,0,255,.5)",
stroke: "black",
perPixelTargetFind: true
}))
.renderAll();
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="300" height="200"></canvas>
Just add one property during adding object to the canvas.
perPixelTargetFind: true
My task is a bit different - the mission is to pick the overlapping obj behind the current one:
[Left-click]+[Cmd-Key] on an selected-object to pick the first overlapping object which is right behind it.
If no overlapping objs are found, restart the search from top-layer
The idea is, for each click-event, intercept the selected object and replace it with our desired object.
A Fabric mouse-down event is like this:
User clicks on canvas
On canvas: find the target-obj by the coordinates of mouse cursor and stores it in the instance-variable (canvas._target)
Run event-handlers for mouse:down:before
Compare the target-obj found from step(2) with current selected object, fire selection:cleared/update/create events according to the results of comparison.
Set new activeObject(s)
Run event-handlers for mouse:down
We can use a customized event handler on mouse:down:before to intercept the target-obj found on Step(2) and replace it by our desired-object
fCanvas = new fabric.Canvas('my-canvas', {
backgroundColor: '#cbf1f1',
width: 800,
height: 600,
preserveObjectStacking: true
})
const r1 = new fabric.Rect({ width: 200, height: 200, stroke: null, top: 0, left: 0, fill:'red'})
const r2 = new fabric.Rect({ width: 200, height: 200, stroke: null, top: 50, left: 50, fill:'green'})
const r3 = new fabric.Rect({ width: 200, height: 200, stroke: null, top: 100, left: 100, fill:'yellow'})
fCanvas.add(r1, r2, r3)
fCanvas.requestRenderAll()
fCanvas.on('mouse:down:before', ev => {
if (!ev.e.metaKey) {
return
}
// Prevent conflicts with multi-selection
if (ev.e[fCanvas.altSelectionKey]) {
return
}
const currActiveObj = fCanvas.getActiveObject()
if (!currActiveObj) {
return
}
const pointer = fCanvas.getPointer(ev, true)
const hitObj = fCanvas._searchPossibleTargets([currActiveObj], pointer)
if (!hitObj) {
return
}
let excludeObjs = []
if (currActiveObj instanceof fabric.Group) {
currActiveObj._objects.forEach(x => { excludeObjs.push(x) })
} else {
// Target is single active object
excludeObjs.push(currActiveObj)
}
let remain = excludeObjs.length
let objsToSearch = []
let lastIdx = -1
const canvasObjs = fCanvas._objects
for (let i = canvasObjs.length-1; i >=0 ; i--) {
if (remain === 0) {
lastIdx = i
break
}
const obj = canvasObjs[i]
if (excludeObjs.includes(obj)) {
remain -= 1
} else {
objsToSearch.push(obj)
}
}
const headObjs = canvasObjs.slice(0, lastIdx+1)
objsToSearch = objsToSearch.reverse().concat(headObjs)
const found = fCanvas._searchPossibleTargets(objsToSearch, pointer)
if (found) {
fCanvas._target = found
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.3.1/fabric.min.js"></script>
<html>
<h4>Left-click + Cmd-key on overlapping area to pick the obj which is behind current one</h4>
<canvas id="my-canvas"></canvas>
</html>

Categories