mirror of
https://github.com/igorski/bitmappery.git
synced 2026-07-26 08:47:43 +02:00
Address issue where stepping through state history for mask drawing is not restoring state correctly
This commit is contained in:
@@ -85,7 +85,7 @@ class LayerSprite extends ZoomableSprite {
|
||||
protected _invertSelection: boolean;
|
||||
protected _toolType: ToolTypes;
|
||||
protected _orgSourceToStore: string;
|
||||
protected _pendingPaintState: number;
|
||||
protected _pendingPaintState: number; // ReturnType<typeof setTimeout>;
|
||||
protected _rafFx: boolean;
|
||||
|
||||
constructor( layer: Layer ) {
|
||||
@@ -391,14 +391,14 @@ class LayerSprite extends ZoomableSprite {
|
||||
* not delay to history state UI from updating more than necessary.
|
||||
*/
|
||||
preparePendingPaintState(): void {
|
||||
canvasToBlob( this.layer.source ).then( blob => {
|
||||
canvasToBlob( this.getPaintSource() ).then( blob => {
|
||||
this._orgSourceToStore = blobToResource( blob );
|
||||
});
|
||||
this.debouncePaintStore();
|
||||
}
|
||||
|
||||
debouncePaintStore( timeout: number = 5000 ): void {
|
||||
this._pendingPaintState = window.setTimeout( this.storePaintState.bind( this ), timeout ) as unknown as number;
|
||||
this._pendingPaintState = window.setTimeout( this.storePaintState.bind( this ), timeout );
|
||||
}
|
||||
|
||||
getPaintSource(): HTMLCanvasElement {
|
||||
@@ -430,14 +430,15 @@ class LayerSprite extends ZoomableSprite {
|
||||
|
||||
this._orgSourceToStore = null;
|
||||
|
||||
const blob = await canvasToBlob( layer.source );
|
||||
const isMask = this.isMaskable();
|
||||
const blob = await canvasToBlob( this.getPaintSource() );
|
||||
const newState = blobToResource( blob );
|
||||
enqueueState( `spritePaint_${layer.id}`, {
|
||||
undo(): void {
|
||||
restorePaintFromHistory( layer, orgState );
|
||||
restorePaintFromHistory( layer, orgState, isMask );
|
||||
},
|
||||
redo(): void {
|
||||
restorePaintFromHistory( layer, newState);
|
||||
restorePaintFromHistory( layer, newState, isMask );
|
||||
},
|
||||
resources: [ orgState, newState ],
|
||||
});
|
||||
@@ -781,9 +782,10 @@ function positionSpriteFromHistory( layer: Layer, x: number, y: number ): void {
|
||||
}
|
||||
}
|
||||
|
||||
function restorePaintFromHistory( layer: Layer, state: string ): void {
|
||||
const ctx = layer.source.getContext( "2d" );
|
||||
ctx.clearRect( 0, 0, layer.source.width, layer.source.height );
|
||||
function restorePaintFromHistory( layer: Layer, state: string, isMask: boolean ): void {
|
||||
const source = isMask ? layer.mask : layer.source;
|
||||
const ctx = source.getContext( "2d" ) as CanvasRenderingContext2D;
|
||||
ctx.clearRect( 0, 0, source.width, source.height );
|
||||
const image = new Image();
|
||||
image.onload = () => {
|
||||
ctx.drawImage( image, 0, 0 );
|
||||
|
||||
36
src/rendering/masking.ts
Normal file
36
src/rendering/masking.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Igor Zinken 2021-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
|
||||
* 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 maskImage = (
|
||||
destinationContext: CanvasRenderingContext2D, image: HTMLCanvasElement, mask: HTMLCanvasElement,
|
||||
width: number, height: number, maskOffsetX = 0, maskOffsetY = 0
|
||||
): void => {
|
||||
destinationContext.clearRect( 0, 0, width, height );
|
||||
destinationContext.drawImage( image, 0, 0 );
|
||||
|
||||
destinationContext.save();
|
||||
|
||||
destinationContext.globalCompositeOperation = "destination-out";
|
||||
destinationContext.drawImage( mask, maskOffsetX, maskOffsetY );
|
||||
|
||||
destinationContext.restore();
|
||||
};
|
||||
@@ -31,6 +31,7 @@ import { replaceLayerSource } from "@/utils/layer-util";
|
||||
import { clone } from "@/utils/object-util";
|
||||
import { getLayerCache, setLayerCache } from "@/rendering/cache/bitmap-cache";
|
||||
import type { RenderCache } from "@/rendering/cache/bitmap-cache";
|
||||
import { maskImage } from "@/rendering/masking";
|
||||
import { renderMultiLineText } from "@/rendering/text";
|
||||
import { loadGoogleFont } from "@/services/font-service";
|
||||
import FilterWorker from "@/workers/filter.worker?worker";
|
||||
@@ -132,7 +133,7 @@ export const renderEffectsForLayer = async ( layer: Layer, useCaching = true ):
|
||||
|
||||
if ( applyMask ) {
|
||||
//console.info( "apply mask" );
|
||||
await renderMask( layer, ctx, applyFilter ? cloneCanvas( cvs ) : layer.source, width, height );
|
||||
renderMask( layer, ctx, applyFilter ? cloneCanvas( cvs ) : layer.source, width, height );
|
||||
}
|
||||
|
||||
// step 4. update cache and on-screen canvas contents
|
||||
@@ -222,20 +223,11 @@ const renderText = async ( layer: Layer ): Promise<HTMLCanvasElement> => {
|
||||
return cvs;
|
||||
};
|
||||
|
||||
const renderMask = async ( layer: Layer, ctx: CanvasRenderingContext2D, sourceBitmap: HTMLCanvasElement,
|
||||
width: number, height: number ): Promise<void> => {
|
||||
const renderMask = ( layer: Layer, ctx: CanvasRenderingContext2D, sourceBitmap: HTMLCanvasElement, width: number, height: number ): void => {
|
||||
if ( !layer.mask ) {
|
||||
return;
|
||||
}
|
||||
ctx.clearRect( 0, 0, width, height );
|
||||
ctx.drawImage( sourceBitmap, 0, 0 );
|
||||
|
||||
ctx.save();
|
||||
|
||||
// commenting out the next line allows you to debug by seeing the mask
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.drawImage( layer.mask, layer.maskX, layer.maskY );
|
||||
ctx.restore();
|
||||
maskImage( ctx, sourceBitmap, layer.mask, width, height, layer.maskX, layer.maskY );
|
||||
};
|
||||
|
||||
function getJobFromQueue( jobId: number ): RenderJob | undefined {
|
||||
|
||||
Reference in New Issue
Block a user