import Monologue from 'monologue.js'; import now from 'mout/src/time/now';
import CanvasOffscreenBuffer from '../../../Common/Misc/CanvasOffscreenBuffer';
const IMAGE_READY_TOPIC = 'image-ready'; const MODEL_CHANGE_TOPIC = 'model-change';
export default class MagicLensImageBuilder { constructor( frontImageBuilder, backImageBuilder, lensColor = '#ff0000', minZoom = 20, maxZoom = 0.5, lineWidth = 2 ) { this.frontImageBuilder = frontImageBuilder; this.backImageBuilder = backImageBuilder; this.frontEvent = null; this.backEvent = null; this.queryDataModel = this.frontImageBuilder.queryDataModel;
this.frontSubscription = this.frontImageBuilder.onImageReady( (data, envelope) => { this.frontEvent = data; this.draw(); } );
this.backSubscription = this.backImageBuilder.onImageReady( (data, envelope) => { this.backEvent = data; this.draw(); } );
const { dimensions } = frontImageBuilder.getControlModels(); this.width = dimensions[0]; this.height = dimensions[1];
this.frontActive = true;
this.minZoom = minZoom; this.maxZoom = Math.min(this.width, this.height) * maxZoom; this.lineWidth = lineWidth;
this.lensColor = lensColor; this.lensCenterX = this.width / 2; this.lensCenterY = this.height / 2; this.lensOriginalCenterX = this.lensCenterX; this.lensOriginalCenterY = this.lensCenterY; this.lensDragDX = 0; this.lensDragDY = 0; this.lensRadius = Math.floor(Math.min(this.width, this.height) / 5); this.lensOriginalRadius = this.lensRadius;
this.lastDragTime = now(); this.lastZoomTime = now(); this.newMouseTimeout = 250;
this.lensDrag = false; this.listenerDrag = false; this.lensZoom = false; this.listenerZoom = false;
this.bgCanvas = new CanvasOffscreenBuffer(this.width, this.height);
this.listener = { drag: (event, envelope) => { const time = now(); const newDrag = this.lastDragTime + this.newMouseTimeout < time; let eventManaged = false; const activeArea = event.activeArea; let xRatio = (event.relative.x - activeArea[0]) / activeArea[2]; let yRatio = (event.relative.y - activeArea[1]) / activeArea[3];
xRatio = xRatio < 0 ? 0 : xRatio > 1 ? 1 : xRatio; yRatio = yRatio < 0 ? 0 : yRatio > 1 ? 1 : yRatio;
const xPos = Math.floor(xRatio * this.width); const yPos = Math.floor(yRatio * this.height); const distFromLensCenter = (xPos - this.lensCenterX) ** 2 + (yPos - this.lensCenterY) ** 2;
if (newDrag) { this.lensZoom = false; this.listenerZoom = false; this.lensDrag = false; this.listenerDrag = false;
this.lensOriginalCenterX = this.lensCenterX; this.lensOriginalCenterY = this.lensCenterY;
this.lensDragDX = xPos - this.lensCenterX; this.lensDragDY = yPos - this.lensCenterY; }
if ( (this.lensDrag || distFromLensCenter < this.lensRadius ** 2) && event.modifier === 0 && !this.listenerDrag ) { eventManaged = true; this.lensDrag = true;
this.lensCenterX = xPos - this.lensDragDX; this.lensCenterY = yPos - this.lensDragDY;
this.lensCenterX = Math.max(this.lensCenterX, this.lensRadius); this.lensCenterY = Math.max(this.lensCenterY, this.lensRadius); this.lensCenterX = Math.min( this.lensCenterX, this.width - this.lensRadius ); this.lensCenterY = Math.min( this.lensCenterY, this.height - this.lensRadius );
this.draw(); }
const ibListener = this.frontImageBuilder.getListeners(); if (!eventManaged && ibListener && ibListener.drag) { this.listenerDrag = true; eventManaged = ibListener.drag(event, envelope); }
this.lastDragTime = time;
return eventManaged; }, zoom: (event, envelope) => { const time = now(); const newZoom = this.lastZoomTime + this.newMouseTimeout < time; let eventManaged = false; const activeArea = event.activeArea; let xRatio = (event.relative.x - activeArea[0]) / activeArea[2]; let yRatio = (event.relative.y - activeArea[1]) / activeArea[3];
if (newZoom) { this.lensZoom = false; this.listenerZoom = false; this.lensDrag = false; this.listenerDrag = false; }
xRatio = xRatio < 0 ? 0 : xRatio > 1 ? 1 : xRatio; yRatio = yRatio < 0 ? 0 : yRatio > 1 ? 1 : yRatio;
const xPos = Math.floor(xRatio * this.width); const yPos = Math.floor(yRatio * this.height); const distFromLensCenter = (xPos - this.lensCenterX) ** 2 + (yPos - this.lensCenterY) ** 2;
if ( (this.lensZoom || distFromLensCenter < this.lensRadius ** 2) && event.modifier === 0 && !this.listenerZoom ) { eventManaged = true; this.lensZoom = true;
if (event.isFirst) { this.lensOriginalRadius = this.lensRadius; } let zoom = this.lensOriginalRadius * event.scale;
if (zoom < this.minZoom) { zoom = this.minZoom; } if (zoom > this.maxZoom) { zoom = this.maxZoom; }
if (this.lensRadius !== zoom) { this.lensRadius = zoom; this.draw(); }
if (event.isFinal) { this.lensOriginalRadius = this.lensRadius; } }
const ibListener = this.frontImageBuilder.getListeners(); if (!eventManaged && ibListener && ibListener.zoom) { this.listenerZoom = true; eventManaged = ibListener.zoom(event, envelope); }
this.lastZoomTime = time;
return eventManaged; }, click: (event, envelope) => { this.lensDrag = false; this.listenerDrag = false; this.lensZoom = false; this.listenerZoom = false;
return false; }, }; }
draw() { if (!this.frontEvent || !this.backEvent) { return; }
const ctx = this.bgCanvas.get2DContext(); ctx.clearRect(0, 0, this.width, this.height);
ctx.drawImage( this.backEvent.canvas, this.backEvent.area[0], this.backEvent.area[1], this.backEvent.area[2], this.backEvent.area[3], 0, 0, this.width, this.height );
ctx.save();
ctx.beginPath(); ctx.arc( this.lensCenterX, this.lensCenterY, this.lensRadius, 0, 2 * Math.PI ); ctx.clip();
ctx.clearRect(0, 0, this.width, this.height);
ctx.drawImage( this.frontEvent.canvas, this.frontEvent.area[0], this.frontEvent.area[1], this.frontEvent.area[2], this.frontEvent.area[3], 0, 0, this.width, this.height );
ctx.restore();
ctx.beginPath(); ctx.lineWidth = this.lineWidth; ctx.strokeStyle = this.lensColor; ctx.arc( this.lensCenterX, this.lensCenterY, this.lensRadius, 0, 2 * Math.PI ); ctx.closePath(); ctx.stroke();
const readyImage = { canvas: this.bgCanvas.el, area: [0, 0, this.width, this.height], outputSize: [this.width, this.height], builder: this, arguments: this.frontEvent.arguments, };
this.emit(IMAGE_READY_TOPIC, readyImage); }
update() { this.frontImageBuilder.update(); this.backImageBuilder.update(); }
render() { this.frontImageBuilder.render(); this.backImageBuilder.render(); }
onImageReady(callback) { return this.on(IMAGE_READY_TOPIC, callback); }
onModelChange(callback) { return this.on(MODEL_CHANGE_TOPIC, callback); }
getListeners() { return this.listener; }
destroy() { this.off(); this.listener = null;
this.frontSubscription.unsubscribe(); this.frontSubscription = null;
this.backSubscription.unsubscribe(); this.backSubscription = null;
this.frontImageBuilder.destroy(); this.backImageBuilder.destroy(); }
getActiveImageBuilder() { return this.frontActive ? this.frontImageBuilder : this.backImageBuilder; }
isFront() { return this.frontActive; }
toggleLens() { this.frontActive = !this.frontActive; this.emit(MODEL_CHANGE_TOPIC); } }
Monologue.mixInto(MagicLensImageBuilder);
|