Place image to alpha channels in canvas - javascript

I need to find top, left, max width and max height values of all transparent places in canvas.
http://jsfiddle.net/alperxx/t521jra4/ (fiddle)
I have 4 transparent place in canvas. but i can't detect true places. First alpha channel corners colour must be red. Next is: black, yellow and green. But my groups is not correct.
If I complete the groups truely, every uploading photos will place to alpha places.
My Processes are in comment.
function alphaRatio(ctx) {
var pixelData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const data = pixelData.data;
const pixel_groups = [];
var total = 0;
for (let i = 0, dx = 0; dx < data.length; i++, dx = i << 2) {
if (data[dx + 3] == 0) {
//First, I'm catching all transparent pixels.
total++;
var x = (dx / 4) % ctx.canvas.width;
var y = ~~((dx / 4) / ctx.canvas.width);
// if pixel after a last grouped item add to in.
var found = false;
if (pixel_groups.length) {
for (im = 0; im < pixel_groups.length; im++) {
last_pixels = pixel_groups[im][pixel_groups[im].length - 1];
if (
last_pixels.x + 1 == x || last_pixels.x - 1 == x ||
last_pixels.y + 1 == y || last_pixels.y - 1 == y
) {
found = true;
pixel_groups[im].push({
x: x,
y: y
});
break;
}
}
}
if (!found) {
pixel_groups.push([{
x: x,
y: y
}]);
}
}
}
// i grouped all them
if (pixel_groups.length) {
console.debug(pixel_groups);
for (i = 0; i < pixel_groups.length; i++) {
var alphawidth = {};
var alphaheight = {};
for (im = 0; im < pixel_groups[i].length; im++) {
//now lets calculate first pixel left and top for width and height
if (typeof(alphawidth['min']) === 'undefined') {
alphawidth['min'] = pixel_groups[i][im].x;
alphawidth['max'] = pixel_groups[i][im].x;
} else {
if (pixel_groups[i][im].x < alphawidth['min']) {
alphawidth['min'] = pixel_groups[i][im].x;
}
if (pixel_groups[i][im].x > alphawidth['max']) {
alphawidth['max'] = pixel_groups[i][im].x;
}
}
if (typeof(alphaheight['min']) === 'undefined') {
alphaheight['min'] = pixel_groups[i][im].y;
alphaheight['max'] = pixel_groups[i][im].y;
} else {
if (pixel_groups[i][im].y < alphaheight['min']) {
alphaheight['min'] = pixel_groups[i][im].y;
}
if (pixel_groups[i][im].y > alphaheight['max']) {
alphaheight['max'] = pixel_groups[i][im].y;
}
}
}
// update group key for only x y w h
pixel_groups[i] = {
x: pixel_groups[i][0].x,
y: pixel_groups[i][0].y,
w: alphawidth['max'] - alphawidth['min'],
h: alphaheight['max'] - alphaheight['min']
};
}
// for test alpha places put a colour all corners
for (i = 0; i < pixel_groups.length; i++) {
var colour = ['red', 'black', 'yellow', 'green'];
canvas.add(new fabric.Circle({
left: pixel_groups[i].x,
top: pixel_groups[i].y,
radius: 4,
fill: colour[i],
originX: 'center',
originY: 'center',
hasControls: false
}));
canvas.add(new fabric.Circle({
left: pixel_groups[i].x + pixel_groups[i].w,
top: pixel_groups[i].y + pixel_groups[i].h,
radius: 4,
fill: colour[i],
originX: 'center',
originY: 'center',
hasControls: false
}));
canvas.add(new fabric.Circle({
left: pixel_groups[i].x + pixel_groups[i].w,
top: pixel_groups[i].y,
radius: 4,
fill: colour[i],
originX: 'center',
originY: 'center',
hasControls: false
}));
canvas.add(new fabric.Circle({
left: pixel_groups[i].x,
top: pixel_groups[i].y + pixel_groups[i].h,
radius: 4,
fill: colour[i],
originX: 'center',
originY: 'center',
hasControls: false
}));
}
return pixel_groups;
}
return false;
}

Related

I have an array that generates rectangles for me, but how can I add the lines to each rectangle in fabricjs

