{"mappings":"AAEA,UAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElC;IACE,EAAE,EAAE,EAAE,CAAC;IACP,EAAE,EAAE,EAAE,CAAC;IACP,EAAE,EAAE,EAAE,CAAC;IACP,EAAE,EAAE,EAAE,CAAC;CACR;AAED;IACE,CAAC,EAAE,EAAE,CAAC;IACN,CAAC,EAAE,EAAE,CAAC;IACN,CAAC,EAAE,EAAE,CAAC;IACN,CAAC,EAAE,EAAE,CAAC;CACP;AAED;IACE,CAAC,EAAE,EAAE,CAAC;CACP;AEhBD;IACE,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,uCAAuC;IACvC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+BAA+B;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6BAA6B;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,8BAA8B;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sDAAsD;IACtD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uEAAuE;IACvE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0BAA0B;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2CAA2C;IAC3C,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,gCAAgC;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iCAAiC;IACjC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;IACE,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sCAAsC;IACtC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,oDAAoD;IACpD,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;WA+BsB,cAAc;WAYpB,OAAO;gBAIT,MAAM,EAAE,iBAAiB,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,cAAc;IAgPtF,+BAA+B;IACxB,OAAO;IAwPP,UAAU,CAAC,OAAO,EAAE,MAAM,OAAO,GAAG,MAAM,WAAW,GAAG,MAAM,UAAU,GAAG,SAAS;IAMpF,UAAU,CAAC,OAAO,EAAE,MAAM,OAAO,GAAG,MAAM,WAAW,GAAG,MAAM,UAAU,GAAG,SAAS;IAK3F,+EAA+E;IACxE,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;IAwBzC,UAAU;IAIV,WAAW;IA+BX,YAAY;IAIZ,UAAU;CA+ClB","sources":["src/cropper/src/util.ts","src/cropper/src/edge-detector/edgedetector.ts","src/cropper/src/cropper/cropper.ts","src/cropper/cropper.ts"],"sourcesContent":[null,null,null,"import cv from \"@techstark/opencv-js/\";\nimport EdgeDetector from \"../edge-detector/edgedetector\";\nimport Util, { Corners, EdgeCenters, Line, Pt, ViewCenter } from \"../util\";\n\nexport interface CropperTheme {\n  /** Color used for background and margin */\n  backgroundColor?: string;\n  /** Color used for tinting the surroundings of the selected area */\n  surroundingOverlayColor?: string;\n  /** Radius of corner grabbers in rem */\n  cornerGrabberRadius?: number;\n  /** Color of corner grabbers */\n  cornerGrabberColor?: string;\n  /** Line thickness of edges in rem (0 to hide edges) */\n  edgeThickness?: number;\n  /** Color of edges */\n  edgeColor?: string;\n  /** Radius of edge grabbers in rem (0 to hide edge grabbers) */\n  edgeGrabberRadius?: number;\n  /** Color of edge grabbers */\n  edgeGrabberColor?: string;\n  /** Radius of center grabber in rem (0 to hide center grabber) */\n  centerGrabberRadius?: number;\n  /** Color of center grabber */\n  centerGrabberColor?: string;\n  /** Factor by which to expand grabbers when hovered */\n  grabberExpansionFactor?: number;\n  /** Factor for scaling grabber hitboxes (increase for touch devices) */\n  grabberHitboxFactor?: number;\n  /** Line thickness of crosslines in rem (0 to hide crosslines) */\n  crossLineThickness?: number;\n  /** Color of crosslines */\n  crossLineColor?: string;\n  /** Radius of zoom lens in rem (0 to hide zoom lens) */\n  zoomLensRadius?: number;\n  /** Margin between zoom lens and grabber in rem */\n  zoomLensMargin?: number;\n  /** Zoom factor of zoom lens */\n  zoomLensFactor?: number;\n  /** Border thickness of zoom lens in rem */\n  zoomLensBorderThickness?: number;\n  /** Border color of zoom lens */\n  zoomLensBorderColor?: string;\n  /** Overlay color of zoom lens */\n  zoomLensOverlayColor?: string;\n}\n\nexport interface CropperOptions {\n  /** Use edge detection for placing draggable corners */\n  useEdgeDetection?: boolean;\n  /** Theme to be used for Cropper UI */\n  theme?: CropperTheme;\n  /** Canvas element to show edge detection results */\n  debugCanvas?: HTMLCanvasElement;\n  /** Log debug information in console */\n  debugLogs?: boolean;\n  /** Initial placement draggable corners (overrides useEdgeDetection) */\n  corners?: Corners;\n  /** Initial amount of clockwise 90° rotations */\n  rotations?: number;\n}\n\nexport default class Cropper {\n  private readonly canvas: HTMLCanvasElement;\n  private readonly ctx: CanvasRenderingContext2D;\n  private readonly img: HTMLImageElement;\n  private readonly imgMat: cv.Mat;\n  private readonly options: CropperOptions;\n  /** Current width of image as displayed on canvas (accounting for rotation) */\n  private imgW: number;\n  /** Current height of image as displayed on canvas (accounting for rotation) */\n  private imgH: number;\n  /** Current position of corner grabbers relative to unrotated source image */\n  private corners: Corners | undefined;\n  /** Current position of edge grabbers relative to unrotated source image */\n  private edgeCenters: EdgeCenters | undefined;\n  /** Current position of center grabber relative to unrotated source image */\n  private viewCenter: ViewCenter | undefined;\n  /** Amount of pixels per rem */\n  private remPx: number;\n  /** Amount of clockwise 90° rotations */\n  private rotations = 0;\n  /** Grabber that is currently being dragged by the user */\n  private dragged: keyof Corners | keyof EdgeCenters | keyof ViewCenter | undefined;\n  /** Grabber that is currently being hovered by the user */\n  private hovered: keyof Corners | keyof EdgeCenters | keyof ViewCenter | undefined;\n\n  private onPointerDown: (event: PointerEvent) => void;\n  private onPointerMove: (event: PointerEvent) => void;\n  private onPointerUp: (event: PointerEvent) => void;\n  private onMouseMove: (event: MouseEvent) => void;\n  private onResize = () => this.adjustDimensions();\n\n  public static async initialization() {\n    return new Promise<void>((resolve) => {\n      if (cv.getBuildInformation) {\n        resolve();\n      } else {\n        cv.onRuntimeInitialized = () => {\n          resolve();\n        };\n      }\n    });\n  }\n\n  public static isReady() {\n    return !!cv.getBuildInformation;\n  }\n\n  constructor(canvas: HTMLCanvasElement, img: HTMLImageElement, options?: CropperOptions) {\n    const defaultOptions: CropperOptions = {\n      useEdgeDetection: true,\n      theme: {\n        backgroundColor: \"black\",\n        surroundingOverlayColor: \"rgba(0,0,0,0.333)\",\n        cornerGrabberRadius: 0.5,\n        cornerGrabberColor: \"white\",\n        edgeThickness: 0.2,\n        edgeColor: \"white\",\n        edgeGrabberRadius: options?.theme?.cornerGrabberRadius ? options.theme.cornerGrabberRadius * 0.8 : 0.4,\n        edgeGrabberColor: options?.theme?.cornerGrabberColor ?? \"white\",\n        centerGrabberRadius: options?.theme?.cornerGrabberRadius ? options.theme.cornerGrabberRadius * 1.2 : 0.6,\n        centerGrabberColor: options?.theme?.cornerGrabberColor ?? \"rgba(255,255,255,0.5)\",\n        grabberExpansionFactor: 1.2,\n        grabberHitboxFactor: 1.5,\n        crossLineThickness: options?.theme?.edgeThickness ?? 0.2,\n        crossLineColor: options?.theme?.edgeColor ?? \"rgba(255,255,255,0.5)\",\n        zoomLensRadius: options?.theme?.cornerGrabberRadius ? options.theme.cornerGrabberRadius * 8 : 4,\n        zoomLensMargin: options?.theme?.cornerGrabberRadius ? options.theme.cornerGrabberRadius * 2 : 1,\n        zoomLensFactor: 2,\n        zoomLensBorderThickness: options?.theme?.edgeThickness ? options?.theme?.edgeThickness * 0.5 : 0.1,\n        zoomLensBorderColor: \"black\",\n        zoomLensOverlayColor: options?.theme?.surroundingOverlayColor ?? \"rgba(0,0,0,0.333)\",\n      },\n    };\n\n    this.canvas = canvas;\n    this.ctx = canvas.getContext(\"2d\");\n    this.img = img;\n    this.imgMat = cv.imread(img);\n    this.options = {\n      useEdgeDetection: !options.corners && (options.useEdgeDetection ?? defaultOptions.useEdgeDetection),\n      theme: Object.assign(defaultOptions.theme, options?.theme),\n      debugCanvas: options.debugCanvas,\n      debugLogs: options.debugLogs,\n      corners: options.corners,\n      rotations: options.rotations,\n    };\n\n    if (this.options.rotations) {\n      this.rotations = Util.rotateVal(options.rotations, 0, 4);\n    }\n    this.imgW = this.rotations % 2 == 0 ? this.imgMat.cols : this.imgMat.rows;\n    this.imgH = this.rotations % 2 == 0 ? this.imgMat.rows : this.imgMat.cols;\n    this.canvas.width = this.imgW;\n    this.canvas.height = this.imgH;\n    this.adjustDimensions();\n\n    setTimeout(() => {\n      if (this.options.useEdgeDetection) {\n        if (this.options.debugLogs) console.time(\"edgedetection\");\n        this.corners = EdgeDetector.detect(this.imgMat, this.options.debugCanvas);\n        if (this.options.debugLogs) console.timeEnd(\"edgedetection\");\n      }\n      if (!this.corners) {\n        this.corners = this.options.corners ?? {\n          tl: [0, 0],\n          tr: [this.imgW, 0],\n          br: [this.imgW, this.imgH],\n          bl: [0, this.imgH],\n        };\n      }\n      this.updateCenters();\n      this.registerListeners();\n      this.render();\n    }, 1);\n  }\n\n  private registerListeners() {\n    let bounds: Pt | undefined;\n    let edgeDragProps:\n      | {\n          dragLine: Line;\n          edgeCenter: Pt;\n          oppositeEdgeCenter: Pt;\n          moveCorners: { key: keyof Corners; towardsKey: keyof Corners; deltaX: number; deltaY: number }[];\n          scalarMin: number;\n          scalarMax: number;\n        }\n      | undefined;\n    let centerDragProps:\n      | {\n          cornerOffsets: Corners;\n        }\n      | undefined;\n\n    this.onPointerDown = (event) => {\n      event.preventDefault();\n      event.stopPropagation();\n      this.setDragged(this.getGrabber([event.clientX, event.clientY]));\n      if (!this.dragged) return;\n\n      bounds = this.rotations % 2 == 0 ? [this.imgW, this.imgH] : [this.imgH, this.imgW];\n      if (this.dragged in this.edgeCenters) {\n        const edgeCenter = this.edgeCenters[this.dragged];\n        const oppositeEdgeCenter = this.edgeCenters[Util.rotateKey(this.edgeCenters, this.dragged, 2)];\n        const dragLine = Util.lineThrough(edgeCenter, oppositeEdgeCenter);\n\n        const cornerKeys = Object.keys(this.corners) as (keyof Corners)[];\n        const edgeCenterKeys = Object.keys(this.edgeCenters) as (keyof EdgeCenters)[];\n        const moveCorners = [];\n        let scalarMin = -Infinity;\n        let scalarMax = Infinity;\n\n        for (let i = 0; i < 2; i++) {\n          const edgeCenterKeyIndex = edgeCenterKeys.indexOf(this.dragged as keyof EdgeCenters);\n          const moveCornerKey = cornerKeys[Util.rotateVal(edgeCenterKeyIndex, i, cornerKeys.length)];\n          const towardsCornerKey = cornerKeys[Util.rotateVal(edgeCenterKeyIndex, -i - 1, cornerKeys.length)];\n          const moveCornerPt = this.corners[moveCornerKey];\n          const towardsCornerPt = this.corners[towardsCornerKey];\n          const deltaX = moveCornerPt[0] - towardsCornerPt[0];\n          const deltaY = moveCornerPt[1] - towardsCornerPt[1];\n\n          let scalarLeft = -towardsCornerPt[0] / deltaX;\n          let scalarRight = (bounds[0] - towardsCornerPt[0]) / deltaX;\n          let scalarTop = -towardsCornerPt[1] / deltaY;\n          let scalarBottom = (bounds[1] - towardsCornerPt[1]) / deltaY;\n\n          scalarMin = Math.max(scalarMin, ...[scalarLeft, scalarRight, scalarTop, scalarBottom].filter((s) => s < 0));\n          scalarMax = Math.min(scalarMax, ...[scalarLeft, scalarRight, scalarTop, scalarBottom].filter((s) => s > 1));\n\n          moveCorners[i] = {\n            key: moveCornerKey,\n            towardsKey: towardsCornerKey,\n            deltaX: deltaX,\n            deltaY: deltaY,\n          };\n        }\n\n        edgeDragProps = { dragLine, edgeCenter, oppositeEdgeCenter, moveCorners, scalarMin, scalarMax };\n      } else if (this.dragged in this.viewCenter) {\n        const cornerOffsets = Util.mapObj(this.corners, (c) => Util.ptDiff(this.viewCenter.c, c));\n\n        centerDragProps = { cornerOffsets };\n      }\n\n      window.addEventListener(\"pointermove\", this.onPointerMove);\n      window.addEventListener(\"pointerup\", this.onPointerUp);\n    };\n\n    let requestedAnimationFrame = false;\n    this.onPointerMove = (event) => {\n      event.preventDefault();\n      event.stopPropagation();\n      if (this.dragged && !requestedAnimationFrame) {\n        window.requestAnimationFrame(() => {\n          requestedAnimationFrame = false;\n          if (this.dragged in this.corners) {\n            this.corners[this.dragged] = Util.ptClipBounds(this.cl2imgPt([event.clientX, event.clientY]), bounds);\n          } else if (this.dragged in this.edgeCenters) {\n            const { dragLine, edgeCenter, oppositeEdgeCenter, moveCorners, scalarMin, scalarMax } = edgeDragProps;\n\n            const newPt = Util.closestPoint(dragLine, this.cl2imgPt([event.clientX, event.clientY]));\n            const newLenRelative = Math.min(\n              Math.max(Util.ptRelativeDistance(newPt, oppositeEdgeCenter, edgeCenter), scalarMin),\n              scalarMax\n            );\n\n            for (let moveCorner of moveCorners) {\n              const towardsCornerPt = this.corners[moveCorner.towardsKey];\n              this.corners[moveCorner.key] = [\n                towardsCornerPt[0] + moveCorner.deltaX * newLenRelative,\n                towardsCornerPt[1] + moveCorner.deltaY * newLenRelative,\n              ];\n            }\n          } else if (this.dragged in this.viewCenter) {\n            const offsetArray = Object.values(centerDragProps.cornerOffsets) as Pt[];\n\n            const viewCenter = Util.ptClipBoundsRect(this.cl2imgPt([event.clientX, event.clientY]), {\n              left: Math.max(...offsetArray.map((co) => -co[0])),\n              right: Math.min(...offsetArray.map((co) => bounds[0] - co[0])),\n              top: Math.max(...offsetArray.map((co) => -co[1])),\n              bottom: Math.min(...offsetArray.map((co) => bounds[1] - co[1])),\n            });\n            this.corners = Util.mapObj(centerDragProps.cornerOffsets, (c) => [\n              c[0] + viewCenter[0],\n              c[1] + viewCenter[1],\n            ]);\n          }\n          this.updateCenters();\n          this.render();\n        });\n        requestedAnimationFrame = true;\n      }\n    };\n\n    this.onPointerUp = (event) => {\n      event.preventDefault();\n      event.stopPropagation();\n      this.setDragged(undefined);\n      window.removeEventListener(\"pointermove\", this.onPointerMove);\n      window.removeEventListener(\"pointerup\", this.onPointerUp);\n      this.render();\n    };\n\n    this.onMouseMove = (event) => {\n      if (this.dragged) return;\n      event.preventDefault();\n      event.stopPropagation();\n      const prevHovered = this.hovered;\n      this.setHovered(this.getGrabber([event.clientX, event.clientY]));\n      if (this.hovered === prevHovered) return;\n      this.render();\n    };\n\n    this.canvas.addEventListener(\"pointerdown\", this.onPointerDown);\n    this.canvas.addEventListener(\"mousemove\", this.onMouseMove);\n    window.addEventListener(\"resize\", this.onResize);\n  }\n\n  private getGrabber(clientPt: Pt): keyof Corners | keyof EdgeCenters | keyof ViewCenter | undefined {\n    const cursorPt: Pt = this.cl2imgPt(clientPt);\n    const fac = this.remPx * this.options.theme.grabberHitboxFactor;\n    let radius: number;\n\n    radius = this.options.theme.cornerGrabberRadius * fac;\n    for (const key in this.corners) {\n      const corner = this.corners[key];\n      if (Util.withinRadius(cursorPt, corner, radius)) {\n        return key as keyof Corners;\n      }\n    }\n\n    radius = this.options.theme.edgeGrabberRadius * fac;\n    for (const key in this.edgeCenters) {\n      const corner = this.edgeCenters[key];\n      if (Util.withinRadius(cursorPt, corner, radius)) {\n        return key as keyof EdgeCenters;\n      }\n    }\n\n    radius = this.options.theme.centerGrabberRadius * fac;\n    if (Util.withinRadius(cursorPt, this.viewCenter.c, radius)) {\n      return \"c\";\n    }\n\n    return undefined;\n  }\n\n  /** Discards event listeners */\n  public discard() {\n    window.removeEventListener(\"resize\", this.onResize);\n    this.canvas.removeEventListener(\"pointerdown\", this.onPointerDown);\n    window.removeEventListener(\"pointermove\", this.onPointerMove);\n    window.removeEventListener(\"pointerup\", this.onPointerUp);\n  }\n\n  private adjustDimensions() {\n    const bounds = this.canvas.getBoundingClientRect();\n    const s = this.imgH / bounds.height;\n    this.remPx = parseFloat(getComputedStyle(document.documentElement).fontSize) * s;\n    this.canvas.width = this.imgW + this.options.theme.cornerGrabberRadius * this.remPx * 2;\n    this.canvas.height = this.imgH + this.options.theme.cornerGrabberRadius * this.remPx * 2;\n    this.render();\n  }\n\n  private render() {\n    this.ctx.fillStyle = this.options.theme.backgroundColor;\n    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);\n\n    //Draw image\n    let xOffset = 0;\n    let yOffset = 0;\n    if (this.rotations) {\n      this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2);\n      this.ctx.rotate(this.rotations * (Math.PI / 2));\n      xOffset = this.rotations % 2 == 0 ? -this.canvas.width / 2 : -this.canvas.height / 2;\n      yOffset = this.rotations % 2 == 0 ? -this.canvas.height / 2 : -this.canvas.width / 2;\n    }\n    this.ctx.drawImage(\n      this.img,\n      xOffset + this.options.theme.cornerGrabberRadius * this.remPx,\n      yOffset + this.options.theme.cornerGrabberRadius * this.remPx,\n      this.imgMat.cols,\n      this.imgMat.rows\n    );\n    if (this.rotations) {\n      this.ctx.rotate(-this.rotations * (Math.PI / 2));\n      this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2);\n    }\n\n    //Draw UI\n    if (this.corners) {\n      const cornerKeys = Object.keys(this.corners);\n      const edgeCenterKeys = Object.keys(this.edgeCenters);\n\n      //Draw surrounding overlay\n      if (this.options.theme.surroundingOverlayColor) {\n        this.ctx.beginPath();\n        this.ctx.fillStyle = this.options.theme.surroundingOverlayColor;\n\n        //Path around image\n        this.ctx.moveTo(...this.img2ctxPt([0, 0]));\n        this.ctx.lineTo(...this.img2ctxPt([this.imgMat.cols, 0]));\n        this.ctx.lineTo(...this.img2ctxPt([this.imgMat.cols, this.imgMat.rows]));\n        this.ctx.lineTo(...this.img2ctxPt([0, this.imgMat.rows]));\n        this.ctx.lineTo(...this.img2ctxPt([0, 0]));\n        this.ctx.closePath();\n\n        //Path around selected area\n        this.ctx.moveTo(...this.img2ctxPt(this.corners.tl));\n        this.ctx.lineTo(...this.img2ctxPt(this.corners.tr));\n        this.ctx.lineTo(...this.img2ctxPt(this.corners.br));\n        this.ctx.lineTo(...this.img2ctxPt(this.corners.bl));\n        this.ctx.lineTo(...this.img2ctxPt(this.corners.tl));\n        this.ctx.closePath();\n\n        this.ctx.fill(\"evenodd\");\n      }\n\n      //Draw edges\n      if (this.options.theme.edgeThickness) {\n        for (let i = 0; i < cornerKeys.length; i++) {\n          const cornerKey = cornerKeys[i];\n          const nextCornerKey = cornerKeys[(i + 1) % cornerKeys.length];\n          const corner = this.corners[cornerKey];\n          const nextCorner = this.corners[nextCornerKey];\n          const cornerPt = this.img2ctxPt(corner);\n          const nextCornerPt = this.img2ctxPt(nextCorner);\n\n          this.ctx.beginPath();\n          this.ctx.strokeStyle = this.options.theme.edgeColor;\n          this.ctx.lineWidth = this.options.theme.edgeThickness * this.remPx;\n          this.ctx.moveTo(...cornerPt);\n          this.ctx.lineTo(...nextCornerPt);\n          this.ctx.stroke();\n        }\n      }\n\n      //Draw crosslines\n      if (this.options.theme.crossLineThickness) {\n        for (const edgeCenterPair of [\n          [this.edgeCenters.t, this.edgeCenters.b],\n          [this.edgeCenters.l, this.edgeCenters.r],\n        ]) {\n          const edgeCenter0Pt = this.img2ctxPt(edgeCenterPair[0]);\n          const edgeCenter1Pt = this.img2ctxPt(edgeCenterPair[1]);\n          this.ctx.beginPath();\n          this.ctx.strokeStyle = this.options.theme.crossLineColor;\n          this.ctx.lineWidth = this.options.theme.crossLineThickness * this.remPx;\n          this.ctx.moveTo(...edgeCenter0Pt);\n          this.ctx.lineTo(...edgeCenter1Pt);\n          this.ctx.stroke();\n        }\n      }\n\n      //Draw grabbers\n      for (let i = 0; i < cornerKeys.length; i++) {\n        const cornerKey = cornerKeys[i];\n        const edgeCenterKey = edgeCenterKeys[i];\n        const corner = this.corners[cornerKey];\n        const edgeCenter = this.edgeCenters[edgeCenterKey];\n        const cornerPt = this.img2ctxPt(corner);\n        const edgeCenterPt = this.img2ctxPt(edgeCenter);\n\n        //Draw corner\n        const cornerRadiusFactor = this.hovered === cornerKey ? this.options.theme.grabberExpansionFactor : 1.0;\n        this.ctx.beginPath();\n        this.ctx.fillStyle = this.options.theme.cornerGrabberColor;\n        this.ctx.arc(\n          ...cornerPt,\n          this.options.theme.cornerGrabberRadius * this.remPx * cornerRadiusFactor,\n          0,\n          2 * Math.PI\n        );\n        this.ctx.fill();\n\n        //Draw edge grabber\n        if (this.options.theme.edgeGrabberRadius) {\n          const edgeRadiusFactor = this.hovered === edgeCenterKey ? this.options.theme.grabberExpansionFactor : 1.0;\n          this.ctx.beginPath();\n          this.ctx.fillStyle = this.options.theme.edgeGrabberColor;\n          this.ctx.arc(\n            ...edgeCenterPt,\n            this.options.theme.edgeGrabberRadius * this.remPx * edgeRadiusFactor,\n            0,\n            2 * Math.PI\n          );\n          this.ctx.fill();\n        }\n      }\n\n      //Draw center grabber\n      if (this.options.theme.centerGrabberRadius) {\n        const centerRadiusFactor = this.hovered === \"c\" ? this.options.theme.grabberExpansionFactor : 1.0;\n        const viewCenterPt = this.img2ctxPt(this.viewCenter.c);\n        this.ctx.beginPath();\n        this.ctx.fillStyle = this.options.theme.centerGrabberColor;\n        this.ctx.arc(\n          ...viewCenterPt,\n          this.options.theme.centerGrabberRadius * this.remPx * centerRadiusFactor,\n          0,\n          2 * Math.PI\n        );\n        this.ctx.fill();\n      }\n\n      //Draw zoom lens\n      if (this.options.theme.zoomLensRadius && this.dragged && !(this.dragged in this.viewCenter)) {\n        let draggedPt: Pt;\n        let lensOffset: [number, number];\n        let angleFrom: number;\n        let angleTo: number;\n\n        if (this.dragged in this.corners) {\n          draggedPt = this.corners[this.dragged];\n          lensOffset = { tl: [1, 1], tr: [-1, 1], br: [-1, -1], bl: [1, -1] }[this.dragged];\n\n          angleFrom = Util.angleBetween(\n            this.corners[this.dragged],\n            this.corners[Util.rotateKey(this.corners, this.dragged, -1)]\n          );\n          angleTo = Util.angleBetween(\n            this.corners[this.dragged],\n            this.corners[Util.rotateKey(this.corners, this.dragged, 1)]\n          );\n        } else if (this.dragged in this.edgeCenters) {\n          draggedPt = this.edgeCenters[this.dragged];\n          lensOffset = { t: [0, 1], r: [-1, 0], b: [0, -1], l: [1, 0] }[this.dragged];\n\n          const cornerKeys = Object.keys(this.corners) as (keyof Corners)[];\n          const edgeCenterKeys = Object.keys(this.edgeCenters) as (keyof EdgeCenters)[];\n          const edgeCenterKeyIndex = edgeCenterKeys.indexOf(this.dragged as keyof EdgeCenters);\n          const cornerFromKey = cornerKeys[Util.rotateVal(edgeCenterKeyIndex, 0, cornerKeys.length)];\n          angleFrom = Util.angleBetween(this.edgeCenters[this.dragged], this.corners[cornerFromKey]);\n          angleTo = angleFrom + Math.PI;\n        }\n        angleFrom += this.rotations * (Math.PI / 2);\n        angleTo += this.rotations * (Math.PI / 2);\n\n        let lensPt = this.img2ctxPt(draggedPt);\n        lensPt = [\n          lensPt[0] +\n            lensOffset[0] * (this.options.theme.zoomLensRadius + this.options.theme.zoomLensMargin) * this.remPx,\n          lensPt[1] +\n            lensOffset[1] * (this.options.theme.zoomLensRadius + this.options.theme.zoomLensMargin) * this.remPx,\n        ];\n\n        const lensRadius = this.options.theme.zoomLensRadius * this.remPx;\n        const magnifiedLensRadius = lensRadius / (this.options.theme.zoomLensFactor || 1);\n\n        //Draw zoom lens border\n        this.ctx.beginPath();\n        this.ctx.strokeStyle = this.options.theme.zoomLensBorderColor;\n        this.ctx.lineWidth = this.options.theme.zoomLensBorderThickness * this.remPx;\n        this.ctx.arc(...lensPt, lensRadius, 0, 2 * Math.PI);\n        this.ctx.stroke();\n\n        //Draw zoom lens img\n        this.ctx.save();\n        this.ctx.beginPath();\n        this.ctx.arc(...lensPt, this.options.theme.zoomLensRadius * this.remPx, 0, 2 * Math.PI);\n        this.ctx.clip();\n        this.ctx.fillStyle = this.options.theme.backgroundColor;\n        this.ctx.fillRect(lensPt[0] - lensRadius, lensPt[1] - lensRadius, lensRadius * 2, lensRadius * 2);\n        if (this.rotations) {\n          this.ctx.translate(lensPt[0], lensPt[1]);\n          this.ctx.rotate(this.rotations * (Math.PI / 2));\n          this.ctx.translate(-lensPt[0], -lensPt[1]);\n        }\n        this.ctx.drawImage(\n          this.img,\n          draggedPt[0] - magnifiedLensRadius,\n          draggedPt[1] - magnifiedLensRadius,\n          magnifiedLensRadius * 2,\n          magnifiedLensRadius * 2,\n          lensPt[0] - lensRadius,\n          lensPt[1] - lensRadius,\n          lensRadius * 2,\n          lensRadius * 2\n        );\n        this.ctx.restore();\n\n        //Draw crop overlay\n        this.ctx.beginPath();\n        this.ctx.fillStyle = this.options.theme.zoomLensOverlayColor;\n        this.ctx.moveTo(...lensPt);\n        this.ctx.lineTo(...Util.ptAtAngle(lensPt, angleFrom, lensRadius));\n        this.ctx.arc(...lensPt, lensRadius, angleFrom, angleTo);\n        this.ctx.lineTo(...lensPt);\n        this.ctx.fill();\n      }\n    } else {\n      this.ctx.fillStyle = \"rgba(0,0,0,0.5)\";\n      this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);\n    }\n  }\n\n  public setDragged(dragged: keyof Corners | keyof EdgeCenters | keyof ViewCenter | undefined) {\n    this.dragged = dragged;\n    this.canvas.style.cursor = dragged ? \"grabbing\" : \"default\";\n    this.setHovered(dragged);\n  }\n\n  public setHovered(hovered: keyof Corners | keyof EdgeCenters | keyof ViewCenter | undefined) {\n    this.hovered = hovered;\n    if (!this.dragged) this.canvas.style.cursor = hovered ? \"grab\" : \"default\";\n  }\n\n  /** Returns image cropped according to current position of draggable corners */\n  public getResult(type?: string, quality?: number) {\n    let dst = new cv.Mat();\n    let srcTri = cv.matFromArray(4, 1, cv.CV_32FC2, [\n      ...this.corners.tl,\n      ...this.corners.tr,\n      ...this.corners.bl,\n      ...this.corners.br,\n    ]); //Make new size from srcTri?\n    const w =\n      (Util.ptDistance(this.corners.tl, this.corners.tr) + Util.ptDistance(this.corners.bl, this.corners.br)) / 2;\n    const h =\n      (Util.ptDistance(this.corners.tl, this.corners.bl) + Util.ptDistance(this.corners.tr, this.corners.br)) / 2;\n    let dstTri = cv.matFromArray(4, 1, cv.CV_32FC2, [0, 0, w, 0, 0, h, w, h]);\n    let M = cv.getPerspectiveTransform(srcTri, dstTri);\n    cv.warpPerspective(this.imgMat, dst, M, new cv.Size(w, h), cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar());\n\n    // Convert to image\n    const image = new Image();\n    const dstCanvas = document.createElement(\"canvas\");\n    cv.imshow(dstCanvas, dst);\n    image.src = dstCanvas.toDataURL(type, quality);\n    return image;\n  }\n\n  public rotateLeft() {\n    this.rotate(\"left\");\n  }\n\n  public rotateRight() {\n    this.rotate(\"right\");\n  }\n\n  private rotate(dir: \"left\" | \"right\") {\n    const left = dir === \"left\";\n    const { tl, tr, br, bl } = this.corners;\n    this.corners = {\n      tl: left ? tr : bl,\n      tr: left ? br : tl,\n      br: left ? bl : tr,\n      bl: left ? tl : br,\n    };\n\n    this.rotations = Util.rotateVal(this.rotations, left ? -1 : 1, 4);\n\n    this.imgW = this.rotations % 2 == 0 ? this.imgMat.cols : this.imgMat.rows;\n    this.imgH = this.rotations % 2 == 0 ? this.imgMat.rows : this.imgMat.cols;\n\n    this.canvas.width = this.imgW;\n    this.canvas.height = this.imgH;\n\n    this.updateCenters();\n    this.adjustDimensions();\n  }\n\n  private updateCenters() {\n    this.edgeCenters = this.corners ? Util.edgeCenters(this.corners) : undefined;\n    this.viewCenter = this.corners ? { c: Util.cornerCenter(this.corners) } : undefined;\n  }\n\n  public getRotations() {\n    return this.rotations;\n  }\n\n  public getCorners() {\n    const { tl, tr, br, bl } = this.corners;\n    return {\n      tl: [tl[0], tl[1]],\n      tr: [tr[0], tr[1]],\n      br: [br[0], br[1]],\n      bl: [bl[0], bl[1]],\n    } as Corners;\n  }\n\n  /** Converts coordinates on unrotated source image to coordinates on ctx  */\n  private img2ctxPt(imgPt: Pt): Pt {\n    const cr = this.options.theme.cornerGrabberRadius;\n\n    if (this.rotations === 0) {\n      return [imgPt[0] + cr * this.remPx, imgPt[1] + cr * this.remPx];\n    } else if (this.rotations === 1) {\n      return [this.imgW - imgPt[1] + cr * this.remPx, imgPt[0] + cr * this.remPx];\n    } else if (this.rotations === 2) {\n      return [this.imgW - imgPt[0] + cr * this.remPx, this.imgH - imgPt[1] + cr * this.remPx];\n    } else if (this.rotations === 3) {\n      return [imgPt[1] + cr * this.remPx, this.imgH - imgPt[0] + cr * this.remPx];\n    }\n  }\n\n  /** Converts coordinates on ctx to coordinates on unrotated source image */\n  private ctx2imgPt(ctxPt: Pt): Pt {\n    const cr = this.options.theme.cornerGrabberRadius;\n\n    if (this.rotations === 0) {\n      return [ctxPt[0] - cr * this.remPx, ctxPt[1] - cr * this.remPx];\n    } else if (this.rotations === 1) {\n      return [ctxPt[1] - cr * this.remPx, this.imgW - ctxPt[0] + cr * this.remPx];\n    } else if (this.rotations === 2) {\n      return [this.imgW - ctxPt[0] + cr * this.remPx, this.imgH - ctxPt[1] + cr * this.remPx];\n    } else if (this.rotations === 3) {\n      return [this.imgH - ctxPt[1] + cr * this.remPx, ctxPt[0] - cr * this.remPx];\n    }\n  }\n\n  /** Converts cursor position on canvas to coordinates on unrotated source image */\n  private cl2imgPt(clPt: Pt): Pt {\n    const bounds = this.canvas.getBoundingClientRect();\n    const sX = this.canvas.width / bounds.width;\n    const sY = this.canvas.height / bounds.height;\n    return this.ctx2imgPt([(clPt[0] - bounds.left) * sX, (clPt[1] - bounds.top) * sY]);\n  }\n}\n"],"names":[],"version":3,"file":"intellicrop.d.ts.map"}