-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(Control):
calcCornerCoords
angle + calculation (#9377)
- Loading branch information
Showing
10 changed files
with
255 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { expect, test } from '@playwright/test'; | ||
import setup from '../../../setup'; | ||
import { CanvasUtil } from '../../../utils/CanvasUtil'; | ||
|
||
setup(); | ||
|
||
test('Control hit regions', async ({ page }) => { | ||
const canvasUtil = new CanvasUtil(page); | ||
await canvasUtil.executeInBrowser((canvas) => { | ||
const rect = canvas.getActiveObject(); | ||
const render = ({ x, y }: fabric.Point, fill: string) => { | ||
const ctx = canvas.getTopContext(); | ||
ctx.fillStyle = fill; | ||
ctx.beginPath(); | ||
ctx.arc(x, y, 1, 0, Math.PI * 2); | ||
ctx.fill(); | ||
}; | ||
for (let y = 0; y <= canvas.height; y++) { | ||
for (let x = 0; x < canvas.width; x++) { | ||
const point = new fabric.Point(x, y); | ||
rect._findTargetCorner(point, true) && render(point, 'indigo'); | ||
rect._findTargetCorner(point) && render(point, 'magenta'); | ||
} | ||
} | ||
}); | ||
expect(await new CanvasUtil(page).screenshot()).toMatchSnapshot(); | ||
}); |
Binary file added
BIN
+14.7 KB
e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/** | ||
* Runs in the **BROWSER** | ||
* Imports are defined in 'e2e/imports.ts' | ||
*/ | ||
|
||
import * as fabric from 'fabric'; | ||
import { beforeAll } from '../../test'; | ||
|
||
beforeAll((canvas) => { | ||
canvas.setDimensions({ width: 300, height: 325 }); | ||
const controls = fabric.controlsUtils.createObjectDefaultControls(); | ||
Object.values(controls).forEach((control) => { | ||
control.sizeX = 20; | ||
control.sizeY = 25; | ||
control.touchSizeX = 30; | ||
control.touchSizeY = 35; | ||
}); | ||
const rect = new fabric.Rect({ | ||
left: 25, | ||
top: 60, | ||
width: 75, | ||
height: 100, | ||
controls, | ||
scaleY: 2, | ||
fill: 'blue', | ||
padding: 10, | ||
}); | ||
const group = new fabric.Group([rect], { | ||
angle: 30, | ||
scaleX: 2, | ||
interactive: true, | ||
subTargetCheck: true, | ||
}); | ||
canvas.add(group); | ||
canvas.centerObject(group); | ||
group.setCoords(); | ||
canvas.setActiveObject(rect); | ||
canvas.renderAll(); | ||
return { rect, group }; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { | ||
Object as FabricObject, | ||
Point, | ||
util, | ||
Control, | ||
} from '../../dist/index.mjs'; | ||
|
||
// Swapping of calcCornerCoords in #9377 | ||
|
||
// OLD CODE FOR REFERENCE AND IMPLEMENTATION TEST | ||
|
||
const halfPI = Math.PI / 2; | ||
|
||
class OldControl extends Control { | ||
calcCornerCoords( | ||
objectAngle, | ||
angle, | ||
objectCornerSize, | ||
centerX, | ||
centerY, | ||
isTouch | ||
) { | ||
let cosHalfOffset, sinHalfOffset, cosHalfOffsetComp, sinHalfOffsetComp; | ||
const xSize = isTouch ? this.touchSizeX : this.sizeX, | ||
ySize = isTouch ? this.touchSizeY : this.sizeY; | ||
if (xSize && ySize && xSize !== ySize) { | ||
// handle rectangular corners | ||
const controlTriangleAngle = Math.atan2(ySize, xSize); | ||
const cornerHypotenuse = Math.sqrt(xSize * xSize + ySize * ySize) / 2; | ||
const newTheta = | ||
controlTriangleAngle - util.degreesToRadians(objectAngle); | ||
const newThetaComp = | ||
halfPI - controlTriangleAngle - util.degreesToRadians(objectAngle); | ||
cosHalfOffset = cornerHypotenuse * util.cos(newTheta); | ||
sinHalfOffset = cornerHypotenuse * util.sin(newTheta); | ||
// use complementary angle for two corners | ||
cosHalfOffsetComp = cornerHypotenuse * util.cos(newThetaComp); | ||
sinHalfOffsetComp = cornerHypotenuse * util.sin(newThetaComp); | ||
} else { | ||
// handle square corners | ||
// use default object corner size unless size is defined | ||
const cornerSize = xSize && ySize ? xSize : objectCornerSize; | ||
const cornerHypotenuse = cornerSize * Math.SQRT1_2; | ||
// complementary angles are equal since they're both 45 degrees | ||
const newTheta = util.degreesToRadians(45 - objectAngle); | ||
cosHalfOffset = cosHalfOffsetComp = cornerHypotenuse * util.cos(newTheta); | ||
sinHalfOffset = sinHalfOffsetComp = cornerHypotenuse * util.sin(newTheta); | ||
} | ||
|
||
return { | ||
tl: new Point(centerX - sinHalfOffsetComp, centerY - cosHalfOffsetComp), | ||
tr: new Point(centerX + cosHalfOffset, centerY - sinHalfOffset), | ||
bl: new Point(centerX - cosHalfOffset, centerY + sinHalfOffset), | ||
br: new Point(centerX + sinHalfOffsetComp, centerY + cosHalfOffsetComp), | ||
}; | ||
} | ||
} | ||
|
||
class OldObject extends FabricObject { | ||
_calcCornerCoords(control, position) { | ||
const corner = control.calcCornerCoords( | ||
this.angle, | ||
this.cornerSize, | ||
position.x, | ||
position.y, | ||
false | ||
); | ||
const touchCorner = control.calcCornerCoords( | ||
this.angle, | ||
this.touchCornerSize, | ||
position.x, | ||
position.y, | ||
true | ||
); | ||
return { corner, touchCorner }; | ||
} | ||
} | ||
|
||
// END OF OLD CODE | ||
|
||
const newObject = new FabricObject({ width: 100, height: 100 }); | ||
|
||
const oldObject = new OldObject({ width: 100, height: 100 }); | ||
|
||
newObject.controls = { | ||
tl: new Control({ | ||
x: -0.5, | ||
y: -0.5, | ||
}), | ||
}; | ||
|
||
oldObject.controls = { | ||
tl: new OldControl({ | ||
x: -0.5, | ||
y: -0.5, | ||
}), | ||
}; | ||
|
||
const benchmark = (callback) => { | ||
const start = Date.now(); | ||
callback(); | ||
return Date.now() - start; | ||
}; | ||
|
||
const controlNew = benchmark(() => { | ||
for (let i = 0; i < 1_000_000; i++) { | ||
newObject._calcCornerCoords(newObject.controls.tl, new Point(4.5, 4.5)); | ||
} | ||
}); | ||
|
||
const controlOld = benchmark(() => { | ||
for (let i = 0; i < 1_000_000; i++) { | ||
oldObject._calcCornerCoords(oldObject.controls.tl, new Point(4.5, 4.5)); | ||
} | ||
}); | ||
|
||
console.log({ controlOld, controlNew }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { radiansToDegrees } from '../../util'; | ||
import { Group } from '../Group'; | ||
import { FabricObject } from './FabricObject'; | ||
import type { TOCoord } from './InteractiveObject'; | ||
|
||
describe('Object', () => { | ||
describe('setCoords for objects inside group with rotation', () => { | ||
it('all corners are rotated as much as the object total angle', () => { | ||
const object = new FabricObject({ | ||
left: 25, | ||
top: 60, | ||
width: 75, | ||
height: 100, | ||
angle: 10, | ||
scaleY: 2, | ||
fill: 'blue', | ||
}); | ||
const group = new Group([object], { | ||
angle: 30, | ||
scaleX: 2, | ||
interactive: true, | ||
subTargetCheck: true, | ||
}); | ||
group.setCoords(); | ||
const objectAngle = Math.round(object.getTotalAngle()); | ||
expect(objectAngle).toEqual(35); | ||
Object.values(object.oCoords).forEach((cornerPoint: TOCoord) => { | ||
const controlAngle = Math.round( | ||
radiansToDegrees( | ||
Math.atan2( | ||
cornerPoint.corner.tr.y - cornerPoint.corner.tl.y, | ||
cornerPoint.corner.tr.x - cornerPoint.corner.tl.x | ||
) | ||
) | ||
); | ||
expect(controlAngle).toEqual(objectAngle); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters