UNPKG

@inweb/markup

Version:
246 lines (199 loc) 8.89 kB
/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a // license agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// import Konva from "konva"; import { IMarkupImage, IMarkupImageParams } from "../IMarkupImage"; import { IWorldTransform } from "../IWorldTransform"; import { WorldTransform } from "../WorldTransform"; export class KonvaImage implements IMarkupImage { private _ref: Konva.Image; private _canvasImage: HTMLImageElement; private _ratio = 1; private readonly EPSILON = 10e-6; private readonly BASE64_HEADER_START = "data:image/"; private readonly BASE64_NOT_FOUND = "data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAADsAAAA7AF5KHG9AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAmhJREFUWIXtlr9rVEEQxz+H8RQUJIdeIopYm0vkCg0GBBtbG1NF7Kxt7dR/IGIw/uhTaBNLERURg2kCEUyCYCPi70b0InjGS57FzOZN3r19d+9HJIVfWO52dma/s7Mz8xa2KAaBCWAR+AkECWOmSOIdwC1gtQOpHc+NfQ8wClQ8+1d0vcdH/lQ3bSIRGAZ2pTjAqNovANXIWlXlAXA2zvi2Ln4AjqYgtagYEutENSLvjRoOImFv5iB32Ae8UrLXwFBk3h9ndF0VJnKSO9gTu3yKu5Z1LKnS8YIcABgw5Ks692JZFXcXRJ46Aq6kikCnHNi/mQ50WwVtfaIoBzL3gRk2drSscJ2wrc4VvUoe2wn/41/iBfoVLRnBGnDSY3AAKacy8AmYR+o7K1zCl6wgrgpOAc/MuhvfgMuk+1JGHQgSBcAloKXy78AjYBppJk5/noTulseBMZ23iD/piHFkEdgTQzKk+5wHjmHC3cmBg0BD5xcSTrFXyQPgIWFtDwMvab+2N8DpbhyY1v/3E8gdDgNfVX9SCVZ0/gW4B0wB71S2BpxLcuCM/jaQSHSDEeAX4VMuAG4gTzyHbcAVXXO6GxxwIX+vvxe7JHcYQ07nHqklj96UIW/YhSWzMKcep8VVtf8B1Dw6h4DfhB+sdbgn2R+gnoEc5NR3dZ+3QJ9H74HqXLPCGlJyTfI9y3YCs0owq3OLOpKkLeBI1HhSDT/mdKIPiUCARMTlQx34TMLjtww8IczmO8AJ/N/2JNSQXAiQ671JePePge0+wzJSQq4FFzlaenIvucUAkiQLhC/mLGNZ9xgn5s63BP4CCk0QDtm4BhoAAAAASUVORK5CYII="; private _worldTransformer: IWorldTransform; constructor(params: IMarkupImageParams, ref = null, worldTransformer = new WorldTransform()) { this._worldTransformer = worldTransformer; if (ref) { if (!ref.src || !ref.src.startsWith(this.BASE64_HEADER_START)) ref.src = this.BASE64_NOT_FOUND; if (ref.height() <= this.EPSILON) ref.height(32); if (ref.width() <= this.EPSILON) ref.width(32); this._ref = ref; this._canvasImage = ref.image(); this._ratio = this._ref.height() <= this.EPSILON || this._ref.width() <= this.EPSILON ? 1 : this._ref.height() / this._ref.width(); const wcsStart = this._ref.getAttr("wcsStart"); if (!wcsStart) { this._ref.setAttr("wcsStart", this._worldTransformer.screenToWorld({ x: ref.x(), y: ref.y() })); } return; } if (!params) params = {}; if (!params.position) params.position = { x: 0, y: 0 }; if (!params.src || !params.src.startsWith(this.BASE64_HEADER_START)) params.src = this.BASE64_NOT_FOUND; this._canvasImage = new Image(); this._canvasImage.onload = () => { this._ref.image(this._canvasImage); if (this._ref.height() <= this.EPSILON) this._ref.height(this._canvasImage.height); if (this._ref.width() <= this.EPSILON) this._ref.width(this._canvasImage.width); this._ratio = this._ref.height() <= this.EPSILON || this._ref.width() <= this.EPSILON ? 1 : this._ref.height() / this._ref.width(); // need to rescale only if input width and height are 0 - we do not loading Viewpoint with existing params if ( (params.width <= this.EPSILON || params.height <= this.EPSILON) && (params.maxWidth >= this.EPSILON || params.maxWidth >= this.EPSILON) ) { const heightOutOfCanvas = params.maxHeight - this._canvasImage.height; const widthOutOfCanvas = params.maxWidth - this._canvasImage.width; if (heightOutOfCanvas <= this.EPSILON || widthOutOfCanvas <= this.EPSILON) { if (widthOutOfCanvas <= this.EPSILON && widthOutOfCanvas < heightOutOfCanvas / this._ratio) { this._ref.height(params.maxWidth * this._ratio); this._ref.width(params.maxWidth); } else { this._ref.width(params.maxHeight / this._ratio); this._ref.height(params.maxHeight); } } } }; this._canvasImage.onerror = () => { this._canvasImage.onerror = function () {}; this._canvasImage.src = this.BASE64_NOT_FOUND; }; this._canvasImage.src = params.src; this._ref = new Konva.Image({ x: params.position.x, y: params.position.y, image: this._canvasImage, width: params.width ?? 0, height: params.height ?? 0, draggable: true, }); this._ref.setAttr("wcsStart", this._worldTransformer.screenToWorld({ x: params.position.x, y: params.position.y })); this._ref.on("transform", (e) => { const attrs = e.target.attrs; if (attrs.rotation !== this._ref.rotation()) this._ref.rotation(attrs.rotation); const scaleByX = Math.abs(attrs.scaleX - 1) > 10e-6; const scaleByY = Math.abs(attrs.scaleY - 1) > 10e-6; let newWidth = this._ref.width(); if (scaleByX) newWidth *= attrs.scaleX; let newHeight = this._ref.height(); if (scaleByY) newHeight *= attrs.scaleY; if (e.evt.ctrlKey || e.evt.shiftKey) { if (scaleByX) { this._ref.width(newWidth); this._ref.height(newWidth * this._ratio); } else { this._ref.width(newHeight / this._ratio); this._ref.height(newHeight); } } else { if (scaleByX) { this._ref.width(newWidth); } if (scaleByY) { this._ref.height(newHeight); } } this._ref.scale({ x: 1, y: 1 }); }); this._ref.on("transformend", (e) => { const absoluteTransform = this._ref.getStage().getAbsoluteTransform(); const position = absoluteTransform.point({ x: this._ref.x(), y: this._ref.y() }); this._ref.setAttr("wcsStart", this._worldTransformer.screenToWorld(position)); }); this._ref.on("dragend", (e) => { const absoluteTransform = this._ref.getStage().getAbsoluteTransform(); const position = absoluteTransform.point({ x: this._ref.x(), y: this._ref.y() }); this._ref.setAttr("wcsStart", this._worldTransformer.screenToWorld(position)); }); this._ref.id(this._ref._id.toString()); } getSrc(): string { return this._canvasImage.src; } setSrc(src: any) { this._canvasImage.src = src; } getWidth(): number { return this._ref.width(); } setWidth(w: number): void { this._ref.width(w); this._ref.height(w * this._ratio); } getHeight(): number { return this._ref.height(); } setHeight(h: number): void { this._ref.height(h); this._ref.width(h / this._ratio); } ref() { return this._ref; } id(): string { return this._ref.id(); } enableMouseEditing(value: boolean): void { this._ref.draggable(value); } type(): string { return "Image"; } getRotation(): number { return this._ref.rotation(); } setRotation(degrees: number): void { this._ref.rotation(degrees); } getZIndex(): number { return this._ref.zIndex(); } setZIndex(zIndex: number): void { this._ref.zIndex(zIndex); } delete(): void { this._ref.destroy(); this._ref = null; } getPosition(): { x: number; y: number } { return this._ref.getPosition(); } setPosition(x: number, y: number): void { this._ref.setPosition({ x, y }); this._ref.setAttr("wcsStart", this._worldTransformer.screenToWorld({ x, y })); } updateScreenCoordinates(): void { let screenPositionStart = this._worldTransformer.worldToScreen(this._ref.getAttr("wcsStart")); let invert = this._ref.getStage().getAbsoluteTransform().copy(); invert = invert.invert(); const positionStart = invert.point(screenPositionStart); this._ref.position({ x: positionStart.x, y: positionStart.y }); } }