mirror of
https://github.com/igorski/bitmappery.git
synced 2026-07-15 03:18:46 +02:00
Fixed issue where scaled Layers could be obscured depending on viewport size and pan position
This commit is contained in:
@@ -21,12 +21,13 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import Vue from "vue";
|
||||
import { sprite } from "zcanvas"
|
||||
import ZoomableSprite from "./zoomable-sprite";
|
||||
import { createCanvas, canvasToBlob, resizeImage, globalToLocal } from "@/utils/canvas-util";
|
||||
import { renderCross } from "@/utils/render-util";
|
||||
import { blobToResource } from "@/utils/resource-manager";
|
||||
import { LAYER_GRAPHIC, LAYER_MASK, LAYER_TEXT } from "@/definitions/layer-types";
|
||||
import { getRectangleForSelection, isSelectionClosed } from "@/math/selection-math";
|
||||
import { scaleRectangle } from "@/math/image-math";
|
||||
import { translatePointerRotation } from "@/math/point-math";
|
||||
import { renderEffectsForLayer } from "@/services/render-service";
|
||||
import { clipContextToSelection } from "@/rendering/clipping";
|
||||
@@ -41,12 +42,14 @@ import { getSpriteForLayer } from "@/factories/sprite-factory";
|
||||
import { enqueueState } from "@/factories/history-state-factory";
|
||||
import ToolTypes, { canDrawOnSelection } from "@/definitions/tool-types";
|
||||
|
||||
const HALF = 0.5;
|
||||
|
||||
/**
|
||||
* A LayerSprite is the renderer for a Documents Layer.
|
||||
* It handles all tool interactions with the layer and also provides interaction with the Layers Mask.
|
||||
* It inherits from the zCanvas Sprite to be an interactive Canvas drawable.
|
||||
*/
|
||||
class LayerSprite extends sprite {
|
||||
class LayerSprite extends ZoomableSprite {
|
||||
constructor( layer ) {
|
||||
const { bitmap, x, y, width, height } = layer;
|
||||
super({ bitmap, x, y, width, height }); // zCanvas inheritance
|
||||
@@ -481,26 +484,46 @@ class LayerSprite extends sprite {
|
||||
}
|
||||
}
|
||||
|
||||
drawCropped( canvasContext, size ) {
|
||||
if ( !this.isScaled() ) {
|
||||
return super.drawCropped( canvasContext, size );
|
||||
}
|
||||
const scale = 1 / this.layer.effects.scale;
|
||||
const { src, dest } = size;
|
||||
canvasContext.drawImage(
|
||||
this._bitmap,
|
||||
( HALF + src.left * scale ) << 0,
|
||||
( HALF + src.top * scale ) << 0,
|
||||
( HALF + src.width * scale ) << 0,
|
||||
( HALF + src.height * scale ) << 0,
|
||||
( HALF + dest.left ) << 0,
|
||||
( HALF + dest.top ) << 0,
|
||||
( HALF + dest.width ) << 0,
|
||||
( HALF + dest.height ) << 0
|
||||
);
|
||||
}
|
||||
|
||||
draw( documentContext, viewport, omitOutlines = false ) {
|
||||
const scaleDocument = this.isScaled();
|
||||
let orgBounds;
|
||||
|
||||
// in case Layer has scale effect, apply it here (we don't resample the
|
||||
// actual Layer source to make this behaviour non-destructive, it's
|
||||
// merely a visualization and thus renderer affair)
|
||||
|
||||
if ( scaleDocument ) {
|
||||
if ( this.isScaled() ) {
|
||||
const { scale } = this.layer.effects;
|
||||
const { left, top, width, height } = this._bounds;
|
||||
const xTranslation = ( left + width * 0.5 ) - viewport.left;
|
||||
const yTranslation = ( top + height * 0.5 ) - viewport.top;
|
||||
documentContext.save();
|
||||
documentContext.translate( xTranslation, yTranslation );
|
||||
documentContext.scale( scale, scale );
|
||||
documentContext.translate( -xTranslation, -yTranslation );
|
||||
// we could scale the canvas context instead, but scaling the bounds means
|
||||
// that viewport pan logic will work "out of the box"
|
||||
orgBounds = this._bounds;
|
||||
this._bounds = scaleRectangle( orgBounds, scale );
|
||||
}
|
||||
// invoke base class behaviour to render bitmap
|
||||
super.draw( documentContext, viewport );
|
||||
|
||||
if ( orgBounds ) {
|
||||
this._bounds = orgBounds;
|
||||
}
|
||||
|
||||
// sprite is currently brushing, render low resolution temp contents onto screen
|
||||
if ( this.tempCanvas ) {
|
||||
documentContext.save();
|
||||
@@ -567,10 +590,6 @@ class LayerSprite extends sprite {
|
||||
documentContext.restore();
|
||||
}
|
||||
}
|
||||
|
||||
if ( scaleDocument ) {
|
||||
documentContext.restore();
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
121
src/rendering/canvas-elements/zoomable-sprite.js
Normal file
121
src/rendering/canvas-elements/zoomable-sprite.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import { sprite } from "zcanvas";
|
||||
|
||||
const { min } = Math;
|
||||
const HALF = 0.5;
|
||||
|
||||
class ZoomableSprite extends sprite {
|
||||
constructor( opts ) {
|
||||
super( opts );
|
||||
}
|
||||
|
||||
drawCropped( canvasContext, { src, dest }) {
|
||||
canvasContext.drawImage(
|
||||
this._bitmap,
|
||||
( HALF + src.left ) << 0,
|
||||
( HALF + src.top ) << 0,
|
||||
( HALF + src.width ) << 0,
|
||||
( HALF + src.height ) << 0,
|
||||
( HALF + dest.left ) << 0,
|
||||
( HALF + dest.top ) << 0,
|
||||
( HALF + dest.width ) << 0,
|
||||
( HALF + dest.height ) << 0
|
||||
);
|
||||
}
|
||||
|
||||
/* zCanvas overrides */
|
||||
|
||||
// ZoomableSprites dont function as masks, don't support tile sheets and have no children
|
||||
|
||||
draw( canvasContext, viewport = null ) {
|
||||
const bounds = this._bounds;
|
||||
let render = this._bitmapReady;
|
||||
if ( render && viewport ) {
|
||||
render = isInsideViewport( bounds, viewport );
|
||||
}
|
||||
if ( !render ) {
|
||||
return;
|
||||
}
|
||||
if ( viewport ) {
|
||||
this.drawCropped( canvasContext, calculateDrawRectangle( bounds, viewport ));
|
||||
} else {
|
||||
const { left, top, width, height } = bounds;
|
||||
canvasContext.drawImage(
|
||||
this._bitmap,
|
||||
( HALF + left ) << 0,
|
||||
( HALF + top ) << 0,
|
||||
( HALF + width ) << 0,
|
||||
( HALF + height ) << 0
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
export default ZoomableSprite;
|
||||
|
||||
/* internal methods */
|
||||
|
||||
export const isInsideViewport = ({ left, top, width, height }, viewport ) => {
|
||||
return ( left + width ) >= viewport.left && left <= viewport.right &&
|
||||
( top + height ) >= viewport.top && top <= viewport.bottom;
|
||||
};
|
||||
|
||||
/**
|
||||
* If the full zCanvas "document" is represented inside a smaller, pannable viewport
|
||||
* we can omit drawing a Sprites unseen pixels by calculating the visible area from both
|
||||
* the source drawable and destination canvas context.
|
||||
*/
|
||||
export const calculateDrawRectangle = ({ left, top, width, height }, viewport ) => {
|
||||
const {
|
||||
left : viewportX,
|
||||
top : viewportY,
|
||||
width : viewportWidth,
|
||||
height : viewportHeight
|
||||
} = viewport;
|
||||
|
||||
if ( left > viewportX ) {
|
||||
width = min( width, viewportWidth - ( left - viewportX ));
|
||||
} else {
|
||||
width = min( viewportWidth, width - ( viewportX - left ));
|
||||
}
|
||||
if ( top > viewportY ) {
|
||||
height = min( height, viewportHeight - ( top - viewportY ));
|
||||
} else {
|
||||
height = min( viewportHeight, height - ( viewportY - top ));
|
||||
}
|
||||
|
||||
return {
|
||||
src: {
|
||||
left : left > viewportX ? 0 : viewportX - left,
|
||||
top : top > viewportY ? 0 : viewportY - top,
|
||||
width,
|
||||
height
|
||||
},
|
||||
dest: {
|
||||
left : left > viewportX ? left - viewportX : 0,
|
||||
top : top > viewportY ? top - viewportY : 0,
|
||||
width,
|
||||
height
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user