Fabric.js - Dynamic Crop Area with a rectangle - javascript

I want to make and dynamic crop area and found this snippet. It works perfect in normal usage, but when you scaled the original object before making the crop area, the crop zone seems not in the right position. Can you look into this pen for some help ?
var canvas = new fabric.CanvasEx('canvas');
var el;
var object, lastActive, object1, object2;
var cntObj = 0;
var selection_object_left = 0;
var selection_object_top = 0;
var src = "http://fabricjs.com/lib/pug.jpg";
fabric.Image.fromURL('https://omicron.aeon.co/images/08e7f2bb-f2ce-4058-a955-1c8d594468a2/card_SIZED-Aleksandr-Zykov-4975950437_b84f9f9ef8_o.jpg', function (oImg) {
oImg.top = canvas.getHeight()/2 - oImg.getHeight()/2;
oImg.left = canvas.getWidth()/2 - oImg.getWidth()/2;
canvas.add(oImg);
bindCropEvent(oImg);
});
canvas.renderAll();
function bindCropEvent(obj){
obj.on('object:dblclick', function(){
CropMode();
});
};
function CropMode() {
canvas.remove(el);
if (canvas.getActiveObject()) {
object = canvas.getActiveObject();
if (lastActive !== object) {
console.log('different object');
} else {
console.log('same object');
}
if (lastActive && lastActive !== object) {
//lastActive.clipTo = null; results in clip loss
}
el = new fabric.Rect({
fill: 'rgba(0,0,0,0.6)',
originX: 'left',
originY: 'top',
stroke: '#ccc',
strokeDashArray: [2, 2],
opacity: 1,
width: 1,
height: 1,
borderColor: 'red',
cornerColor: 'red',
hasRotatingPoint: false
});
el.left = canvas.getActiveObject().left;
selection_object_left = canvas.getActiveObject().left;
selection_object_top = canvas.getActiveObject().top;
el.top = canvas.getActiveObject().top;
el.width = canvas.getActiveObject().width * canvas.getActiveObject().scaleX;
el.height = canvas.getActiveObject().height * canvas.getActiveObject().scaleY;
//插入
canvas.add(el);
canvas.setActiveObject(el);
el.on('deselected', function(){
console.log('des');
doCrop();
});
} else {
alert("Please select an object or layer");
}
}
function doCrop() {
var eLeft = el.get('left');
var eTop = el.get('top');
var left = eLeft - object.left;
var top = eTop - object.top;
console.log(left, top);
left *= 1;
top *= 1;
console.log(left, top);
var eWidth = el.get('width');
var eHeight = el.get('height');
var eScaleX = el.get('scaleX');
var eScaleY = el.get('scaleY');
var width = eWidth * 1;
var height = eHeight * 1;
object.clipTo = function (ctx) {
ctx.rect(-(eWidth / 2) + left, -(eHeight / 2) + top, parseInt(width * eScaleX), parseInt( height * eScaleY));
}
canvas.remove(el);
lastActive = object;
canvas.renderAll();
}
Thanks !

when you create a rect, you can create new image with toDataURL(). What will be cropped image.
cropOptions = {
left: Math.floor(rect.left),
top: Math.floor(rect.top),
width: Math.floor(rect.width),
height: Math.floor(rect.height)
},
cropDataUrl ;
cropDataUrl = image.toDataURL(cropOptions);
new fabric.Image.fromURL(cropDataUrl, function(img) {
canvas.remove(image,rect).add(img); //this is your cropped image
})

Related

Text won't update in the view - fabricjs

