import { wrapArray } from "../../utils/utilities"; import { Geometry } from "./Geometry"; /** * @type {import("./Geometry").BBox} */ const DEFAULT_BBOX = { x: 0, y: 0, width: 0, height: 0 }; /** * Provides an abstract boudnign box for any types of regions */ export class BoundingBox { options = {}; static bbox(region) { const bbox = _detect(region); return wrapArray(bbox).map((bbox) => Object.assign({ ...DEFAULT_BBOX }, bbox)); } /** * Contructor * * _source_ might be any object that provides its dimensions and position * * @param {{ * source: any, * getX: (any) => number, * getY: (any) => number, * getXWidth: (any) => number, * getHeight: (any) => number * }} options */ constructor(options) { Object.assign(this.options, options); } get _source() { return this.options.source; } get x() { return this.options.getX(this._source); } get y() { return this.options.getY(this._source); } get width() { return this.options.getWidth(this._source); } get height() { return this.options.getHeight(this._source); } } const stageRelatedBBox = (region, bbox) => { // If there is no stageRef we just wait for it in the next renders if (!region.parent?.stageRef) return null; const imageBbox = Geometry.getDOMBBox(region.parent.stageRef.content, true); const transformedBBox = Geometry.clampBBox( Geometry.modifyBBoxCoords(bbox, region.parent.zoomOriginalCoords), { x: 0, y: 0 }, { x: region.parent.canvasSize.width, y: region.parent.canvasSize.height }, ); return { ...transformedBBox, x: imageBbox.x + transformedBBox.x, y: imageBbox.y + transformedBBox.y, }; }; const _detect = (region) => { // that's a tricky way to detect bbox of exact result instead of whole region // works for global classifications and per-regions const isResult = !!region.from_name; if (isResult) { return Geometry.getDOMBBox(region.from_name.elementRef?.current); } switch (region.type) { case "textrange": case "richtextregion": case "textarearegion": case "paragraphs": case "timeseriesregion": { const regionBbox = Geometry.getDOMBBox(region.getRegionElement()); const container = region.parent?.mountNodeRef?.current; if (container?.tagName === "IFRAME") { const iframeBbox = Geometry.getDOMBBox(container, true); return ( regionBbox?.map((bbox) => ({ ...bbox, x: bbox.x + iframeBbox.x, y: bbox.y + iframeBbox.y, })) || null ); } return regionBbox; } case "audioregion": { const bbox = region.bboxCoordsCanvas; const stageEl = region.parent?.stageRef?.current; const stageBbox = Geometry.getDOMBBox(stageEl, true); return bbox ? stageBbox ? { x: stageBbox.x + bbox.left, y: stageBbox.y + bbox.top, width: bbox.right - bbox.left, height: bbox.bottom - bbox.top, } : bbox : DEFAULT_BBOX; } case "rectangleregion": case "ellipseregion": case "polygonregion": case "vectorregion": case "keypointregion": case "brushregion": case "bitmaskregion": { const bbox = region.bboxCoordsCanvas; return bbox ? stageRelatedBBox(region, { x: bbox.left, y: bbox.top, width: bbox.right - bbox.left, height: bbox.bottom - bbox.top, }) : DEFAULT_BBOX; } default: { console.warn(`Unknown region type: ${region.type}`); return { ...DEFAULT_BBOX }; } } };