** I have a matrix of rectangles, and I have a for loop to generate lines, but it only appears in the first rectangle, how can it be done so that it is also generated in the other rectangles.
this is how it is currently adj image .. enter link description here**
rect: any;
numLine=3;
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 2; j++) {
this.rect = {
width: 90,
height: 90,
top: 90 * j,
left: 90 * i,
hasControls: false,
stroke: '#000000',
strokeWidth: 2,
fill: 'transparent',
borderColor: 'transparent',
cornerStrokeColor: ''
};
let c = new fabric.Rect(this.rect);
this.canvas.add(c);
}
}
**here I generate the lines invoking the measure of the rectangle to divide with the quantities of lines.**
for (let i = 1; i < this.numLine; i++) {
let line = {
top: this.rect.height / this.numLine * i,
stroke: 'red',
}
let l = new fabric.Line([0, 0, 90, 0],line)
this.canvas.add(l);
}

How to Space Evenly between objects horizontally and Vertically on canvas with fabricJS?

I'm using fabric js + typescript. I have more then 2 Objects and i want to set Space Evenly between those objects horizontally and Vertically . I can't find any solution.
I'm using fabric js + typescript. I have more then 2 Objects and i want to set Space Evenly between those objects horizontally and Vertically . I can't find any solution.
see this
fabric.Object.prototype.originX = "center";
fabric.Object.prototype.originY = "center";
var canvas = window._canvas = new fabric.Canvas('c');
var circle1 = new fabric.Circle({
radius: 30, fill: 'red', left: 50, top: 70, originX: "left", originY: "top"
});
var circle2 = new fabric.Circle({
radius: 40, fill: 'green', left: 200, top: 100, originX: "left", originY: "top"});
var circle3 = new fabric.Circle({
radius: 30, fill: 'blue', left: 300, top: 150, originX: "left", originY: "top"
});
canvas.add(circle1);
canvas.add(circle2);
canvas.add(circle3);
$('#distribute').click(function () {
var group = canvas.getActiveObject();
var items = canvas.getActiveObject()._objects,
newDim = getNewViewDimensions(group, 'horizontal');
group.set({
// width: newDim.width + (2 * 0),
// height: newDim.height + (items.length - 1) * 10 + (2 * 0)
});
for (var i = 0; i < items.length; i++) {
if (i == 0) {
items[i].set({
left: -group.width / 2 + 0,
top: -group.height / 2 + 0
});
} else {
items[i].set({
left: items[i - 1].left,
top: items[i - 1].top + items[i - 1].height + 10
});
}
}
canvas.renderAll();
});
function getNewViewDimensions(group, val) {
var objects = canvas.getActiveObject()._objects, minWidth = 0, minHeight = 0, w, h;
switch (val) {
case 'vertical':
for (i = 0; i < objects.length; i++) {
minWidth += objects[i].width * objects[i].scaleX;
h = objects[i].height * objects[i].scaleY;
if (h > minHeight) {
minHeight = h;
}
}
break;
case 'horizontal':
for (var i = 0; i < objects.length; i++) {
minHeight += objects[i].height * objects[i].scaleY;
w = objects[i].width * objects[i].scaleX;
if (w > minWidth) {
minWidth = w;
}
}
break;
}
return {
width: minWidth,
height: minHeight
}
}
canvas {
border: 1px solid #999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.5.0/fabric.min.js"></script>
<canvas id="c" width="400" height="300"></canvas>
<small> select all circles and hit the button </small>
<br>
<br>
<button id="distribute"> distribute </button>

Fabric js with gesture how to prevent zooming on touch devices

I'm using fabric js with gestures, but i want to prevent zooming, if zoom is getting more than 4x in and less than 1x out
I've tried to call preventDefault() and stopPropogation() functions on the event, but it doesn't stop the zooming
event.e.stopPropagation();
Get the deltaY & getZoom then you can limit the zoom as below.
var canvas = new fabric.Canvas('c');
var dia1 = new fabric.Circle({
radius: 12,
originX: 'center',
originY: 'center',
fill: 'transparent',
strokeWidth: 5,
stroke: "red",
});
var dia2 = new fabric.Circle({
radius: 5,
originX: 'center',
originY: 'center',
fill: 'red',
});
var targetEl = new fabric.Group([dia1, dia2], {
originX: 'center',
originY: 'center',
});
canvas.centerObject(targetEl);
canvas.add(targetEl);
canvas.renderAll();
//mouse zoom
canvas.on('mouse:wheel', function(opt) {
var delta = opt.e.deltaY;
var pointer = canvas.getPointer(opt.e);
var zoom = canvas.getZoom();
zoom = zoom - delta / 200;
// limit zoom to 4x in
if (zoom > 4) zoom = 4;
// limit zoom to 1x out
if (zoom < 1) {
zoom = 1;
canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
}
canvas.zoomToPoint({
x: opt.e.offsetX,
y: opt.e.offsetY
}, zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
});
//touch zoom
canvas.on({
'touch:gesture': function(e) {
if (e.e.touches && e.e.touches.length == 2) {
pausePanning = true;
var point = new fabric.Point(e.self.x, e.self.y);
if (e.self.state == "start") {
zoomStartScale = canvas.getZoom();
}
var delta = zoomStartScale * e.self.scale;
canvas.zoomToPoint(point, delta);
pausePanning = false;
// limit zoom to 4x in
if (delta > 4) delta = 4;
// limit zoom to 1x out
if (delta < 1) {
delta = 1;
canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
}
}
}
});
canvas {
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.4.0/fabric.js"></script>
<canvas id="c" width="600" height="300"></canvas>

Create Custom Pattern Brush

I am with a task in which I need to create two very specific type of Pattern Brush using Fabric.js
A Dashed Line with a X in the end.
A simple arrowed Line.
I need these two types of brushes in the free drawing mode.
Any guidance or suggestion would be very helpful.
This is what I tried for Brush Number 1 but this doesn't solve the purpose:
var hLinePatternBrush = new fabric.PatternBrush(canvas);
hLinePatternBrush.getPatternSrc = function() {
var patternCanvas = fabric.document.createElement('canvas');
patternCanvas.width = patternCanvas.height = 10;
var ctx = patternCanvas.getContext('2d');
ctx.strokeStyle = '#ffffff';
ctx.strokeLineCap ="round";
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(5, 10);
ctx.closePath();
ctx.stroke();
return patternCanvas;
};
canvas.freeDrawingBrush = hLinePatternBrush;
And for Brush number 2 I have no clue whatsoever.
Here is my implementation for the problem. Might help someone in the future.
fabric.Path.prototype.selectable = false;
fabric.Triangle.prototype.selectable = false;
fabric.Text.prototype.selectable = false;
/* ------------------------ Player Path Brush --------------------- */
var vLinePatternBrush = new fabric.PencilBrush(canvas);
vLinePatternBrush.color = '#fff';
if($("#line_type").val() == 'choose_type' || $("#line_type").val() == 'player_path' )
{
vLinePatternBrush.strokeDashArray = [5, 15];
}
else
{
vLinePatternBrush.strokeDashArray = [0,0];
}
vLinePatternBrush.hasControls = false;
canvas.freeDrawingBrush = vLinePatternBrush;
//canvas.freeDrawingBrush.color = '#fff';
canvas.freeDrawingBrush.width = 4;
//Choosing the Right Brush as per Users Requirement
$("#line_type").change(function(e){
if($("#line_type").val() == 'choose_type' || $("#line_type").val() == 'player_path' )
{
canvas.freeDrawingBrush = vLinePatternBrush;
canvas.freeDrawingBrush.color = '#fff';
canvas.freeDrawingBrush.width = 4;
vLinePatternBrush.strokeDashArray = [5, 15];
}
else
{
var normalLine = new fabric['PencilBrush'](canvas);
normalLine.strokeDashArray = [0,0];
canvas.freeDrawingBrush = normalLine;
canvas.freeDrawingBrush.color = '#fff';
canvas.freeDrawingBrush.width = 4;
console.log('here');
}
});
// This is required to make sure that the objects can be selected if wrapped inside a path line.
canvas.on('object:added', function(e) {
console.log(e);
if(e.target.type == 'path' || e.target.type == 'text' || e.target.type == 'triangle')
{
console.log('Sending Object to Background');
canvas.sendToBack(e.target);
}
});
// Handling the X and the Arrow part Once the Path is Drawn
canvas.on('path:created', function(path) {
console.log(path);
if($("#line_type").val() == 'choose_type' || $("#line_type").val() == 'player_path' )
{
console.log(path.path.path[(path.path.path.length -1)]);
var x1 = path.path.path[0][1];
var y1 = path.path.path[0][2];
var x2 = path.path.path[(path.path.path.length -1)][1];
var y2 = path.path.path[(path.path.path.length -1)][2];
var angle = calcArrowAngle(x1,y1,x2,y2);
angle = angle - 90;
var text = new fabric.Text('+', {
left: path.path.path[(path.path.path.length -1)][1],
top: path.path.path[(path.path.path.length -1)][2],
fill: 'white',
originX: 'center',
originY: 'center',
flipx: true,
selectable: false,
flipy: true,
fontSize: 80,
fontFamily: 'ABeeZee',
fill: 'white',
angle: angle,
hasControls: false
});
canvas.add(text);
}
if($("#line_type").val() == 'ball_path' )
{
console.log(path.path.path[(path.path.path.length -1)]);
var x1 = path.path.path[0][1];
var y1 = path.path.path[0][2];
var x2 = path.path.path[(path.path.path.length -1)][1];
var y2 = path.path.path[(path.path.path.length -1)][2];
var angle = calcArrowAngle(x2,y2,x1,y1);
angle = angle - 90;
arrow = new fabric.Triangle({
left: (path.path.path[(path.path.path.length -1)][1] + 2),
top: (path.path.path[(path.path.path.length -1)][2] + 2),
originX: 'center',
originY: 'center',
hasBorders: false,
hasControls: false,
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
pointType: 'arrow_start',
angle: angle,
width: 15,
height: 15,
fill: 'white',
hasControls: false
});
canvas.add(arrow);
}
// This is specific to my implementation for undo and redo. Once can ignore
updateModifications(true);
});
In the First Section of the code I am just creating two simple pencilBrush one with DottedLines and one a normal Line
In the second part I am getting the exact point where the Path ended (may be not the best way to do it, but it worked for me). Once I get the Point I am dropping the desired shape in that position.
CalcArrowAngle: Credits StackOverflow
function calcArrowAngle(x1, y1, x2, y2) {
var angle = 0,
x, y;
x = (x2 - x1);
y = (y2 - y1);
if (x === 0) {
angle = (y === 0) ? 0 : (y > 0) ? Math.PI / 2 : Math.PI * 3 / 2;
} else if (y === 0) {
angle = (x > 0) ? 0 : Math.PI;
} else {
angle = (x < 0) ? Math.atan(y / x) + Math.PI : (y < 0) ? Math.atan(y / x) + (2 * Math.PI) : Math.atan(y / x);
}
return (angle * 180 / Math.PI);
}
I am not saying this is the best way to do it. But it solved the problem in hand.
OUTPUT PNG:

Fabric.js bounding box with blurred shadows

I'm trying to draw a bounding box around objects that have a shadow. object.getBoundingRect() does not take into account shadows. Regular shadows are straightforward, but when blur is added it is more difficult.
Is there a way to get the bounds?
https://jsfiddle.net/e77c0owf/
function getObjBounds(obj) {
var bounds = obj.getBoundingRect();
var shadow = obj.getShadow();
if (shadow !== null) {
var blur = shadow.blur;
var signX = shadow.offsetX >= 0.0 ? 1.0 : -1.0;
var signY = shadow.offsetY >= 0.0 ? 1.0 : -1.0;
var offsetX = (shadow.offsetX + (signX * blur)) * Math.abs(obj.scaleX);
var offsetY = (shadow.offsetY + (signY * blur)) * Math.abs(obj.scaleY);
if (offsetX > 0) {
bounds.width += offsetX;
} else if (offsetX < 0) {
bounds.width += Math.abs(offsetX);
bounds.left -= Math.abs(offsetX);
}
if (offsetY > 0) {
bounds.height += offsetY;
} else if (offsetY < 0) {
bounds.height += Math.abs(offsetY);
bounds.top -= Math.abs(offsetY);
}
}
return bounds;
}
You have to check fabricjs calculations for blurring and scaling, plus you have to take in account situation where offset of shadow is smaller than blur value.
function getObjBounds(obj) {
var bounds = obj.getBoundingRect();
var shadow = obj.getShadow();
if (shadow !== null) {
var blur = shadow.blur;
var mBlur = blur * Math.abs(obj.scaleX + obj.scaleY) / 4
var signX = shadow.offsetX >= 0.0 ? 1.0 : -1.0;
var signY = shadow.offsetY >= 0.0 ? 1.0 : -1.0;
var mOffsetX = shadow.offsetX * Math.abs(obj.scaleX);
var mOffsetY = shadow.offsetY * Math.abs(obj.scaleY);
var offsetX = mOffsetX + (signX * mBlur);
var offsetY = mOffsetY + (signY * mBlur);
if (mOffsetX > mBlur) {
bounds.width += offsetX;
} else if (mOffsetX < -mBlur) {
bounds.width -= offsetX;
bounds.left += offsetX;;
} else {
bounds.width += mBlur * 2;
bounds.left -= mBlur - mOffsetX;
}
if (mOffsetY > mBlur) {
bounds.height += offsetY;
} else if (mOffsetY < -mBlur) {
bounds.height -= offsetY;
bounds.top += offsetY;
} else {
bounds.height += mBlur * 2;
bounds.top -= mBlur - mOffsetY;
}
}
return bounds;
}
resulting fiddle:
https://jsfiddle.net/e77c0owf/1/
// ************************************
// Bounding box calculation logic
// ************************************
function getObjBounds(obj) {
var bounds = obj.getBoundingRect();
var shadow = obj.getShadow();
if (shadow !== null) {
var blur = shadow.blur;
var mBlur = blur * Math.abs(obj.scaleX + obj.scaleY) / 4
var signX = shadow.offsetX >= 0.0 ? 1.0 : -1.0;
var signY = shadow.offsetY >= 0.0 ? 1.0 : -1.0;
var mOffsetX = shadow.offsetX * Math.abs(obj.scaleX);
var mOffsetY = shadow.offsetY * Math.abs(obj.scaleY);
var offsetX = mOffsetX + (signX * mBlur);
var offsetY = mOffsetY + (signY * mBlur);
if (mOffsetX > mBlur) {
bounds.width += offsetX;
} else if (mOffsetX < -mBlur) {
bounds.width -= offsetX;
bounds.left += offsetX;;
} else {
bounds.width += mBlur * 2;
bounds.left -= mBlur - mOffsetX;
}
if (mOffsetY > mBlur) {
bounds.height += offsetY;
} else if (mOffsetY < -mBlur) {
bounds.height -= offsetY;
bounds.top += offsetY;
} else {
bounds.height += mBlur * 2;
bounds.top -= mBlur - mOffsetY;
}
}
return bounds;
}
// ************************************
// Draw a ton of test cases below here
// ************************************
// Create a canvas
var canvas = new fabric.Canvas('c', {
backgroundColor: '#FFFFFF'
});
canvas.add(new fabric.Rect({
fill: 'red',
left: 100,
top: 100,
width: 50,
height: 50,
shadow: {
color: 'black',
blur: 0,
offsetX: -20,
offsetY: -10
}
}));
canvas.add(new fabric.Rect({
fill: 'red',
left: 30,
top: 20,
width: 50,
height: 50,
shadow: {
color: 'black',
blur: 0,
offsetX: -20,
offsetY: 0
}
}));
canvas.add(new fabric.Rect({
fill: 'red',
left: 30,
top: 250,
width: 50,
height: 50,
shadow: {
color: 'black',
blur: 0,
offsetX: 60,
offsetY: -60
}
}));
canvas.add(new fabric.Rect({
fill: 'red',
left: 180,
top: 20,
width: 50,
height: 50,
shadow: {
color: 'black',
blur: 10,
offsetX: 20,
offsetY: 20
}
}));
canvas.add(new fabric.Rect({
fill: 'red',
left: 180,
top: 150,
width: 50,
height: 50,
shadow: {
color: 'black',
blur: 20,
offsetX: 0,
offsetY: 0
}
}));
canvas.add(new fabric.Rect({
fill: 'red',
left: 220,
top: 280,
width: 50,
height: 50,
shadow: {
color: 'black',
blur: 20,
offsetX: -10,
offsetY: -10
}
}));
canvas.add(new fabric.Rect({
fill: 'red',
left: 280,
top: 20,
width: 50,
height: 50,
scaleX: 2.3,
scaleY: 2.3,
shadow: {
color: 'black',
blur: 10,
offsetX: 20,
offsetY: 20
}
}));
canvas.add(new fabric.Rect({
fill: 'red',
left: 360,
top: 230,
width: 50,
height: 50,
scaleX: 0.5,
scaleY: 0.5,
shadow: {
color: 'black',
blur: 20,
offsetX: 0,
offsetY: 0
}
}));
canvas.add(new fabric.Rect({
fill: 'red',
left: 380,
top: 290,
width: 50,
height: 50,
scaleX: 2.0,
scaleY: 2.0,
shadow: {
color: 'black',
blur: 10,
offsetX: 0,
offsetY: 0
}
}));
canvas.add(new fabric.Rect({
fill: 'red',
left: 140,
top: 380,
width: 50,
height: 50,
scaleX: -4.0,
scaleY: 0.5,
shadow: {
color: 'black',
blur: 20,
offsetX: -10,
offsetY: -10
}
}));
// Draw bounding boxes
var objs = canvas.getObjects();
var boxes = [];
for (var i = 0; i < objs.length; i++) {
var box = getObjBounds(objs[i]);
boxes.push(new fabric.Rect({
fill: '',
stroke: 'blue',
strokeWidth: 1,
left: box.left,
top: box.top,
width: box.width,
height: box.height
}));
}
for (var j = 0; j < boxes.length; j++) {
canvas.add(boxes[j]);
}
canvas.renderAll();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.js"></script>
<canvas id="c" width="550" height="450"></canvas>

Categories