Separated math utility into area specific functions. Decluttered LayerSprite selection logic

This commit is contained in:
Igor Zinken
2021-01-10 22:16:45 +01:00
parent 9df39d1270
commit 60b87f6626
12 changed files with 118 additions and 60 deletions

View File

@@ -186,7 +186,7 @@ import {
DROPBOX_FILE_SELECTOR, SAVE_DROPBOX_DOCUMENT
} from "@/definitions/modal-windows";
import { supportsFullscreen, setToggleButton } from "@/utils/environment-util";
import { getRectangleForSelection } from "@/utils/image-math";
import { getRectangleForSelection } from "@/math/selection-math";
import { getCanvasInstance, runSpriteFn, getSpriteForLayer } from "@/factories/sprite-factory";
import messages from "./messages.json";

View File

@@ -56,7 +56,7 @@ import ZoomableCanvas from "@/components/ui/zcanvas/zoomable-canvas";
import InteractionPane, { MODE_PAN, MODE_LAYER_SELECT } from "@/components/ui/zcanvas/interaction-pane";
import Scrollbars from "./scrollbars/scrollbars";
import ToolTypes, { MAX_ZOOM, calculateMaxScaling } from "@/definitions/tool-types";
import { scaleToRatio, scaleValue } from "@/utils/image-math";
import { scaleToRatio, scaleValue } from "@/math/image-math";
import { isMobile } from "@/utils/environment-util";
import {
getCanvasInstance, setCanvasInstance,

View File

@@ -37,7 +37,7 @@ import { mapGetters, mapMutations } from "vuex";
import ToolTypes, { MIN_ZOOM, MAX_ZOOM } from "@/definitions/tool-types";
import Slider from "@/components/ui/slider/slider";
import messages from "./messages.json";
import { degreesToRadians, radiansToDegrees } from "@/utils/image-math";
import { degreesToRadians, radiansToDegrees } from "@/math/image-math";
export default {
i18n: { messages },

View File

@@ -26,9 +26,9 @@ import { createCanvas, resizeImage, globalToLocal } from "@/utils/canvas-util";
import { renderCross, renderMasked } from "@/utils/render-util";
import { LAYER_GRAPHIC, LAYER_MASK, LAYER_TEXT } from "@/definitions/layer-types";
import {
isPointInRange, translatePointerRotation, getRectangleForSelection,
rotatePoints, rectangleToCoordinates
} from "@/utils/image-math";
isPointInRange, translatePointerRotation, rotatePoints, rectangleToCoordinates
} from "@/math/image-math";
import { getRectangleForSelection, isSelectionClosed } from "@/math/selection-math";
import { renderEffectsForLayer } from "@/services/render-service";
import { flushLayerCache, clearCacheProperty } from "@/services/caches/bitmap-cache";
import { getSpriteForLayer } from "@/factories/sprite-factory";
@@ -186,15 +186,9 @@ class LayerSprite extends sprite {
// drawable tools can work alongside an existing selection
const selection = activeLayer.selection;
if ( selection?.length > 2 && canDrawOnSelection( activeLayer )) {
const firstPoint = selection[ 0 ];
const lastPoint = selection[ selection.length - 1 ];
if ( firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y ) {
this._hasSelection = true;
this._selectionClosed = true;
} else {
this._hasSelection = false;
}
if ( isSelectionClosed( selection ) && canDrawOnSelection( activeLayer )) {
this._hasSelection = true;
this._selectionClosed = true;
} else {
this._hasSelection = false;
this.resetSelection();
@@ -465,7 +459,7 @@ class LayerSprite extends sprite {
documentContext.restore();
}
// render selection outline
if ( this._isSelectMode || this._hasSelection ) {
if (( this._isSelectMode || this._hasSelection ) && this.layer.selection ) {
documentContext.save();
documentContext.beginPath();
documentContext.lineWidth = 2 / this.canvas.zoomFactor;

View File

@@ -22,7 +22,7 @@
*/
import Vue from "vue";
import { canvas } from "zcanvas";
import { fastRound } from "@/utils/image-math";
import { fastRound } from "@/math/image-math";
class ZoomableCanvas extends canvas {
constructor( opts, store, rescaleFn ) {

View File

@@ -20,7 +20,7 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { constrain, isPortrait } from "@/utils/image-math";
import { constrain, isPortrait } from "@/math/image-math";
import { MAX_IMAGE_SIZE, MAX_MEGAPIXEL } from "@/definitions/image-types";
import { LAYER_GRAPHIC } from "@/definitions/layer-types";

View File

@@ -91,26 +91,6 @@ export const isPointInRange = ( point1x, point1y, point2x, point2y, margin = 5 )
return point1x >= left && point1x <= right && point1y >= top && point1y <= bottom;
};
export const getRectangleForSelection = selection => {
let minX = Infinity;
let minY = Infinity;
let maxX = 0;
let maxY = 0;
selection.forEach(({ x, y }) => {
minX = Math.min( minX, x );
maxX = Math.max( maxX, x );
minY = Math.min( minY, y );
maxY = Math.max( maxY, y );
});
return {
left : minX,
top : minY,
width : maxX - minX,
height : maxY - minY
};
};
export const translatePointerRotation = ( x, y, rotationCenterX, rotationCenterY, angleInRadians ) => {
const x2 = x - rotationCenterX;
const y2 = y - rotationCenterY;

View File

@@ -0,0 +1,52 @@
/**
* The MIT License (MIT)
*
* Igor Zinken 2021 - https://www.igorski.nl
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export const getRectangleForSelection = selection => {
let minX = Infinity;
let minY = Infinity;
let maxX = 0;
let maxY = 0;
selection.forEach(({ x, y }) => {
minX = Math.min( minX, x );
maxX = Math.max( maxX, x );
minY = Math.min( minY, y );
maxY = Math.max( maxY, y );
});
return {
left : minX,
top : minY,
width : maxX - minX,
height : maxY - minY
};
};
export const isSelectionClosed = selection => {
// smallest selection is four point polygon
if ( !selection || selection.length < 3 ) {
return false;
}
const firstPoint = selection[ 0 ];
const lastPoint = selection[ selection.length - 1 ];
return firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y;
};

View File

@@ -26,7 +26,7 @@ import {
CREATE_DOCUMENT, ADD_LAYER, EXPORT_DOCUMENT, DROPBOX_FILE_SELECTOR, SAVE_DROPBOX_DOCUMENT
} from "@/definitions/modal-windows";
import { getCanvasInstance, getSpriteForLayer } from "@/factories/sprite-factory";
import { translatePoints } from "@/utils/image-math";
import { translatePoints } from "@/math/image-math";
let state, getters, commit, dispatch, listener,
suspended = false, blockDefaults = true, optionDown = false, shiftDown = false;

View File

@@ -29,7 +29,8 @@ import { hasFilters, isEqual as isFiltersEqual } from "@/factories/filters-facto
import { isEqual as isTextEqual } from "@/factories/text-factory";
import { createCanvas, cloneCanvas, resizeToBase64 } from "@/utils/canvas-util";
import { replaceLayerSource } from "@/utils/layer-util";
import { fastRound, getRotatedSize, getRotationCenter, getRectangleForSelection } from "@/utils/image-math";
import { fastRound, getRotatedSize, getRotationCenter } from "@/math/image-math";
import { getRectangleForSelection } from "@/math/selection-math";
import { hasLayerCache, getLayerCache, setLayerCache } from "@/services/caches/bitmap-cache";
import { loadGoogleFont } from "@/services/font-service";
import FilterWorker from "@/workers/filter.worker";

View File

@@ -1,7 +1,6 @@
import {
fastRound, scaleToRatio, constrain, isPortrait, isLandscape, isSquare,
getRectangleForSelection, translatePoints,
} from "@/utils/image-math";
fastRound, scaleToRatio, constrain, isPortrait, isLandscape, isSquare, translatePoints,
} from "@/math/image-math";
describe( "Image math utilities", () => {
describe( "When rounding numbers", () => {
@@ -60,23 +59,6 @@ describe( "Image math utilities", () => {
});
});
describe( "when given a polygon selection", () => {
it( "should be able to calculate the bounding box of the selection", () => {
const selection = [
{ x: 100, y: 150 },
{ x: 50, y: 899 },
{ x: 50, y: 100 },
{ x: 101, y: 100 }
];
expect( getRectangleForSelection( selection )).toEqual({
left: 50,
top: 100,
width: 51,
height: 799
});
});
});
it( "should be able to translate the coordinates within a list", () => {
const list = [
{ x: 10, y: 10 }, { x: 15, y: 15 }

View File

@@ -0,0 +1,49 @@
import { getRectangleForSelection, isSelectionClosed } from "@/math/selection-math";
describe( "selection math", () => {
it( "should be able to calculate the bounding box of the selection", () => {
const selection = [
{ x: 100, y: 150 },
{ x: 50, y: 899 },
{ x: 50, y: 100 },
{ x: 101, y: 100 }
];
expect( getRectangleForSelection( selection )).toEqual({
left: 50,
top: 100,
width: 51,
height: 799
});
});
describe( "when determining whether a selection is closed", () => {
it( "should not consider a selection with less than four points closable", () => {
const selection = [{ x: 100, y: 150 }];
expect( isSelectionClosed( selection )).toBe( false );
selection.push({ x: 150, y: 150 });
expect( isSelectionClosed( selection )).toBe( false );
selection.push({ x: 100, y: 200 });
expect( isSelectionClosed( selection )).toBe( false );
});
it( "should not consider a selection where the first and last point are not at the same coordinate closed", () => {
const selection = [
{ x: 100, y: 150 },
{ x: 150, y: 150 },
{ x: 100, y: 200 },
{ x: 100, y: 160 }
];
expect( isSelectionClosed( selection )).toBe( false );
});
it( "should consider a selection where the first and last point are at the same coordinate closed", () => {
const selection = [
{ x: 100, y: 150 },
{ x: 150, y: 150 },
{ x: 100, y: 200 },
{ x: 100, y: 150 }
];
expect( isSelectionClosed( selection )).toBe( true );
});
});
});