-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf(): optimize perPixelTargetFind
#8770
Changes from 17 commits
8036930
34b0e17
04efeec
6bbe23a
d21ee1b
5f98acb
04ae878
a3a1123
22ffc9f
2c60600
d7ee90a
a6bb89a
815a743
a91831d
096eadb
04d471f
a97e51d
7caef9e
d5ed5bf
03e8019
593f760
a94ed32
de87f9f
f8f02d2
a1bf0e3
3593d05
84a0a2b
a81cf72
ee441ba
22fa5a3
a8c6251
ae30ec4
c6794b7
9d4b3e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -524,7 +524,7 @@ export class SelectableCanvas< | |
declare upperCanvasEl: HTMLCanvasElement; | ||
declare contextTop: CanvasRenderingContext2D; | ||
declare wrapperEl: HTMLDivElement; | ||
declare cacheCanvasEl: HTMLCanvasElement; | ||
declare pixelFindCanvasEl: HTMLCanvasElement; | ||
protected declare _isCurrentlyDrawing: boolean; | ||
declare freeDrawingBrush?: BaseBrush; | ||
declare _activeObject?: FabricObject; | ||
|
@@ -667,10 +667,23 @@ export class SelectableCanvas< | |
* @return {Boolean} | ||
*/ | ||
isTargetTransparent(target: FabricObject, x: number, y: number): boolean { | ||
const ctx = this.contextCache; | ||
const retina = this.getRetinaScaling(); | ||
const size = Math.max(2 * this.targetFindTolerance * retina, 10); | ||
if (this.pixelFindCanvasEl.width < size) { | ||
// TODO: handle in set | ||
this.pixelFindCanvasEl.width = this.pixelFindCanvasEl.height = size; | ||
} else { | ||
this.clearContext(ctx); | ||
} | ||
// in case the target is the activeObject, we cannot execute this optimization | ||
// because we need to draw controls too. | ||
if (isFabricObjectCached(target) && target !== this._activeObject) { | ||
// optimizatio: we can reuse the cache | ||
if ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it happened that i thought a couple of times of this:
And then i asked myself, why do i have to draw the controls? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. absolutely |
||
isFabricObjectCached(target) && | ||
target !== this._activeObject && | ||
!target.dirty | ||
) { | ||
// optimization: we can reuse the cache | ||
const normalizedPointer = this._normalizePointer(target, new Point(x, y)), | ||
targetRelativeX = Math.max( | ||
target.cacheTranslationX + normalizedPointer.x * target.zoomX, | ||
|
@@ -680,31 +693,46 @@ export class SelectableCanvas< | |
target.cacheTranslationY + normalizedPointer.y * target.zoomY, | ||
0 | ||
); | ||
// transform tolerance according to vpt | ||
// TODO: use sendVectorToPlane | ||
const tolerance = new Point() | ||
.scalarAdd(this.targetFindTolerance) | ||
.transform(invertTransform(this.viewportTransform), true); | ||
const size = tolerance.scalarMultiply(2).max(new Point(1, 1)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i need to think about this. this put ourselves in the case of transforming the tolerance with the vpt when what we want is really just canvas pixels. So if there is a small text or a thin line and the zoom level makes it super thin, we were not composensating for this, if the line was so thinned by the zoom out that was nearly transparent, we would consider it transparent before. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Shaman can you share the app code used to test this so i can reproduce the testing case? i m curious about trying a couple of things. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method accepts a global pointer, living in the viewport, so 1 is the is perceived as 1 by the user, not by the canvas. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes 1 viewport pixel. 1 of what are you looking at in that moment and also 1 of how much you can move your mouse. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I fixed it because previously it was checking tolerance in the canvas plane and not in the viewport. I will add a dedicated test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The app... I will share a link? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// performance optimization: | ||
// we draw the hit area to the dedicated canvas instead of using `getImageData` on the target's cache canvas | ||
// since `size` is transformed according to vpt the image is drawn as if transformed as well so `targetFindTolerance` can be used as the tolerance value | ||
ctx.drawImage( | ||
target._cacheCanvas, | ||
Math.floor(targetRelativeX - tolerance.x), | ||
Math.floor(targetRelativeY - tolerance.y), | ||
Math.ceil(size.x), | ||
Math.ceil(size.y), | ||
0, | ||
0, | ||
Math.ceil(size.x * retina), | ||
Math.ceil(size.y * retina) | ||
); | ||
ShaMan123 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return isTransparent( | ||
target._cacheContext, | ||
Math.round(targetRelativeX), | ||
Math.round(targetRelativeY), | ||
ctx, | ||
tolerance.x, | ||
tolerance.y, | ||
this.targetFindTolerance | ||
); | ||
} | ||
|
||
const ctx = this.contextCache, | ||
originalColor = target.selectionBackgroundColor, | ||
v = this.viewportTransform; | ||
|
||
target.selectionBackgroundColor = ''; | ||
|
||
this.clearContext(ctx); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. who is clearing the context now between different checks? and where the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oops. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will restore selection background... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure. i m building a little demo based on this concept so we can talk with examples. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
cool Since you merged the sandbox pr just run There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the deployed version Can you look at the sandbox github app and see it can operate in the repo? It should post links for us to reproduce with ease using the templates I built that are now merged into master There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The app I posted is nothing really, just objects on canvas using the next template There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i will look into this sandbox as soon as i can. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. correct |
||
|
||
ctx.save(); | ||
ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); | ||
ctx.translate(-x + this.targetFindTolerance, -y + this.targetFindTolerance); | ||
ctx.transform(...this.viewportTransform); | ||
target.render(ctx); | ||
ctx.restore(); | ||
|
||
target.selectionBackgroundColor = originalColor; | ||
|
||
return isTransparent(ctx, x, y, this.targetFindTolerance); | ||
return isTransparent( | ||
ctx, | ||
this.targetFindTolerance, | ||
this.targetFindTolerance, | ||
this.targetFindTolerance | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looking at this again shouldn't it be multiplied by retina? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in my branch i completely removed retina from the logic of target transparent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought it is needed for precision in case of blur There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. considering retina makes the cached/noncached case very similar and cut differences there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That isn't too bad, is it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes i think it will win the smaller code at the end. |
||
); | ||
ShaMan123 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
|
@@ -1195,7 +1223,6 @@ export class SelectableCanvas< | |
_setBackstoreDimension(prop: keyof TSize, value: number) { | ||
super._setBackstoreDimension(prop, value); | ||
this.upperCanvasEl[prop] = value; | ||
this.cacheCanvasEl[prop] = value; | ||
} | ||
|
||
/** | ||
|
@@ -1237,8 +1264,15 @@ export class SelectableCanvas< | |
} | ||
|
||
protected _createCacheCanvas() { | ||
this.cacheCanvasEl = this._createCanvasElement(); | ||
this.contextCache = this.cacheCanvasEl.getContext('2d')!; | ||
this.pixelFindCanvasEl = this._createCanvasElement(); | ||
this.contextCache = this.pixelFindCanvasEl.getContext('2d', { | ||
willReadFrequently: true, | ||
})!; | ||
const retina = this.getRetinaScaling(); | ||
this.contextCache.scale(retina, retina); | ||
this.pixelFindCanvasEl.width = this.pixelFindCanvasEl.height = Math.ceil( | ||
Math.max(2 * this.targetFindTolerance * retina, retina) | ||
); | ||
} | ||
|
||
protected _initWrapperElement() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would you max it out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would rather have a method
setTargetFindTolerance
that creates this mini canvas, i m not even sure retina scaling is a requirement since the mouse pixels ( the so called css pixels ) are always without retina.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do
I wasn't sure about retina. What will happen in case of ay scaled and blurry image? Will the hit test return a false positive?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I removed the max value of 10