I'm using fabricjs to make a ruler tool. When the user click on the canvas it will draw a small rectangle and a line (with length of 0) as the user starts to move the mouse the length of line increases. At the top of the line There's a text which will show the length of the line. The length of the line is updating But it won't display the updated length. It displays the updated length once I click again to finish drawing.
Here's my code
let oneSideDrawn = false;
canvas.on("mouse:down", (event) => {
function rulerDown() {
const mouse = canvas.getPointer(event.e);
const { x, y } = mouse;
const side = new fabric.Rect({
width: 3,
height: 16,
left: x,
top: y,
fill: "#222",
});
canvas.add(side);
updateCanvas();
if (oneSideDrawn) {
oneSideDrawn = false;
const objs = canvas.getObjects();
const length = objs.length;
const line = objs[length - 3];
const side2 = objs[length - 4];
side.set({ top: side2.top });
const txt = canvas.getById("myId");
const rulerGroup = new fabric.Group([side2, line, side, txt], {
basicStyle,
lockScalingX: true,
lockScalingY: true,
});
canvas.add(rulerGroup);
canvas.remove(side2);
canvas.remove(line);
canvas.remove(side);
canvas.remove(txt);
canvas.setActiveObject(rulerGroup);
updateCanvas();
updateMode("");
} else {
const line = new fabric.Rect({
width: 0,
height: 3,
left: x,
top: y + 6,
fill: "#222",
});
const txt = new fabric.Text("0 m", {
fontSize: 10,
left: line.left + line.width / 2,
top: side.top - 5,
id: "myId",
});
txt.set({ left: txt.left - txt.width / 2 });
canvas.add(line);
canvas.add(txt);
updateCanvas();
canvas.setActiveObject(line);
oneSideDrawn = true;
}
}
}
canvas.on("mouse:move", (event) => {
function rulerMove() {
const line = canvas.getActiveObject();
const lineTxt = canvas.getById("myId");
const mouse = canvas.getPointer(event.e);
const { x } = mouse;
const width = Math.abs(x - line.left);
if (!width) {
return false;
}
line.set({ width });
lineTxt.set({
text: parseFloat(width).toFixed(2) + " m",
left: line.left + width / 2 - 15,
});
updateCanvas();
}
}
Here's a screen shot
As we can see the text is updating in the console.log But the value is still "0 m" on the canvas
Update fabricjs!
I was facing this issue when I was using fabricjs 4.3.0.
In fabricjs 4.4.0 they solved this issue

Zoom/Pan on custom subclass and serialization

JSFiddle: https://jsfiddle.net/bd7kc24a/20/
A coworker and I have hit a snag with our attempt to use canvas and we are looking to have a Rect on the canvas to be the actual work area. We are trying to add a grid and/or dot background to that Rect, but we need that section to not scale when zooming occurs. If there is a simple way to do this, we'd love to know about it, but this is where we are currently.
The example jsfiddle above kinda does what we need it to do. But we are having issues:
Persisting that to the database as well as the redux store.
Making sure the grid stays the same pixel distance instead of zooming with objects
So I'm trying to create a custom subclass to re-apply that static canvas background when serialization occurs, however once I've added this base class it will not longer do the loadFromJSON call or hit the callback.
Any help would be appreciated! Especially if there is a simple way to do this that we simply missed in the documentation.
JSFiddle: https://jsfiddle.net/bd7kc24a/20/
'use strict';
debugger;
fabric.CanvasMap = fabric.util.createClass(fabric.Rect, {
type: 'canvasMap',
initialize: function(element, options) {
this.callSuper('initialize', element, options);
options && this.set('name', options.name);
options && this.set('imageRef', options.imageRef);
this.rescaleBackground(10);
},
rescaleBackground: function(scale) {
const padding = 0;
this.imageRef.scaleToWidth(scale);
var patternSourceCanvas = new fabric.StaticCanvas();
patternSourceCanvas.add(this.imageRef);
patternSourceCanvas.setDimensions({
width: this.imageRef.getWidth() + padding,
height: this.imageRef.getHeight() + padding
});
let pattern = new fabric.Pattern({
source: patternSourceCanvas.getElement(),
repeat: 'repeat'
});
this.setPatternFill(pattern);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
name: this.name
});
}
});
fabric.CanvasMap.fromObject = function (object, callback) {
var _enlivenedObjects;
object.imageRef = squareImg;
fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) {
delete object.objects;
_enlivenedObjects = enlivenedObjects;
});
const newCanvasMap = new fabric.CanvasMap(_enlivenedObjects, object);
return newCanvasMap;
};
let floorPlan = new fabric.Canvas('floorPlan', {
hoverCursor: 'pointer',
backgroundColor: '#cccccc'
});
function reloadFromJSON() {
let jsonMap = floorPlan.toJSON();
let jsonMapString = JSON.stringify(jsonMap);
floorPlan.loadFromJSON(jsonMapString, function() {
floorPlan.renderAll();
});
}
//let mapBackground = new fabric.Canvas();
function addShape() {
let coordinates = floorPlan.getVpCenter();
const circle = new fabric.Circle({
name: 'some circle',
radius: 20,
fill: 'blue',
left: coordinates.x - 20,
top: coordinates.y - 20
});
floorPlan.add(circle);
}
let defaultScale = 10;
let squareSVG = '<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg"><g> <title>background</title><rect fill="#fff" id="canvas_background" height="12" width="12" y="-1" x="-1"/></g><g> <title>Layer 1</title> <rect id="svg_1" height="9.87496" width="9.99996" y="0.062519" x="0.031269" stroke-width="1.5" stroke="#000000" fill="none"/> </g></svg>'
let squareImg;
fabric.loadSVGFromString(squareSVG, function(objects, options) {
squareImg = fabric.util.groupSVGElements(objects, options);
const map = new fabric.CanvasMap({
imageRef: squareImg,
height: 300, width: 300,
left: 20, top: 20,
strokeWidth: 2,
stroke: 'red',
selectable: false,
evented: false, hasBorders: true, hasControls: false
});
floorPlan.add(map);
floorPlan.renderAll();
});
function setBackground(scale) {
floorPlan._objects[0].rescaleBackground(scale);
}
//logic for binding objects to MAP
floorPlan.observe('object:moving', function(e) {
let obj = e.target;
if (obj.getHeight() > map.height || obj.getWidth() > map.width) {
obj.setScaleY(obj.originalState.scaleY);
obj.setScaleX(obj.originalState.scaleX);
}
obj.setCoords();
if (obj.top - (obj.cornerSize / 2) < map.top ||
obj.left - (obj.cornerSize / 2) < map.left) {
console.log(map.top, map.left);
obj.top = Math.max(obj.top, obj.top - obj.top + (obj.cornerSize / 2) + map.top);
obj.left = Math.max(obj.left, obj.left - obj.left + (obj.cornerSize / 2) + map.left);
}
if (obj.top + obj.height + obj.cornerSize > map.height ||
obj.left + obj.width + obj.cornerSize > map.width) {
obj.top = Math.min(obj.top, (map.height + map.top) - obj.height + obj.top - obj.top - obj.cornerSize / 2);
obj.left = Math.min(obj.left, (map.width + map.left) - obj.width + obj.left - obj.left - obj.cornerSize / 2);
}
});
//zoom
let clicks = 0;
floorPlan.on('mouse:down', function(event) {
clicks += 1;
setTimeout(function() {
if (clicks > 1) {
console.log(event.e.clientX);
floorPlan.zoomToPoint(new fabric.Point(event.e.clientX, event.e.clientY), floorPlan.getZoom() + 1);
}
clicks = 0;
}, 300);
});
function zoomIn() {
floorPlan.setZoom(floorPlan.getZoom() + 0.5);
console.log(floorPlan.getZoom(), defaultScale * floorPlan.getZoom());
setBackground(defaultScale / floorPlan.getZoom());
floorPlan.renderAll();
}
function zoomOut() {
floorPlan.setZoom(floorPlan.getZoom() - 0.5); ;
console.log(floorPlan.getZoom(), defaultScale * floorPlan.getZoom());
setBackground(defaultScale / floorPlan.getZoom());
floorPlan.renderAll();
}
function renderAll() {
floorPlan.renderAll();
}
//pan
function pan(event) {
event.e.preventDefault();
let panAmount = {
x: event.e.deltaX,
y: event.e.deltaY
};
//logic for binding the map to the screen during pan
/*
* We need to use the following points and check for negative or overflow values
* canvas.oCoords.tl - top left
* canvas.oCoords.bl - bottom left
* canvas.oCoords.tr - top right
* canvas.oCoords.br - bottom right
*/
//pan left
if (panAmount.x > 0) {
if (map.oCoords.tr.x < floorPlan.width - 40) {
panAmount.x = 0;
}
}
//pan right
if (panAmount.x < 0) {
if (map.oCoords.tl.x > 40) {
panAmount.x = 0;
}
}
//pan up
if (panAmount.y > 0) {
if (map.oCoords.bl.y < floorPlan.height - 40) {
panAmount.y = 0;
}
}
//pan down
if (panAmount.y < 0) {
if (map.oCoords.tl.y > 40) {
panAmount.y = 0;
}
}
// assuming the logic above allows the scroll, get the relative scroll values and create a new fabric point and then scroll to it
const delta = new fabric.Point(-panAmount.x, -panAmount.y);
floorPlan.relativePan(delta);
}
floorPlan.on('mouse:wheel', pan);

