Add method for pixel-accurate content copying

This commit is contained in:
Igor Zinken
2025-02-08 15:20:37 +01:00
parent f737a8238a
commit 2c805b25f1
2 changed files with 63 additions and 1 deletions

View File

@@ -1,7 +1,7 @@
/**
* The MIT License (MIT)
*
* Igor Zinken 2020-2022 - https://www.igorski.nl
* Igor Zinken 2020-2025 - 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
@@ -51,6 +51,29 @@ export const isPointInRange = ( point1x: number, point1y: number, point2x: numbe
isCoordinateInVerticalRange( point1y, point2y, margin );
};
export const isPointInsidePolygon = ( point: Point, polygon: Point[] ): boolean => {
const { x, y } = point;
let isInside = false;
for ( let i = 0, l = polygon.length, j = polygon.length - 1; i < l; j = i++ ) {
const compare1 = polygon[ i ];
const compare2 = polygon[ j ];
const x1 = compare1.x;
const y1 = compare1.y;
const x2 = compare2.x;
const y2 = compare2.y;
const intersect = (( y1 > y ) !== ( y2 > y )) && ( x < ( x2 - x1 ) * ( y - y1 ) / ( y2 - y1 ) + x1 );
if ( intersect ) {
// we are ray-casting, an odd number of line crossing determines whether a point is inside the polygon.
// when even, the point is outside the polygon. We cannot directly return true here!
isInside = !isInside;
}
}
return isInside;
};
export const isCoordinateInHorizontalRange = ( point1x: number, point2x: number, margin = 5 ): boolean => {
const left = point2x - margin;
const right = point2x + margin;

View File

@@ -24,6 +24,8 @@ import { loader } from "zcanvas";
import type { Point } from "zcanvas";
import type { CanvasContextPairing, CanvasDrawable } from "@/definitions/editor";
import { JPEG, PNG } from "@/definitions/image-types";
import { isPointInsidePolygon } from "@/math/point-math";
import { fastRound } from "@/math/unit-math";
import type ZoomableCanvas from "@/rendering/canvas-elements/zoomable-canvas";
import { blobToResource, disposeResource } from "@/utils/resource-manager";
@@ -235,3 +237,40 @@ export const blobToCanvas = ( blob: Blob ): Promise<HTMLCanvasElement> => {
};
export const getPixelRatio = (): number => window.devicePixelRatio ?? 1;
/**
* Pixel-accurate copying of selection content. This is quite slow though, depending
* on the source content quality, using drawImage() (provides aliasing) is more than satisfactory.
*/
export const copySelectionCrisp = ( sourceCtx: CanvasRenderingContext2D, destCtx: CanvasRenderingContext2D, selection: Point[] ): void => {
// get bounding box of the selection
const minX = fastRound( Math.min( ...selection.map( point => point.x )));
const minY = fastRound( Math.min( ...selection.map( point => point.y )));
const maxX = fastRound( Math.max( ...selection.map( point => point.x )));
const maxY = fastRound( Math.max( ...selection.map( point => point.y )));
const width = maxX - minX;
const height = maxY - minY;
const sourceData = sourceCtx.getImageData( minX, minY, width, height ).data;
const outputImageData = destCtx.createImageData( width, height );
const outputData = outputImageData.data;
// Loop through each pixel in the bounding box
for ( let y = 0; y < height; y++ ) {
for ( let x = 0; x < width; x++ ) {
const pixelIndex = ( y * width + x ) * 4;
if ( isPointInsidePolygon({ x: minX + x, y: minY + y }, selection )) {
outputData[ pixelIndex ] = sourceData[ pixelIndex ];
outputData[ pixelIndex + 1 ] = sourceData[ pixelIndex + 1 ];
outputData[ pixelIndex + 2 ] = sourceData[ pixelIndex + 2 ];
outputData[ pixelIndex + 3 ] = sourceData[ pixelIndex + 3 ];
} else {
outputData[ pixelIndex + 3 ] = 0; // make transparent
}
}
}
destCtx.putImageData( outputImageData, minX, minY );
};