i have scrambled my puzzzle, now i need to have it rotated when i scrmable? can anyone help? Thanks :)
fillPatternImage:imageObj,
x:-pieceWidth*i/2,
y:-pieceHeight*j/2,
stroke: "#000000",
strokeWidth: 4,
lineCap: "round",
draggable: true,
x: i+pieceWidth/4 + (Math.random()*4)*((stage.getWidth()+pieceWidth)/12),
y: j+pieceHeight/4 + (Math.random()*2)*((stage.getHeight()+pieceHeight)/12),
});
JsFiddle : http://jsfiddle.net/vFez6/25/
The general method to rotate a canvas drawing is:
save the context state: context.save()
translate to the desired center of the object: context.translate(centerX,centerY)
rotate by the desired radian angle: context.rotate(Math.PI/4)
draw the object offset by 1/2 width & height: 'context.fillRect(-width/2,-height/2,50,30)'
restore the context state to its unrotated state: 'context.restore()'
Example code and a Demo: http://jsfiddle.net/m1erickson/7aqwjddt/
rotateRectangle(150,150,75,50,45*Math.PI/180);
function rotateRectangle(centerX,centerY,width,height,radianAngle){
ctx.save();
ctx.translate(centerX,centerY);
ctx.rotate(radianAngle);
ctx.fillRect(-width/2,-height/2,width,height);
ctx.restore();
}
Related
I figured out the only way to make a shape larger is by scaling it, not by setting a width and height. So I scaled the shape like this:
toObject: function() {
return fabric.util.object.extend(this.callSuper("toObject"), {
objectSize: this.objectSize,
scaleX: this.scaleX,
scaleY: this.scaleY,
labelColor: this.labelColor,
hasRotatingPoint: this.hasRotatingPoint,
hasControls: this.hasControls,
evented: this.evented,
hoverCursor: this.hoverCursor,
_controlsVisibility: this._controlsVisibility
});
},
but the ctx.fillText also scales with it. That is not what I want. The text needs to stay the same. So i did the following:
_render: function(ctx) {
this.callSuper("_render", ctx);
ctx.font = "12px Helvetica";
ctx.fillStyle = this.labelColor;
ctx.scale(1 / this.scaleX, 1 / this.scaleY);
ctx.fillText(
this.objectSize,
-this.objectSize.split('x')[0] / 2 + 10,
-this.objectSize.split('x')[1] / 2 - 5
);
}
Now the issue is that at some shape sizes (for example 200x200) the object also scales with it. When I remove the ctx.scale(1 / this.scaleX, 1 / this.scaleY) the object has the right size, except then the text is also scaled up.
How can I scale the shape without scaling the text or visa versa.
I'm new to fabricjs (and to Javascript development in general). I am "porting" a legacy Flex/Actionscript project and need to enable the user to create a complex clipping path for an image.
My approach in Actionscript was to use the Actionscript Graphics class using BlendMode.ERASE to "erase" from the yellow base rectangle (i.e. give the appearance of erasing) and then using that set of rects to create a bitmap to serve as an alpha channel for the final image (Step 3) created on the fly.
Can anyone suggest how I might accomplish a similar functionality in Fabric? It doesn't seem to support HTML5 Canvas Blend modes and while I see that it supports clipping paths for images, I'm not seeing how I can enable the user to interactively create a clipping path without doing lots of intersection checks to try to derive the points to create a new path on the fly.
Thanks!
Step 1: After the user has drawn a base rectangle, drag-option/alt-key enables them to draw a rectangle (the red line) which will be subtracted from the base rect.
Step 2: The base rect is shown with the subtraction.
Step 3: The base rect is used to clip or mask a section of the base image
Step 1
Step 2
Step 3
Will Tower,
There is no easy way to do it. Here are the steps:
Draw 'Yellow' rectangle
Draw 'Red' rectangle
Use clipping library like PolyBool for intersection and xor operations
Convert drawing result into the clipped path of combining rectangles
clip your image
I created some quick fiddle. You have to click on a each button to clip. It won't clip if you will not add 2 rectangles on the canvas. This is very simple example. In order to work properly you have to draw rectangles with mouse (make them dynamic). Also, this logic is not accounting for these variations (you have to work on them as well):
For these use cases Clipping Library will return to you 2 set of results, which means different logic should be implemented.
Actual code without jQuery, FabriJs, and PolyBool libraries:
var imgURL = 'http://fabricjs.com/lib/pug.jpg';
var clipYellowRect = null;
var clipRedRect = null;
var pug = null;
var canvas = new fabric.Canvas('c');
// insert image into canvas
var pugImg = new Image();
pugImg.onload = function (img) {
pug = new fabric.Image(pugImg, {
angle: 0,
width: 500,
height: 500,
left: 100,
top: 50,
scaleX: 0.5,
scaleY: 0.5,
clipName: 'pug',
});
canvas.add(pug);
};
pugImg.src = imgURL;
//draw yellow rectangle
$('#btnYellowRect').on('click', function(){
clipYellowRect = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 120,
top: 60,
width: 200,
height: 200,
fill: 'rgba(255,255,0,0.5)',
strokeWidth: 0,
selectable: false
});
canvas.add(clipYellowRect);
});
//draw red rectangle
$('#btnRedRect').on('click', function(){
clipRedRect = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 90,
top: 120,
width: 100,
height: 100,
strokeWidth: 3,
fill: 'transparent',
stroke: 'rgba(255,0,0,1)', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
canvas.add(clipRedRect);
});
//clip
$('#btnClip').on('click', function(){
var yellowRectRegion = getRegion(clipYellowRect);
var redRectRegion = getRegion(clipRedRect);
//determine inersection
var intersectResult = PolyBool.intersect({
regions: [yellowRectRegion],
inverted: false
}, {
regions: [redRectRegion],
inverted: false
});
//generate clipping path
var xorResult = PolyBool.xor({
regions: [yellowRectRegion],
inverted: false
}, {
regions: intersectResult.regions,
inverted: false
});
clipImage(xorResult.regions[0]);
});
//prepare data for clipping library
function getRegion(rect){
return [[rect.left, rect.top],
[rect.left + rect.width, rect.top],
[rect.left + rect.width, rect.top + rect.height],
[rect.left, rect.top + rect.height]]
}
function clipImage(points){
//actual clipping
pug.clipTo = function (ctx) {
var scaleXTo1 = (1 / pug.scaleX);
var scaleYTo1 = (1 / pug.scaleY);
ctx.save();
var ctxLeft = -( pug.width / 2 );
var ctxTop = -( pug.height / 2 );
ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
console.log(points)
ctx.moveTo(points[0][0] - pug.oCoords.tl.x, points[0][1] - pug.oCoords.tl.y);
for (var i=1; i < points.length; i++){
ctx.lineTo(points[i][0] - pug.oCoords.tl.x, points[i][1] - pug.oCoords.tl.y);
}
ctx.closePath();
ctx.restore();
};
clipYellowRect.remove();
clipRedRect.remove();
canvas.renderAll();
}
Hopefully it will help you.
Is there anyway to add shadow to transparent objects in FabricJS? I've used set fill transparent and after that setshadow. But normally setshadow can't be seen because object is transparent.
You can accomplish this using a clip path when drawing the object with the shadow. To do this with FabricJS you can subclass the object type you're generating the shadow from, and override the _render function to set a clip path that includes the area where the shadow falls but excludes the object itself. Ideally this reuses the code that actually draws the object.
fabric.BoxShadow = fabric.util.createClass(fabric.Rect, {
shadowColor: undefined,
shadowBlur: 0,
shadowOffsetX: 0,
shadowOffsetY: 0,
initialize(options) {
this.callSuper('initialize', options);
// Note: the way I have implemented this, the shadow settings cannot be changed after the object has been created.
this._shadow = new fabric.Shadow({
color: this.shadowColor,
blur: this.shadowBlur,
offsetX: this.shadowOffsetX,
offsetY: this.shadowOffsetY
});
},
_render: function(ctx) {
ctx.save();
// set clip path
let [offsetX, offsetY, blur] = [this.shadowOffsetX,
this.shadowOffsetY,
this.shadowBlur
];
let [top, left] = [this.width / -2, this.height / -2];
let region = new Path2D();
// The outer rectangle for our clipping path completely encompases the object and its shadow
let bounds = {
t: Math.min(top, top + offsetY - blur),
l: Math.min(left, left + offsetX - blur),
b: Math.max(top + this.height, top + this.height + offsetY + blur),
r: Math.max(left + this.width, left + this.width + offsetX + blur),
};
region.rect(bounds.l, bounds.t, bounds.r - bounds.l, bounds.b - bounds.t);
// now we subtract the actual object from our clipping path
// Note: we have to add beginPath function because the base class render code is going to treat this likc a CanvasRenderingContext2D instead of a Path2D
region.beginPath = function() { };
this.callSuper('_render', region);
ctx.clip(region, "evenodd");
// Fabric draws shadows, oddly enough, around the entire area rendered within this function. I haven't figured out the correct function to override to get our clip path to work with the normal fabric rendering pipeline
this.shadow = this._shadow;
// leverage the FabricJS shadow sizing logic
this._setShadow(ctx);
this.callSuper('_render', ctx);
this.shadow = undefined;
ctx.restore();
},
_renderPaintInOrder: function(ctx) {
if (ctx instanceof CanvasRenderingContext2D) {
this.callSuper('_renderPaintInOrder', ctx);
}
}
});
(function() {
let canvas = new fabric.Canvas('c');
fabric.Object.prototype.transparentCorners = false;
canvas.add(new fabric.Rect({
top: 40,
left: 40,
width: 100,
height: 100,
fill: 'lightblue'
}));
canvas.add(
new fabric.BoxShadow({
rx: 10,
top: 10,
left: 10,
width: 100,
height: 100,
shadowColor: 'black',
shadowBlur: 4,
shadowOffsetX: 3,
shadowOffsetY: 3
})
);
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
})();
html {
height: 100%;
}
body {
height: 100%;
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.7.0/fabric.js"></script>
</head>
<body>
<canvas id="c"></canvas>
</body>
</html>
I don't think the FabricJS API has a shadow-without-shape option.
But you can easily create the shadow-only using a native html5 canvas and then use that native canvas as an image source for a Fabric.Image object.
With native html5 canvas you can create a shadow without it's source shape like this:
Draw the shadowed shape,
Use compositing to "erase" the shape -- leaving just it's shadow
Example code drawing shadow-only on a native html5 canvas:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var shadowBlur=8;
var x=shadowBlur;
var y=shadowBlur;
var width=100;
var height=65;
canvas.width=width+shadowBlur*2;
canvas.height=height+shadowBlur*2;
// draw the shadowed shape
ctx.shadowColor='black';
ctx.shadowBlur=8;
ctx.fillRect(x-ctx.shadowOffsetX,y,width,height);
// always clean up! -- undo shadowing
ctx.shadowColor='rgba(0,0,0,0';
// use compositing to remove the shape
// (leaving just the shadow);
ctx.globalCompositeOperation='destination-out';
ctx.fillRect(x,y,width,height);
// always clean up! -- set compositing to default
ctx.globalCompositeOperation='source-over';
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=512 height=512></canvas>
Example creating a Fabric.Image using native html5 canvas as an image source:
// where "canvas" is a reference to an html5 canvas element
var myFabricImage=new fabric.Image(canvas, { left:0, top:0 });
The problem is to update the actual object.width, object.height and keep object.scaleX = 1 and object.scaleY = 1 when using the visual controls on the canvas.
The origin of the problem is when manipulating the object using controls, fabric.js changes the scale of the object, as well as scaling the stroke of paths, which I would like to be of the same width always.
So I would like to make it more like Adobe Illustrator-like, and not scale the width of the stroke along with the width of the rectalngular, for example.
I have found a solution to a question formulated in a different manner here:
Rect with stroke, the stroke line is mis-transformed when scaled
The idea is to calculate the new would-be width and height using the new scale factors, set the new dimensions and reset the scale factors to 1.
Here's the code example from that answer:
el.on({
'scaling': function(e) {
var obj = this,
w = obj.width * obj.scaleX,
h = obj.height * obj.scaleY,
s = obj.strokeWidth;
obj.set({
'height' : h,
'width' : w,
'scaleX' : 1,
'scaleY' : 1
});
}
});
From Fabric.js version 2.7.0 you can enable a strokeUniform property on the object this will always match the exact pixel size entered for stroke width.
var circle2 = new fabric.Circle({
radius: 65,
fill: '#4FC3F7',
left: 110,
opacity: 0.7,
stroke: 'blue',
strokeWidth: 3,
strokeUniform: true
});
http://fabricjs.com/stroke-uniform
I use kineticjs to work with shapes and transitions. For now I have made next code example:
http://jsfiddle.net/z6LaH/2/
hexagon = new Kinetic.RegularPolygon({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
sides: 6,
radius: hexRadius,
cornerRadius: 0,
fillPatternImage: img,
fillPatternOffset: [150, -150],
//fill: 'white',
stroke: 'black',
strokeWidth: 0
});
hexagon.on('mouseover touchstart', function() {
this.transitionTo({
cornerRadius: transRadius,
rotation: Math.PI / 2,
scale: {x: 0.75, y: 0.75},
easing: 'ease-in',
duration: duration,
callback: function() {
hexagon.transitionTo({
scale: {x: 1.1, y: 1.1},
duration: duration * 7,
easing: 'elastic-ease-out'
});
}
});
});
As you can see, fill pattern is rotating with shape. I need it to be fixed. So my question is:
Is it posible to make fixed fill pattern, while shape is rotating, and how?
UPDATE:
I got next approach: rotate fill pattern in opposite direction.
http://jsfiddle.net/z6LaH/3/
Is there any more elegant way to do the same?
Eric has just added the ability to save a user-defined clipping function to layers and groups.
First, you define a function that draws a clipping region on a layer or group
var layer = new Kinetic.Layer({
clipFunc: function(canvas) {
var context = canvas.getContext();
context.rect(0, 0, 400, 100);
}
});
Then you call the .clip() function to apply the clip. Here is Kinetic’s clip() function in the source code:
_clip: function(container) {
var context = this.getContext();
context.save();
this._applyAncestorTransforms(container);
context.beginPath();
container.getClipFunc()(this);
context.clip();
context.setTransform(1, 0, 0, 1, 0, 0);
}
The clip() function applies existing transforms before doing the clip. If you don’t like the transform part of the Kinetic function, you can always use “container.getClipFunc()” and then build your own myClipWithoutTransform() based on the _clip function above.