mirror of
https://github.com/igorski/bitmappery.git
synced 2026-08-03 12:31:37 +02:00
Separated math utility into area specific functions. Decluttered LayerSprite selection logic
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
52
src/math/selection-math.js
Normal file
52
src/math/selection-math.js
Normal 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;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 }
|
||||
49
tests/unit/math/selection-math.spec.js
Normal file
49
tests/unit/math/selection-math.spec.js
Normal 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 );
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user