fengyuanchen Cropper - How to Fit Image into Canvas If Rotated?

I gone through documentation of cropper by fengyuanchen. I want the image to be fit by default into canvas if rotated. But I couldnt find a way to achieve this. Any idea how to achieve this functionality?
I want it to be like this to be default: link
Check issue demo here: link
I fixed this behavior but for my special needs. I just needed one rotate button which rotates an image in 90° steps. For other purposes you might extend/change my fix.
It works in "strict" mode by dynamically change the cropbox dimensions.
Here my function which is called, when I want to rotate an image. Ah and additionally the misplacement bug has also been fixed.
var $image;
function initCropper() {
$image = $('.imageUploadPreviewWrap > img').cropper({
autoCrop : true,
strict: true,
background: true,
autoCropArea: 1,
crop: function(e) {
}
});
}
function rotateImage() {
//get data
var data = $image.cropper('getCropBoxData');
var contData = $image.cropper('getContainerData');
var imageData = $image.cropper('getImageData');
//set data of cropbox to avoid unwanted behavior due to strict mode
data.width = 2;
data.height = 2;
data.top = 0;
var leftNew = (contData.width / 2) - 1;
data.left = leftNew;
$image.cropper('setCropBoxData',data);
//rotate
$image.cropper('rotate', 90);
//get canvas data
var canvData = $image.cropper('getCanvasData');
//calculate new height and width based on the container dimensions
var heightOld = canvData.height;
var heightNew = contData.height;
var koef = heightNew / heightOld;
var widthNew = canvData.width * koef;
canvData.height = heightNew;
canvData.width = widthNew;
canvData.top = 0;
if (canvData.width >= contData.width) {
canvData.left = 0;
}
else {
canvData.left = (contData.width - canvData.width) / 2;
}
$image.cropper('setCanvasData', canvData);
//and now set cropper "back" to full crop
data.left = 0;
data.top = 0;
data.width = canvData.width;
data.height = canvData.height;
$image.cropper('setCropBoxData',data);
}
This is my extended code provided by AlexanderZ to avoid cuttong wider images than container :)
var contData = $image.cropper('getContainerData');
$image.cropper('setCropBoxData',{
width: 2, height: 2, top: (contData.height/ 2) - 1, left: (contData.width / 2) - 1
});
$image.cropper('rotate', 90);
var canvData = $image.cropper('getCanvasData');
var newWidth = canvData.width * (contData.height / canvData.height);
if (newWidth >= contData.width) {
var newHeight = canvData.height * (contData.width / canvData.width);
var newCanvData = {
height: newHeight,
width: contData.width,
top: (contData.height - newHeight) / 2,
left: 0
};
} else {
var newCanvData = {
height: contData.height,
width: newWidth,
top: 0,
left: (contData.width - newWidth) / 2
};
}
$image.cropper('setCanvasData', newCanvData);
$image.cropper('setCropBoxData', newCanvData);
Not a direct answer to the question ... but i'm betting many people that use this plugin will find this helpfull..
Made this after picking up #AlexanderZ code to rotate the image.
So ... If you guys want to ROTATE or FLIP a image that has already a crop box defined and if you want that cropbox to rotate or flip with the image ... use these functions:
function flipImage(r, data) {
var old_cbox = $image.cropper('getCropBoxData');
var new_cbox = $image.cropper('getCropBoxData');
var canv = $image.cropper('getCanvasData');
if (data.method == "scaleX") {
if (old_cbox.left == canv.left) {
new_cbox.left = canv.left + canv.width - old_cbox.width;
} else {
new_cbox.left = 2 * canv.left + canv.width - old_cbox.left - old_cbox.width;
}
} else {
new_cbox.top = canv.height - old_cbox.top - old_cbox.height;
}
$image.cropper('setCropBoxData', new_cbox);
/* BUG: When rotated to a perpendicular position of the original position , the user perceived axis are now inverted.
Try it yourself: GO to the demo page, rotate 90 degrees then try to flip X axis, you'll notice the image flippped vertically ... but still ... it fliped in relation to its original axis*/
if ( r == 90 || r == 270 || r == -90 || r == -270 ) {
if ( data.method == "scaleX") {
$image.cropper("scaleY", data.option);
} else {
$image.cropper("scaleX", data.option);
}
} else {
$image.cropper(data.method, data.option);
}
$image.cropper(data.method, data.option);
}
function rotateImage(rotate) {
/* var img = $image.cropper('getImageData'); */
var old_cbox = $image.cropper('getCropBoxData');
var new_cbox = $image.cropper('getCropBoxData');
var old_canv = $image.cropper('getCanvasData');
var old_cont = $image.cropper('getContainerData');
$image.cropper('rotate', rotate);
var new_canv = $image.cropper('getCanvasData');
//calculate new height and width based on the container dimensions
var heightOld = new_canv.height;
var widthOld = new_canv.width;
var heightNew = old_cont.height;
var racio = heightNew / heightOld;
var widthNew = new_canv.width * racio;
new_canv.height = Math.round(heightNew);
new_canv.width = Math.round(widthNew);
new_canv.top = 0;
if (new_canv.width >= old_cont.width) {
new_canv.left = 0;
} else {
new_canv.left = Math.round((old_cont.width - new_canv.width) / 2);
}
$image.cropper('setCanvasData', new_canv);
if (rotate == 90) {
new_cbox.height = racio * old_cbox.width;
new_cbox.width = racio * old_cbox.height;
new_cbox.top = new_canv.top + racio * (old_cbox.left - old_canv.left);
new_cbox.left = new_canv.left + racio * (old_canv.height - old_cbox.height - old_cbox.top);
}
new_cbox.width = Math.round(new_cbox.width);
new_cbox.height = Math.round(new_cbox.height);
new_cbox.top = Math.round(new_cbox.top);
new_cbox.left = Math.round(new_cbox.left);
$image.cropper('setCropBoxData', new_cbox);
}
var photoToEdit = $('.photo_container img');
$( photoToEdit ).cropper({
autoCrop : true,
crop: function(e) {}
});
$("#rotate_left_btn").click( function () {
$( photoToEdit ).cropper('rotate', -90);
var containerHeightFactor = $(".photo_container").height() / $( photoToEdit).cropper('getCanvasData').height;
if ( containerHeightFactor < 1 ) { // if canvas height is greater than the photo container height, then scale (on both x and y
// axes to maintain aspect ratio) to make canvas height fit container height
$( photoToEdit).cropper('scale', containerHeightFactor, containerHeightFactor);
} else if ( $( photoToEdit).cropper('getData').scaleX != 1 || $( photoToEdit).cropper('getData').scaleY != 1 ) { // if canvas height
// is NOT greater than container height but image is already scaled, then revert the scaling cuz the current rotation will bring
// the image back to its original orientation (landscape/portrait)
$( photoToEdit).cropper('scale', 1, 1);
}
}
I Fixed this issue hope fully. i have added or changed the option to 0 (viewMode: 0,). Now its working well.
cropper = new Cropper(image, {
dragMode: 'none',
viewMode: 0,
width: 400,
height: 500,
zoomable: true,
rotatable: true,
crop: function(e) {
}
});
document.getElementById('rotateImg').addEventListener('click', function () {
cropper.rotate(90);
});

Kineticjs - free rotate on image

I need help only having the anchors for rotating. Right now there is five anchors and I don't know how to get rid of all of them except the rotate one. I would also only like the anchors to show when the user hovers over the image
Here is my code
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<body onmousedown="return false;">
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.4.min.js">
</script>
<script>
function update(activeAnchor) {
var group = activeAnchor.getParent();
var topLeft = group.get('.topLeft')[0];
var topRight = group.get('.topRight')[0];
var bottomRight = group.get('.bottomRight')[0];
var bottomLeft = group.get('.bottomLeft')[0];
var rotateAnchor = group.get('.rotateAnchor')[0];
var image = group.get('Image')[0];
var anchorX = activeAnchor.getX();
var anchorY = activeAnchor.getY();
var imageWidth = image.getWidth();
var imageHeight = image.getHeight();
var offsetX = Math.abs((topLeft.getX() + bottomRight.getX() + 10) / 2);
var offsetY = Math.abs((topLeft.getY() + bottomRight.getY() + 10) / 2);
// update anchor positions
switch (activeAnchor.getName()) {
case 'rotateAnchor':
group.setOffset(offsetX, offsetY);
break;
case 'topLeft':
topRight.setY(anchorY);
bottomLeft.setX(anchorX);
break;
case 'topRight':
topLeft.setY(anchorY);
bottomRight.setX(anchorX);
break;
case 'bottomRight':
topRight.setX(anchorX);
bottomLeft.setY(anchorY);
break;
case 'bottomLeft':
topLeft.setX(anchorX);
bottomRight.setY(anchorY);
break;
}
rotateAnchor.setX(topRight.getX() + 5);
rotateAnchor.setY(topRight.getY() + 20);
image.setPosition((topLeft.getPosition().x + 20), (topLeft.getPosition().y + 20));
var width = topRight.getX() - topLeft.getX() - 30;
var height = bottomLeft.getY() - topLeft.getY() - 30;
if (width && height) {
image.setSize(width, height);
}
}
function addAnchor(group, x, y, name, dragBound) {
var stage = group.getStage();
var layer = group.getLayer();
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
if (dragBound == 'rotate') {
anchor.setAttrs({
dragBoundFunc: function (pos) {
return getRotatingAnchorBounds(pos, group);
}
});
}
anchor.on('dragmove', function() {
update(this);
layer.draw();
});
anchor.on('mousedown touchstart', function() {
group.setDraggable(false);
this.moveToTop();
});
anchor.on('dragend', function() {
group.setDraggable(true);
layer.draw();
});
// add hover styling
anchor.on('mouseover', function() {
var layer = this.getLayer();
document.body.style.cursor = 'pointer';
this.setStrokeWidth(4);
layer.draw();
});
anchor.on('mouseout', function() {
var layer = this.getLayer();
document.body.style.cursor = 'default';
this.setStrokeWidth(2);
layer.draw();
});
group.add(anchor);
}
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function getRotatingAnchorBounds(pos, group) {
var topLeft = group.get('.topLeft')[0];
var bottomRight = group.get('.bottomRight')[0];
var topRight = group.get('.topRight')[0];
var absCenterX = Math.abs((topLeft.getAbsolutePosition().x + 5 + bottomRight.getAbsolutePosition().x + 5) / 2);
var absCenterY = Math.abs((topLeft.getAbsolutePosition().y + 5 + bottomRight.getAbsolutePosition().y + 5) / 2);
var relCenterX = Math.abs((topLeft.getX() + bottomRight.getX()) / 2);
var relCenterY = Math.abs((topLeft.getY() + bottomRight.getY()) / 2);
var radius = distance(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20);
var scale = radius / distance(pos.x, pos.y, absCenterX, absCenterY);
var realRotation = Math.round(degrees(angle(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20)));
var rotation = Math.round(degrees(angle(absCenterX, absCenterY, pos.x, pos.y)));
rotation -= realRotation;
group.setRotationDeg(rotation);
return {
y: Math.round((pos.y - absCenterY) * scale + absCenterY),
x: Math.round((pos.x - absCenterX) * scale + absCenterX)
};
}
function radians(degrees) { return degrees * (Math.PI / 180); }
function degrees(radians) { return radians * (180 / Math.PI); }
// Calculate the angle between two points.
function angle(cx, cy, px, py) {
var x = cx - px;
var y = cy - py;
return Math.atan2(-y, -x);
}
// Calculate the distance between two points.
function distance(p1x, p1y, p2x, p2y) {
return Math.sqrt(Math.pow((p2x - p1x), 2) + Math.pow((p2y - p1y), 2));
}
function initStage(images) {
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 400
});
var darthVaderGroup = new Kinetic.Group({
x: 270,
y: 100,
draggable: true
});
var yodaGroup = new Kinetic.Group({
x: 100,
y: 110,
draggable: true
});
var layer = new Kinetic.Layer();
/*
* go ahead and add the groups
* to the layer and the layer to the
* stage so that the groups have knowledge
* of its layer and stage
*/
layer.add(darthVaderGroup);
layer.add(yodaGroup);
stage.add(layer);
// darth vader
var darthVaderImg = new Kinetic.Image({
x: 0,
y: 0,
image: images.darthVader,
width: 200,
height: 138,
name: 'image'
});
darthVaderGroup.add(darthVaderImg);
addAnchor(darthVaderGroup, -20, -20, 'topLeft', 'none');
addAnchor(darthVaderGroup, 220, -20, 'topRight', 'none');
addAnchor(darthVaderGroup, 220, 158, 'bottomRight', 'none');
addAnchor(darthVaderGroup, -20, 158, 'bottomLeft','none');
addAnchor(darthVaderGroup, 225, 0, 'rotateAnchor','rotate');
darthVaderGroup.on('dragstart', function() {
this.moveToTop();
});
stage.draw();
}
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg'
};
loadImages(sources, initStage);
</script>
</body>
</html>
You can use each anchors show/hide methods inside the images mouseenter/mouseleave events to display the anchors when the mouse enters the image:
image.on("mouseleave",function(){ anchor1.hide(); }
image.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
Problem is that since your anchors are partly outside your image, so hiding the anchors when the mouse leaves the image might make the anchors "disappear" when the user intends to use them.
The ideal solution would be to listen for mouseenter/mouseleave events on the group which contains the image but also extends to include the outside part of the anchors. Unfortunately, a Kinetic.Group will not respond to mouseenter/mouseleave events.
A workaround is to create a Kinetic.Rect background to the group which includes the images plus the anchors. The rect will listen for mouseenter/mouseleave events and will show/hide the anchors. If you don't want the background rect to be visible, just set it's opacity to .001. The rect will still listen for events, but will be invisible.
groupBackgroundRect.on("mouseleave",function(){ anchor1.hide(); }
groupBackgroundRect.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
A related note:
With KineticJS, combining rotation with resizing is made more difficult than it needs to be because KineticJS uses offsetX/offsetY as both an object's rotation point and as an offset to its position. Your key to making it work will be to re-center the offset point after resizing so that your rotation takes place around the new centerpoint--not the previous centerpoint. (or reset the offset reference point to any other point that you want to rotate around).

Proportion scaling AND stretching of image using anchor points

i have an image with 8 anchor points. Thanks to an example, i've managed to get those on the 4 corners to only scale the picture. But i am having difficulty in making the other 4 to stretch the image ONLY.
The midTop & midBottom anchors shall stretch vertically; the midLeft and midRight anchors shall stretch horizontally. I think it might concern the bounds those anchors can move but i don't know how to proceed.
http://jsfiddle.net/Dppm7/3/ (sorry can't get this to work in jsFiddle)..
The output looks like this.
Please if anyone can help. :)
Some code for the anchors (not all codes for the middle anchors have been implemented):
// Update the positions of handles during drag.
// This needs to happen so the dimension calculation can use the
// handle positions to determine the new width/height.
switch (activeHandleName) {
case "topLeft":
topRight.setY(activeHandle.getY());
midRight.setY(activeHandle.getY());
midTop.setY(activeHandle.getY());
bottomLeft.setX(activeHandle.getX());
midLeft.setX(activeHandle.getX());
midBottom.setX(activeHandle.getX());
break;
case "topRight":
topLeft.setY(activeHandle.getY());
midLeft.setY(activeHandle.getY());
midTop.setY(activeHandle.getY());
bottomRight.setX(activeHandle.getX());
midBottom.setX(activeHandle.getX());
midRight.setX(activeHandle.getX());
break;
case "bottomRight":
bottomLeft.setY(activeHandle.getY());
midBottom.setY(activeHandle.getY());
midLeft.setY(activeHandle.getY());
topRight.setX(activeHandle.getX());
midTop.setX(activeHandle.getX());
midRight.setX(activeHandle.getX());
break;
case "bottomLeft":
bottomRight.setY(activeHandle.getY());
midBottom.setY(activeHandle.getY());
midRight.setY(activeHandle.getY());
topLeft.setX(activeHandle.getX());
midTop.setX(activeHandle.getX());
midLeft.setX(activeHandle.getX());
break;
case "midTop":
topRight.setY(activeHandle.getY());
topLeft.setY(activeHandle.getY());
midRight.setY(activeHandle.getY());
midLeft.setY(activeHandle.getY());
break;
case "midBottom":
bottomRight.setY(activeHandle.getY());
bottomLeft.setY(activeHandle.getY());
midRight.setY(activeHandle.getY());
midLeft.setY(activeHandle.getY());
break;
case "midRight":
topRight.setX(activeHandle.getX());
bottomRight.setX(activeHandle.getX());
midTop.setX(activeHandle.getX());
midBottom.setX(activeHandle.getX());
break;
case "midLeft":
topLeft.setX(activeHandle.getX());
bottomLeft.setX(activeHandle.getX());
midTop.setX(activeHandle.getX());
midBottom.setX(activeHandle.getX());
break;
}
// Calculate new dimensions. Height is simply the dy of the handles.
// Width is increased/decreased by a factor of how much the height changed.
newHeight = bottomLeft.getY() - topLeft.getY();
newWidth = image.getWidth() * newHeight / image.getHeight();
// Move the image to adjust for the new dimensions.
// The position calculation changes depending on where it is anchored.
// ie. When dragging on the right, it is anchored to the top left,
// when dragging on the left, it is anchored to the top right.
if (activeHandleName === "topRight" || activeHandleName === "bottomRight") {
image.setPosition(topLeft.getX(), topLeft.getY());
} else if (activeHandleName === "topLeft" || activeHandleName === "bottomLeft") {
image.setPosition(topRight.getX() - newWidth, topRight.getY());
}
imageX = image.getX();
imageY = image.getY();
// Update handle positions to reflect new image dimensions
topLeft.setPosition(imageX, imageY);
topRight.setPosition(imageX + newWidth, imageY);
bottomRight.setPosition(imageX + newWidth, imageY + newHeight);
bottomLeft.setPosition(imageX, imageY + newHeight);
midTop.setPosition(imageX + image.getWidth() / 2, imageY);
midBottom.setPosition(imageX + image.getWidth() / 2, imageY + newHeight);
midRight.setPosition(imageX + image.getWidth(), imageY + image.getHeight() / 2);
midLeft.setPosition(imageX, imageY + image.getHeight() / 2);
// Set the image's size to the newly calculated dimensions
if (newWidth && newHeight) {
image.setSize(newWidth, newHeight);
}
}
<script>
var imWidth;
var imHeight;
var topRight;
var topLeft;
var bottomLeft;
var width;
var height;
var group;
var bottomRight;
var image;
var aspectRatio;
var oldwidth;
var oldheight;
var oldtopleftX;
var oldtopleftY;
var shrinkLimitBound;
function update(activeAnchor) {
group = activeAnchor.getParent();
var anchorX = activeAnchor.getX();
var anchorY = activeAnchor.getY();
actRatio=imWidth/imHeight;
shrinkLimitBound=100;
height=bottomLeft.getY() - topLeft.getY();
width=topRight.getX() - topLeft.getX();
newRatio=(width)/(height);
width=actRatio*height;
switch (activeAnchor.getName()) {
case 'topLeft':
if(height<shrinkLimitBound)
{
height=shrinkLimitBound;
width=actRatio*height;;
topRight.setY(bottomRight.getY()-height);
}
else
{
if(anchorY < bottomRight.getY())
topRight.setY(anchorY);
else
topRight.setY(bottomRight.getY()-height);
}
topRight.setX(bottomRight.getX());
bottomLeft.setX(bottomRight.getX()-width);
bottomLeft.setY(bottomRight.getY());
topLeft.setX(bottomRight.getX()-width);
topLeft.setY(bottomRight.getY()-height);
break;
case 'topRight':
if(height<shrinkLimitBound)
{
height=shrinkLimitBound;
width=actRatio*height;;
topLeft.setY(bottomLeft.getY()-height);
}
else
{
if(anchorY < bottomLeft.getY()-shrinkLimitBound)
{
topLeft.setY(anchorY)
}
else
{
topLeft.setY(bottomLeft.getY()-height);
}
}
topLeft.setX(bottomLeft.getX());
bottomRight.setX(bottomLeft.getX()+width);
bottomRight.setY(bottomLeft.getY());
topRight.setX(bottomLeft.getX()+width);
topRight.setY(bottomLeft.getY()-height);
break;
case 'bottomRight':
if(height<shrinkLimitBound)
{
height=shrinkLimitBound;
width=actRatio*height;;
bottomLeft.setY(topLeft.getY()+height);
}
else
{
if(anchorY > topLeft.getY()+shrinkLimitBound)
{
bottomLeft.setY(anchorY);
}
else
bottomLeft.setY(topLeft.getY()+height);
}
bottomLeft.setX(topLeft.getX());
topRight.setX(topLeft.getX()+width);
topRight.setY(topLeft.getY());
bottomRight.setX(topLeft.getX()+width);
bottomRight.setY(topLeft.getY()+height);
break;
case 'bottomLeft':
if(height<shrinkLimitBound)
{
height=shrinkLimitBound;
width=actRatio*height;;
bottomRight.setY(topRight.getY()+height);
}
else
{
if(anchorY > topRight.getY())
bottomRight.setY(anchorY);
else
bottomRight.setY(topRight.getY()+height);
}
bottomRight.setX(topRight.getX());
topLeft.setX(topRight.getX()-width);
topLeft.setY(topRight.getY());
bottomLeft.setX(topRight.getX()-width);
bottomLeft.setY(topLeft.getY()+height);
break;
}
image.setPosition(topLeft.getPosition());
if(width>0 && height>0)
{
image.setSize(width,height);
}
oldwidth=width;
oldheight=height;
oldtopleftX=topLeft.getX();
oldtopleftY=topLeft.getY();
}
function addAnchor(group, x, y, name) {
var stage = group.getStage();
var layer = group.getLayer();
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 0,
radius: 4,
name: name,
draggable: true,
dragOnTop: false
});
anchor.on('dragmove', function() {
update(this);
layer.draw();
});
anchor.on('mousedown touchstart', function() {
group.setDraggable(false);
this.moveToTop();
});
anchor.on('dragend', function() {
group.setDraggable(true);
layer.draw();
});
// add hover styling
anchor.on('mouseover', function() {
var layer = this.getLayer();
document.body.style.cursor = 'pointer';
this.setStrokeWidth(4);
layer.draw();
});
anchor.on('mouseout', function() {
var layer = this.getLayer();
document.body.style.cursor = 'default';
this.setStrokeWidth(2);
layer.draw();
});
group.add(anchor);
}
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function initStage(images) {
var conWidth = 578; //container Width.
var conHeight = 400; //container Heitgh.
imWidth = images.dressTrailImage.width;
imHeight = images.dressTrailImage.height;
if (imWidth > conWidth)
{
imHeight = (imHeight/imWidth)*conWidth;
imWidth = conWidth;
}
if (imHeight > conHeight)
{
imWidth = (imWidth/imHeight)*conHeight;
imHeight = conHeight;
}
if ((imHeight < conHeight) && (imWidth < conWidth))
{
var diffX = conWidth - imWidth;
var diffY = conHeight - imHeight;
var diffY2 = (imHeight/imWidth)*diffX;
if (diffY2 > diffY)
{
imWidth = (imWidth/imHeight)*conHeight;
imHeight = conHeight;
}
else
{
imHeight = (imHeight/imWidth)*conWidth;
imWidth = conWidth;
}
}
images.UsrTrail.width = imWidth;
images.UsrTrail.height = imHeight;
var stage = new Kinetic.Stage({
container: 'container',
width: imWidth,
height: imHeight
});
var dressTrailImageGroup = new Kinetic.Group({
x: 0,
y: 0,
draggable: true,
//dragBoundFunc: function(pos) {
// console.log(pos);
// // var newX;
// // var newY;
// console.log(topLeft.x+","+bottomRight.y);
// x1=topLeft.x;
// x2=bottomRight.x;
// y1=topLeft.y;
// y2=bottomRight.y;
// x=pos.x;
// y=pos.y;
// var calsign = ((x-x1)*(y-y2))-((y-y1)*(x-x2))
// if (calsign < 0){
// return {
// x : pos.x,
// y : pos.y
// }
// }else {
// return {
// x : 50,
// y : 50
// }
// }
//}
// if (pos.x < ){
// newX=10;
// }
// else if ((pos.x+dressTrailImage.getWidth()) > stage.getWidth()-50){
// newX = stage.getWidth()-dressTrailImage.getWidth()-50;
// }
// else{
// newX = pos.x;
// };
// if(pos.y < 10){
// newY = 10;
// }
// else if((pos.y + dressTrailImage.getHeight()) > stage.getHeight()-50){
// newY = stage.getHeight()-dressTrailImage.getHeight()-50;
// }
// else {
// newY = pos.y;
// }
//console.log("newX:"+newX+", newY:"+newY);
// return {
// x : newX,
// y : newY,
// };
//}
});
// UsrTrail
var UsrTrailImg = new Kinetic.Image({
x: 0,
y: 0,
image: images.UsrTrail,
width: images.UsrTrail.width,
height: images.UsrTrail.height,
name: 'image'
});
var layer = new Kinetic.Layer();
/*
* go ahead and add the groups
* to the layer and the layer to the
* stage so that the groups have knowledge
* of its layer and stage
*/
layer.add(dressTrailImageGroup);
layer.add(UsrTrailImg);
stage.add(layer);
UsrTrailImg.moveToBottom();
intialAspRatio = images.dressTrailImage.width/images.dressTrailImage.height;
console.log("aspectRatio is :"+intialAspRatio);
// dress Trail Image
var inith=200; //set this to the desired height of the dress that shows up initially
var initw=intialAspRatio*inith;
var neck_user_x=50;//from backend
var neck_user_y=20;//from backend
var neck_dress_x=50;//from backend
var neck_dress_y=5;//from backend
//for getting the actual width and height of the User's Image
var UsrImgObjActual= new Image();
UsrImgObjActual.src=sources.UsrTrail;
UsrimgWidth=UsrImgObjActual.width;
UsrimgHeight=UsrImgObjActual.height;
//////////////////////////////////////////
// var UsrimgWidth= 180;
// var UsrimgHeight=270;
console.log("height Should Be 270 and is:"+UsrimgHeight);
console.log("Width Should Be 180 and is:"+UsrimgWidth);
var dressimgWidth=initw;
var dressimgHeight=inith;
console.log("usertrail image width adn height"+images.UsrTrail.width+"::"+images.UsrTrail.height);
console.log("neck user and dress resp"+neck_user_x+","+neck_user_y+','+neck_dress_x+','+neck_dress_y);
var x_draw=((neck_user_x*UsrimgWidth)-(neck_dress_x*dressimgWidth));
var y_draw=((neck_user_y*UsrimgHeight)-(neck_dress_y*dressimgHeight));
x_draw=x_draw/100;
y_draw=y_draw/100;
console.log("xdraw and ydraw:"+x_draw+','+y_draw);
//top left corner coordinates of the dress image.
var initx=x_draw;
var inity=y_draw;
var dressTrailImage = new Kinetic.Image({
x: initx,
y: inity,
image: images.dressTrailImage,
name: 'image',
width: initw,// images.dressTrailImage.width,
height: inith //images.dressTrailImage.height
});
// dressTrailImage.width = 50;
// dressTrailImage.height = dressTrailImage.width / intialAspRatio;
// console.log(dressTrailImage.height);
dressTrailImageGroup.add(dressTrailImage);
addAnchor(dressTrailImageGroup, initx , inity, 'topLeft');
addAnchor(dressTrailImageGroup, initx + initw , inity , 'topRight');
addAnchor(dressTrailImageGroup, initx + initw , inity + inith, 'bottomRight');
addAnchor(dressTrailImageGroup, initx , inity + inith, 'bottomLeft');
topLeft = dressTrailImageGroup.get('.topLeft')[0];
topRight = dressTrailImageGroup.get('.topRight')[0];
bottomRight = dressTrailImageGroup.get('.bottomRight')[0];
bottomLeft = dressTrailImageGroup.get('.bottomLeft')[0];
image = dressTrailImageGroup.get('.image')[0];
dressTrailImageGroup.on('dragstart', function() {
this.moveToTop();
});
stage.draw();
}
var sources = {
dressTrailImage: "http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg",
UsrTrail: "http://www.html5canvastutorials.com/demos/assets/yoda.jpg"
};
loadImages(sources, initStage);
//function called on clicking the submit button.
function Sunmit_fn(){
//neck positions of the trail dress in percentages (assumed for now!).
neckDressX=50;//in percentage.
neckDressY=5;//in percentage.
//height and width of the user image used in the canvas.
//original here here is refered to the height and width used in the canavs
var originalUserImgWidth = imWidth;
var originalUserImgHeight = imHeight;
var trailDressWidth = image.getWidth();//Final Image Width
var trailDressHeight = image.getHeight();//Final Image Height
imageX=topLeft.getParent().getX()+topLeft.getX()+1;//upper right anchor X Position
imageY=topLeft.getParent().getY()+topLeft.getY()+1;//upper right anchor Y Position
//formula for calculating the final neck positions of the resized and dragged dress
//with respect to the user image in percentages
neckFinalX=(imageX+(trailDressWidth*(neckDressX/100)))/originalUserImgWidth;
neckFinalY=(imageY+(trailDressHeight*(neckDressY/100)))/originalUserImgHeight;
//neck in percentages trail pic neck x,y.
neckFinalX=neckFinalX*100;
neckFinalY=neckFinalY*100;
//Just for testing.
console.log("neck position updated by User:");
console.log("X:"+neckFinalX+", Y:"+neckFinalY+")");
console.log("Resized Size of the dress is:");
console.log("Width:"+trailDressWidth+", Height:"+trailDressHeight);
}
</script>
This Script resizes only along one axis for four points.You can figure out the same for all the points

Categories

Resources