mirror of
https://github.com/igorski/bitmappery.git
synced 2026-06-17 03:34:56 +02:00
Round selections in pixel art mode for consistency and more clear draw operations
This commit is contained in:
@@ -24,6 +24,7 @@ import { sprite } from "zcanvas";
|
||||
import type { Point, Size, Viewport } from "zcanvas";
|
||||
import type { Document, Layer, Shape, Selection } from "@/definitions/document";
|
||||
import ToolTypes, { SELECTION_TOOLS } from "@/definitions/tool-types";
|
||||
import { isPixelArt } from "@/definitions/editor-properties";
|
||||
import { getRendererForLayer } from "@/factories/renderer-factory";
|
||||
import { isPointInRange, translatePoints, snapToAngle, rectToCoordinateList } from "@/math/point-math";
|
||||
import { rotateRectangleToCoordinates, scaleRectangle } from "@/math/rectangle-math";
|
||||
@@ -38,7 +39,7 @@ import { invertSelection } from "@/store/actions/selection-invert";
|
||||
import { applySelection } from "@/store/actions/selection-apply";
|
||||
import { getPixelRatio, isInsideTransparentArea } from "@/utils/canvas-util";
|
||||
import { createDocumentSnapshot, createLayerSnapshot } from "@/utils/document-util";
|
||||
import { getLastShape, syncSelection } from "@/utils/selection-util";
|
||||
import { getLastShape, roundSelection, syncSelection } from "@/utils/selection-util";
|
||||
import { isShapeClosed, isOverlappingShape, mergeShapes, rectangleToShape, subtractShapes } from "@/utils/shape-util";
|
||||
|
||||
export enum InteractionModes {
|
||||
@@ -237,6 +238,9 @@ export default class InteractionPane extends sprite {
|
||||
} else {
|
||||
selectionToSet = activeSelection; // no overlap handling necessary, we can commit the whole active selection to history
|
||||
}
|
||||
if ( isPixelArt( this.getActiveDocument() )) {
|
||||
selectionToSet = roundSelection( selectionToSet );
|
||||
}
|
||||
this._selectionClosed = true;
|
||||
this.canvas.store.commit( "setActiveSelection", [ ...selectionToSet ]);
|
||||
applySelection( getCanvasInstance().store, this.getActiveDocument(), currentSelection );
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Igor Zinken 2021-2025 - https://www.igorski.nl
|
||||
* Igor Zinken 2021-2026 - 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
|
||||
@@ -25,7 +25,7 @@ import { type Rectangle } from "zcanvas";
|
||||
import { type Shape, Selection } from "@/definitions/document";
|
||||
import { getRendererForLayer } from "@/factories/renderer-factory";
|
||||
import { type BitMapperyState } from "@/store";
|
||||
import { shapeToRectangle, scaleShape } from "@/utils/shape-util";
|
||||
import { shapeToRectangle, roundShape, scaleShape } from "@/utils/shape-util";
|
||||
|
||||
export const selectionToRectangle = ( selection: Selection ): Rectangle => {
|
||||
if ( selection.length === 1 ) {
|
||||
@@ -49,6 +49,10 @@ export const selectionToRectangle = ( selection: Selection ): Rectangle => {
|
||||
};
|
||||
};
|
||||
|
||||
export const roundSelection = ( selection: Selection ): Selection => {
|
||||
return selection.map(( shape: Shape ) => roundShape( shape ));
|
||||
};
|
||||
|
||||
export const scaleSelection = ( selection: Selection, scale: number ): Selection => {
|
||||
return selection.map(( shape: Shape ) => scaleShape( shape, scale ));
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
union
|
||||
} from "martinez-polygon-clipping";
|
||||
import type { Point, Rectangle } from "zcanvas";
|
||||
import { fastRound } from "@/math/unit-math";
|
||||
import type { Shape } from "@/definitions/document";
|
||||
|
||||
export const shapeToRectangle = ( shape: Shape ): Rectangle => {
|
||||
@@ -62,6 +63,10 @@ export const scaleShape = ( shape: Shape, scale: number ): Shape => {
|
||||
return shape.map(( point: Point ) => ({ x: point.x * scale, y: point.y * scale }));
|
||||
};
|
||||
|
||||
export const roundShape = ( shape: Shape ): Shape => {
|
||||
return shape.map(({ x, y }: Point ) => ({ x: fastRound( x ), y: fastRound( y ) }));
|
||||
};
|
||||
|
||||
export const isShapeRectangular = ( shape: Shape ): boolean => {
|
||||
if ( shape.length !== 5 ) {
|
||||
return false;
|
||||
|
||||
@@ -6,7 +6,7 @@ mockZCanvas();
|
||||
import DocumentFactory from "@/factories/document-factory";
|
||||
import LayerFactory from "@/factories/layer-factory";
|
||||
import { createRendererForLayer, flushRendererCache } from "@/factories/renderer-factory";
|
||||
import { getLastShape, scaleSelection, selectionToRectangle, syncSelection } from "@/utils/selection-util";
|
||||
import { getLastShape, roundSelection, scaleSelection, selectionToRectangle, syncSelection } from "@/utils/selection-util";
|
||||
|
||||
describe( "Selection utilities", () => {
|
||||
const selection = [
|
||||
@@ -47,6 +47,24 @@ describe( "Selection utilities", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it( "should be able to round the Shape coordinates inside a selection", () => {
|
||||
expect( roundSelection([
|
||||
[
|
||||
{ x: 4.25, y: 5.56 }, { x: 5.55, y: 5.56 }, { x: 5.55, y: 7.2 }, { x: 4.25, y: 5.56 },
|
||||
],
|
||||
[
|
||||
{ x: 2.25, y: 2.56 }, { x: 4.55, y: 5.56 }, { x: 4.55, y: 7.2 }, { x: 2.25, y: 5.56 },
|
||||
]
|
||||
])).toEqual([
|
||||
[
|
||||
{ x: 4, y: 6 }, { x: 6, y: 6 }, { x: 6, y: 7 }, { x: 4, y: 6 },
|
||||
],
|
||||
[
|
||||
{ x: 2, y: 3 }, { x: 5, y: 6 }, { x: 5, y: 7 }, { x: 2, y: 6 },
|
||||
]
|
||||
]);
|
||||
});
|
||||
|
||||
it( "should be able to scale the Shapes inside a selection", () => {
|
||||
expect( scaleSelection( selection, 1.5 )).toEqual([
|
||||
[
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { it, describe, expect } from "vitest";
|
||||
import {
|
||||
mergeShapes,
|
||||
rectangleToShape, scaleShape, shapeToRectangle, subtractShapes,
|
||||
rectangleToShape, roundShape, scaleShape, shapeToRectangle, subtractShapes,
|
||||
isOverlappingShape, isShapeRectangular, isShapeClosed
|
||||
} from "@/utils/shape-util";
|
||||
|
||||
@@ -31,6 +31,22 @@ describe( "Shape utilities", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it( "should be able to round the values in a Shape to their nearest integer values", () => {
|
||||
expect( roundShape([
|
||||
{ x: 4.250559227410698, y: 5.228564005399 },
|
||||
{ x: 6.846033445918115, y: 5.228564005399 },
|
||||
{ x: 6.846033445918115, y: 7.598344813601424 },
|
||||
{ x: 4.250559227410698, y: 7.598344813601424 },
|
||||
{ x: 4.250559227410698, y: 5.228564005399 }
|
||||
])).toEqual([
|
||||
{ x: 4, y: 5 },
|
||||
{ x: 7, y: 5 },
|
||||
{ x: 7, y: 8 },
|
||||
{ x: 4, y: 8 },
|
||||
{ x: 4, y: 5 },
|
||||
]);
|
||||
});
|
||||
|
||||
it( "should be able to scale a Shape by given factor", () => {
|
||||
const shape = [
|
||||
{ x: 100, y: 150 },
|
||||
|
||||
Reference in New Issue
Block a user