Fixed issue where scaled Layers could be obscured depending on viewport size and pan position

This commit is contained in:
Igor Zinken
2021-01-31 17:31:18 +01:00
parent 929754db96
commit fdd206869d
2 changed files with 155 additions and 15 deletions

View File

@@ -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() {

View 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
}
};
};