mirror of
https://github.com/igorski/bitmappery.git
synced 2026-08-01 03:23:04 +02:00
Add method for pixel-accurate content copying
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 );
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user