UNPKG

@asoftwareworld/form-builder

Version:

ASW Form Builder helps you with rapid development and designed web forms which includes several controls. The key feature of Form Builder is to make your content attractive and effective. We can customize our control at run time and preview the same befor

1 lines 124 kB
{"version":3,"file":"asoftwareworld-form-builder-image-crop.mjs","sources":["../../src/components/image-crop/interfaces/cropper.settings.ts","../../src/components/image-crop/interfaces/move-start.interface.ts","../../src/components/image-crop/utils/keyboard.utils.ts","../../src/components/image-crop/utils/percentage.utils.ts","../../src/components/image-crop/utils/resize.utils.ts","../../src/components/image-crop/services/crop.service.ts","../../src/components/image-crop/services/cropper-position.service.ts","../../src/components/image-crop/utils/exif.utils.ts","../../src/components/image-crop/services/load-image.service.ts","../../src/components/image-crop/image-crop.component.ts","../../src/components/image-crop/image-crop.component.html","../../src/components/image-crop/image-crop.module.ts","../../src/components/image-crop/utils/blob.utils.ts","../../src/components/image-crop/public_api.ts","../../src/components/image-crop/asoftwareworld-form-builder-image-crop.ts"],"sourcesContent":["/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nimport { SimpleChanges } from '@angular/core';\r\nimport { CropperOptions, OutputFormat } from './cropper-options.interface';\r\nimport { ImageTransform } from './image-transform.interface';\r\n\r\nexport class CropperSettings {\r\n\r\n // From options\r\n format: OutputFormat = 'png';\r\n maintainAspectRatio = true;\r\n transform: ImageTransform = {};\r\n aspectRatio = 1;\r\n resizeToWidth = 0;\r\n resizeToHeight = 0;\r\n cropperMinWidth = 0;\r\n cropperMinHeight = 0;\r\n cropperMaxHeight = 0;\r\n cropperMaxWidth = 0;\r\n cropperStaticWidth = 0;\r\n cropperStaticHeight = 0;\r\n canvasRotation = 0;\r\n initialStepSize = 3;\r\n roundCropper = false;\r\n onlyScaleDown = false;\r\n imageQuality = 92;\r\n autoCrop = true;\r\n backgroundColor: string | null = null;\r\n containWithinAspectRatio = false;\r\n hideResizeSquares = false;\r\n alignImage: 'left' | 'center' = 'center';\r\n\r\n // Internal\r\n cropperScaledMinWidth = 20;\r\n cropperScaledMinHeight = 20;\r\n cropperScaledMaxWidth = 20;\r\n cropperScaledMaxHeight = 20;\r\n stepSize = this.initialStepSize;\r\n\r\n setOptions(options: Partial<CropperOptions>): void {\r\n Object.keys(options)\r\n .filter((k) => k in this)\r\n .forEach((k) => (this as any)[k] = (options as any)[k]);\r\n this.validateOptions();\r\n }\r\n\r\n setOptionsFromChanges(changes: SimpleChanges): void {\r\n Object.keys(changes)\r\n .filter((k) => k in this)\r\n .forEach((k) => (this as any)[k] = changes[k].currentValue);\r\n this.validateOptions();\r\n }\r\n\r\n private validateOptions(): void {\r\n if (this.maintainAspectRatio && !this.aspectRatio) {\r\n throw new Error('`aspectRatio` should > 0 when `maintainAspectRatio` is enabled');\r\n }\r\n }\r\n}\r\n","/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nexport interface MoveStart {\r\n active: boolean;\r\n type: MoveTypes | null;\r\n position: string | null;\r\n x1: number;\r\n y1: number;\r\n x2: number;\r\n y2: number;\r\n clientX: number;\r\n clientY: number;\r\n}\r\n\r\nexport enum MoveTypes {\r\n Move = 'move',\r\n Resize = 'resize',\r\n Pinch = 'pinch'\r\n}\r\n","/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nexport function getPositionForKey(key: string): string {\r\n switch (key) {\r\n case 'ArrowUp':\r\n return 'top';\r\n case 'ArrowRight':\r\n return 'right';\r\n case 'ArrowDown':\r\n return 'bottom';\r\n case 'ArrowLeft':\r\n default:\r\n return 'left';\r\n }\r\n}\r\n\r\nexport function getInvertedPositionForKey(key: string): string {\r\n switch (key) {\r\n case 'ArrowUp':\r\n return 'bottom';\r\n case 'ArrowRight':\r\n return 'left';\r\n case 'ArrowDown':\r\n return 'top';\r\n case 'ArrowLeft':\r\n default:\r\n return 'right';\r\n }\r\n}\r\n\r\nexport function getEventForKey(key: string, stepSize: number): any {\r\n switch (key) {\r\n case 'ArrowUp':\r\n return { clientX: 0, clientY: stepSize * -1 };\r\n case 'ArrowRight':\r\n return { clientX: stepSize, clientY: 0 };\r\n case 'ArrowDown':\r\n return { clientX: 0, clientY: stepSize };\r\n case 'ArrowLeft':\r\n default:\r\n return { clientX: stepSize * -1, clientY: 0 };\r\n }\r\n}\r\n","/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nexport function percentage(percent: number, totalValue: number): number {\r\n return (percent / 100) * totalValue;\r\n}\r\n","/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nexport function resizeCanvas(canvas: HTMLCanvasElement, width: number, height: number): void {\r\n const WIDTH_SOURCE = canvas.width;\r\n const HEIGHT_SOURCE = canvas.height;\r\n width = Math.round(width);\r\n height = Math.round(height);\r\n\r\n const RATIO_W = WIDTH_SOURCE / width;\r\n const RATIO_H = HEIGHT_SOURCE / height;\r\n const RATIO_W_HALF = Math.ceil(RATIO_W / 2);\r\n const RATIO_H_HALF = Math.ceil(RATIO_H / 2);\r\n\r\n const ctx = canvas.getContext('2d');\r\n if (ctx) {\r\n const img = ctx.getImageData(0, 0, WIDTH_SOURCE, HEIGHT_SOURCE);\r\n const img2 = ctx.createImageData(width, height);\r\n const data = img.data;\r\n const data2 = img2.data;\r\n\r\n for (let j = 0; j < height; j++) {\r\n for (let i = 0; i < width; i++) {\r\n const x2 = (i + j * width) * 4;\r\n const CENTER_Y = j * RATIO_H;\r\n let weight = 0;\r\n let weights = 0;\r\n let WEIGHTS_ALPHA = 0;\r\n let GX_R = 0;\r\n let GX_G = 0;\r\n let GX_B = 0;\r\n let GX_A = 0;\r\n\r\n const XX_START = Math.floor(i * RATIO_W);\r\n const YY_START = Math.floor(j * RATIO_H);\r\n let XX_STOP = Math.ceil((i + 1) * RATIO_W);\r\n let YY_STOP = Math.ceil((j + 1) * RATIO_H);\r\n XX_STOP = Math.min(XX_STOP, WIDTH_SOURCE);\r\n YY_STOP = Math.min(YY_STOP, HEIGHT_SOURCE);\r\n\r\n for (let yy = YY_START; yy < YY_STOP; yy++) {\r\n const dy = Math.abs(CENTER_Y - yy) / RATIO_H_HALF;\r\n const CENTER_X = i * RATIO_W;\r\n const w0 = dy * dy; // pre-calc part of w\r\n for (let xx = XX_START; xx < XX_STOP; xx++) {\r\n const dx = Math.abs(CENTER_X - xx) / RATIO_W_HALF;\r\n const w = Math.sqrt(w0 + dx * dx);\r\n if (w >= 1) {\r\n // pixel too far\r\n continue;\r\n }\r\n // hermite filter\r\n weight = 2 * w * w * w - 3 * w * w + 1;\r\n const POS_X = 4 * (xx + yy * WIDTH_SOURCE);\r\n // alpha\r\n GX_A += weight * data[POS_X + 3];\r\n WEIGHTS_ALPHA += weight;\r\n // colors\r\n if (data[POS_X + 3] < 255) {\r\n weight = weight * data[POS_X + 3] / 250;\r\n }\r\n GX_R += weight * data[POS_X];\r\n GX_G += weight * data[POS_X + 1];\r\n GX_B += weight * data[POS_X + 2];\r\n weights += weight;\r\n }\r\n }\r\n data2[x2] = GX_R / weights;\r\n data2[x2 + 1] = GX_G / weights;\r\n data2[x2 + 2] = GX_B / weights;\r\n data2[x2 + 3] = GX_A / WEIGHTS_ALPHA;\r\n }\r\n }\r\n\r\n\r\n canvas.width = width;\r\n canvas.height = height;\r\n\r\n // draw\r\n ctx.putImageData(img2, 0, 0);\r\n }\r\n}\r\n","/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nimport { ElementRef, Injectable } from '@angular/core';\r\nimport { CropperPosition, ImageCroppedEvent, LoadedImage } from '../interfaces';\r\nimport { CropperSettings } from '../interfaces/cropper.settings';\r\nimport { percentage } from '../utils/percentage.utils';\r\nimport { resizeCanvas } from '../utils/resize.utils';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class CropService {\r\n\r\n crop(sourceImage: ElementRef, loadedImage: LoadedImage, cropper: CropperPosition, settings: CropperSettings): ImageCroppedEvent | null {\r\n const imagePosition = this.getImagePosition(sourceImage, loadedImage, cropper, settings);\r\n const width = imagePosition.x2 - imagePosition.x1;\r\n const height = imagePosition.y2 - imagePosition.y1;\r\n const cropCanvas = document.createElement('canvas') as HTMLCanvasElement;\r\n cropCanvas.width = width;\r\n cropCanvas.height = height;\r\n\r\n const ctx = cropCanvas.getContext('2d');\r\n if (!ctx) {\r\n return null;\r\n }\r\n if (settings.backgroundColor != null) {\r\n ctx.fillStyle = settings.backgroundColor;\r\n ctx.fillRect(0, 0, width, height);\r\n }\r\n\r\n const scaleX = (settings.transform.scale || 1) * (settings.transform.flipH ? -1 : 1);\r\n const scaleY = (settings.transform.scale || 1) * (settings.transform.flipV ? -1 : 1);\r\n\r\n const transformedImage = loadedImage.transformed;\r\n ctx.setTransform(scaleX, 0, 0, scaleY, transformedImage.size.width / 2, transformedImage.size.height / 2);\r\n ctx.translate(-imagePosition.x1 / scaleX, -imagePosition.y1 / scaleY);\r\n ctx.rotate((settings.transform.rotate || 0) * Math.PI / 180);\r\n\r\n const translateH = settings.transform.translateH ? percentage(settings.transform.translateH, transformedImage.size.width) : 0;\r\n const translateV = settings.transform.translateV ? percentage(settings.transform.translateV, transformedImage.size.height) : 0;\r\n\r\n ctx.drawImage(\r\n transformedImage.image,\r\n translateH - transformedImage.size.width / 2,\r\n translateV - transformedImage.size.height / 2\r\n );\r\n\r\n const output: ImageCroppedEvent = {\r\n width, height,\r\n imagePosition,\r\n cropperPosition: { ...cropper }\r\n };\r\n if (settings.containWithinAspectRatio) {\r\n output.offsetImagePosition = this.getOffsetImagePosition(sourceImage, loadedImage, cropper, settings);\r\n }\r\n const resizeRatio = this.getResizeRatio(width, height, settings);\r\n if (resizeRatio !== 1) {\r\n output.width = Math.round(width * resizeRatio);\r\n output.height = settings.maintainAspectRatio\r\n ? Math.round(output.width / settings.aspectRatio)\r\n : Math.round(height * resizeRatio);\r\n resizeCanvas(cropCanvas, output.width, output.height);\r\n }\r\n output.base64 = cropCanvas.toDataURL('image/' + settings.format, this.getQuality(settings));\r\n return output;\r\n }\r\n\r\n private getImagePosition(\r\n sourceImage: ElementRef,\r\n loadedImage: LoadedImage,\r\n cropper: CropperPosition,\r\n settings: CropperSettings): CropperPosition {\r\n const sourceImageElement = sourceImage.nativeElement;\r\n const ratio = loadedImage.transformed.size.width / sourceImageElement.offsetWidth;\r\n\r\n const out: CropperPosition = {\r\n x1: Math.round(cropper.x1 * ratio),\r\n y1: Math.round(cropper.y1 * ratio),\r\n x2: Math.round(cropper.x2 * ratio),\r\n y2: Math.round(cropper.y2 * ratio)\r\n };\r\n\r\n if (!settings.containWithinAspectRatio) {\r\n out.x1 = Math.max(out.x1, 0);\r\n out.y1 = Math.max(out.y1, 0);\r\n out.x2 = Math.min(out.x2, loadedImage.transformed.size.width);\r\n out.y2 = Math.min(out.y2, loadedImage.transformed.size.height);\r\n }\r\n\r\n return out;\r\n }\r\n\r\n private getOffsetImagePosition(\r\n sourceImage: ElementRef,\r\n loadedImage: LoadedImage,\r\n cropper: CropperPosition,\r\n settings: CropperSettings): CropperPosition {\r\n const canvasRotation = settings.canvasRotation + loadedImage.exifTransform.rotate;\r\n const sourceImageElement = sourceImage.nativeElement;\r\n const ratio = loadedImage.transformed.size.width / sourceImageElement.offsetWidth;\r\n let offsetX: number;\r\n let offsetY: number;\r\n\r\n if (canvasRotation % 2) {\r\n offsetX = (loadedImage.transformed.size.width - loadedImage.original.size.height) / 2;\r\n offsetY = (loadedImage.transformed.size.height - loadedImage.original.size.width) / 2;\r\n } else {\r\n offsetX = (loadedImage.transformed.size.width - loadedImage.original.size.width) / 2;\r\n offsetY = (loadedImage.transformed.size.height - loadedImage.original.size.height) / 2;\r\n }\r\n\r\n const out: CropperPosition = {\r\n x1: Math.round(cropper.x1 * ratio) - offsetX,\r\n y1: Math.round(cropper.y1 * ratio) - offsetY,\r\n x2: Math.round(cropper.x2 * ratio) - offsetX,\r\n y2: Math.round(cropper.y2 * ratio) - offsetY\r\n };\r\n\r\n if (!settings.containWithinAspectRatio) {\r\n out.x1 = Math.max(out.x1, 0);\r\n out.y1 = Math.max(out.y1, 0);\r\n out.x2 = Math.min(out.x2, loadedImage.transformed.size.width);\r\n out.y2 = Math.min(out.y2, loadedImage.transformed.size.height);\r\n }\r\n\r\n return out;\r\n }\r\n\r\n getResizeRatio(width: number, height: number, settings: CropperSettings): number {\r\n const ratioWidth = settings.resizeToWidth / width;\r\n const ratioHeight = settings.resizeToHeight / height;\r\n const ratios = new Array<number>();\r\n\r\n if (settings.resizeToWidth > 0) {\r\n ratios.push(ratioWidth);\r\n }\r\n if (settings.resizeToHeight > 0) {\r\n ratios.push(ratioHeight);\r\n }\r\n\r\n const result = ratios.length === 0 ? 1 : Math.min(...ratios);\r\n\r\n if (result > 1 && !settings.onlyScaleDown) {\r\n return result;\r\n }\r\n return Math.min(result, 1);\r\n }\r\n\r\n getQuality(settings: CropperSettings): number {\r\n return Math.min(1, Math.max(0, settings.imageQuality / 100));\r\n }\r\n}\r\n","/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nimport { ElementRef, Injectable } from '@angular/core';\r\nimport { CropperPosition, Dimensions, MoveStart } from '../interfaces';\r\nimport { CropperSettings } from '../interfaces/cropper.settings';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class CropperPositionService {\r\n\r\n resetCropperPosition(sourceImage: ElementRef, cropperPosition: CropperPosition, settings: CropperSettings): void {\r\n if (!sourceImage?.nativeElement) {\r\n return;\r\n }\r\n const sourceImageElement = sourceImage.nativeElement;\r\n if (settings.cropperStaticHeight && settings.cropperStaticWidth) {\r\n cropperPosition.x1 = 0;\r\n cropperPosition.x2 = sourceImageElement.offsetWidth > settings.cropperStaticWidth ?\r\n settings.cropperStaticWidth : sourceImageElement.offsetWidth;\r\n cropperPosition.y1 = 0;\r\n cropperPosition.y2 = sourceImageElement.offsetHeight > settings.cropperStaticHeight ?\r\n settings.cropperStaticHeight : sourceImageElement.offsetHeight;\r\n } else {\r\n const cropperWidth = Math.min(settings.cropperScaledMaxWidth, sourceImageElement.offsetWidth);\r\n const cropperHeight = Math.min(settings.cropperScaledMaxHeight, sourceImageElement.offsetHeight);\r\n if (!settings.maintainAspectRatio) {\r\n cropperPosition.x1 = 0;\r\n cropperPosition.x2 = cropperWidth;\r\n cropperPosition.y1 = 0;\r\n cropperPosition.y2 = cropperHeight;\r\n } else if (sourceImageElement.offsetWidth / settings.aspectRatio < sourceImageElement.offsetHeight) {\r\n cropperPosition.x1 = 0;\r\n cropperPosition.x2 = cropperWidth;\r\n const cropperHeightWithAspectRatio = cropperWidth / settings.aspectRatio;\r\n cropperPosition.y1 = (sourceImageElement.offsetHeight - cropperHeightWithAspectRatio) / 2;\r\n cropperPosition.y2 = cropperPosition.y1 + cropperHeightWithAspectRatio;\r\n } else {\r\n cropperPosition.y1 = 0;\r\n cropperPosition.y2 = cropperHeight;\r\n const cropperWidthWithAspectRatio = cropperHeight * settings.aspectRatio;\r\n cropperPosition.x1 = (sourceImageElement.offsetWidth - cropperWidthWithAspectRatio) / 2;\r\n cropperPosition.x2 = cropperPosition.x1 + cropperWidthWithAspectRatio;\r\n }\r\n }\r\n }\r\n\r\n move(event: any, moveStart: MoveStart, cropperPosition: CropperPosition): void {\r\n const diffX = this.getClientX(event) - moveStart.clientX;\r\n const diffY = this.getClientY(event) - moveStart.clientY;\r\n\r\n cropperPosition.x1 = moveStart.x1 + diffX;\r\n cropperPosition.y1 = moveStart.y1 + diffY;\r\n cropperPosition.x2 = moveStart.x2 + diffX;\r\n cropperPosition.y2 = moveStart.y2 + diffY;\r\n }\r\n\r\n resize(event: any, moveStart: MoveStart, cropperPosition: CropperPosition, maxSize: Dimensions, settings: CropperSettings): void {\r\n const moveX = this.getClientX(event) - moveStart.clientX;\r\n const moveY = this.getClientY(event) - moveStart.clientY;\r\n switch (moveStart.position) {\r\n case 'left':\r\n cropperPosition.x1 = Math.min(Math.max(moveStart.x1 + moveX, cropperPosition.x2 - settings.cropperScaledMaxWidth),\r\n cropperPosition.x2 - settings.cropperScaledMinWidth);\r\n break;\r\n case 'topleft':\r\n cropperPosition.x1 = Math.min(Math.max(moveStart.x1 + moveX, cropperPosition.x2 - settings.cropperScaledMaxWidth),\r\n cropperPosition.x2 - settings.cropperScaledMinWidth);\r\n cropperPosition.y1 = Math.min(Math.max(moveStart.y1 + moveY, cropperPosition.y2 - settings.cropperScaledMaxHeight),\r\n cropperPosition.y2 - settings.cropperScaledMinHeight);\r\n break;\r\n case 'top':\r\n cropperPosition.y1 = Math.min(Math.max(moveStart.y1 + moveY, cropperPosition.y2 - settings.cropperScaledMaxHeight),\r\n cropperPosition.y2 - settings.cropperScaledMinHeight);\r\n break;\r\n case 'topright':\r\n cropperPosition.x2 = Math.max(Math.min(moveStart.x2 + moveX, cropperPosition.x1 + settings.cropperScaledMaxWidth),\r\n cropperPosition.x1 + settings.cropperScaledMinWidth);\r\n cropperPosition.y1 = Math.min(Math.max(moveStart.y1 + moveY, cropperPosition.y2 - settings.cropperScaledMaxHeight),\r\n cropperPosition.y2 - settings.cropperScaledMinHeight);\r\n break;\r\n case 'right':\r\n cropperPosition.x2 = Math.max(Math.min(moveStart.x2 + moveX, cropperPosition.x1 + settings.cropperScaledMaxWidth),\r\n cropperPosition.x1 + settings.cropperScaledMinWidth);\r\n break;\r\n case 'bottomright':\r\n cropperPosition.x2 = Math.max(Math.min(moveStart.x2 + moveX, cropperPosition.x1 + settings.cropperScaledMaxWidth),\r\n cropperPosition.x1 + settings.cropperScaledMinWidth);\r\n cropperPosition.y2 = Math.max(Math.min(moveStart.y2 + moveY, cropperPosition.y1 + settings.cropperScaledMaxHeight),\r\n cropperPosition.y1 + settings.cropperScaledMinHeight);\r\n break;\r\n case 'bottom':\r\n cropperPosition.y2 = Math.max(Math.min(moveStart.y2 + moveY, cropperPosition.y1 + settings.cropperScaledMaxHeight),\r\n cropperPosition.y1 + settings.cropperScaledMinHeight);\r\n break;\r\n case 'bottomleft':\r\n cropperPosition.x1 = Math.min(Math.max(moveStart.x1 + moveX, cropperPosition.x2 - settings.cropperScaledMaxWidth),\r\n cropperPosition.x2 - settings.cropperScaledMinWidth);\r\n cropperPosition.y2 = Math.max(Math.min(moveStart.y2 + moveY, cropperPosition.y1 + settings.cropperScaledMaxHeight),\r\n cropperPosition.y1 + settings.cropperScaledMinHeight);\r\n break;\r\n case 'center':\r\n const scale = event.scale;\r\n const newWidth = Math.min(\r\n Math.max(settings.cropperScaledMinWidth, (Math.abs(moveStart.x2 - moveStart.x1)) * scale),\r\n settings.cropperScaledMaxWidth);\r\n const newHeight = Math.min(\r\n Math.max(settings.cropperScaledMinHeight, (Math.abs(moveStart.y2 - moveStart.y1)) * scale),\r\n settings.cropperScaledMaxHeight);\r\n cropperPosition.x1 = moveStart.clientX - newWidth / 2;\r\n cropperPosition.x2 = moveStart.clientX + newWidth / 2;\r\n cropperPosition.y1 = moveStart.clientY - newHeight / 2;\r\n cropperPosition.y2 = moveStart.clientY + newHeight / 2;\r\n if (cropperPosition.x1 < 0) {\r\n cropperPosition.x2 -= cropperPosition.x1;\r\n cropperPosition.x1 = 0;\r\n } else if (cropperPosition.x2 > maxSize.width) {\r\n cropperPosition.x1 -= (cropperPosition.x2 - maxSize.width);\r\n cropperPosition.x2 = maxSize.width;\r\n }\r\n if (cropperPosition.y1 < 0) {\r\n cropperPosition.y2 -= cropperPosition.y1;\r\n cropperPosition.y1 = 0;\r\n } else if (cropperPosition.y2 > maxSize.height) {\r\n cropperPosition.y1 -= (cropperPosition.y2 - maxSize.height);\r\n cropperPosition.y2 = maxSize.height;\r\n }\r\n break;\r\n }\r\n\r\n if (settings.maintainAspectRatio) {\r\n // tslint:disable-next-line:no-non-null-assertion\r\n this.checkAspectRatio(moveStart.position!, cropperPosition, maxSize, settings);\r\n }\r\n }\r\n\r\n checkAspectRatio(position: string, cropperPosition: CropperPosition, maxSize: Dimensions, settings: CropperSettings): void {\r\n let overflowX = 0;\r\n let overflowY = 0;\r\n\r\n switch (position) {\r\n case 'top':\r\n cropperPosition.x2 = cropperPosition.x1 + (cropperPosition.y2 - cropperPosition.y1) * settings.aspectRatio;\r\n overflowX = Math.max(cropperPosition.x2 - maxSize.width, 0);\r\n overflowY = Math.max(0 - cropperPosition.y1, 0);\r\n if (overflowX > 0 || overflowY > 0) {\r\n cropperPosition.x2 -= (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX;\r\n cropperPosition.y1 += (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio;\r\n }\r\n break;\r\n case 'bottom':\r\n cropperPosition.x2 = cropperPosition.x1 + (cropperPosition.y2 - cropperPosition.y1) * settings.aspectRatio;\r\n overflowX = Math.max(cropperPosition.x2 - maxSize.width, 0);\r\n overflowY = Math.max(cropperPosition.y2 - maxSize.height, 0);\r\n if (overflowX > 0 || overflowY > 0) {\r\n cropperPosition.x2 -= (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX;\r\n cropperPosition.y2 -= (overflowY * settings.aspectRatio) > overflowX ? overflowY : (overflowX / settings.aspectRatio);\r\n }\r\n break;\r\n case 'topleft':\r\n cropperPosition.y1 = cropperPosition.y2 - (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio;\r\n overflowX = Math.max(0 - cropperPosition.x1, 0);\r\n overflowY = Math.max(0 - cropperPosition.y1, 0);\r\n if (overflowX > 0 || overflowY > 0) {\r\n cropperPosition.x1 += (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX;\r\n cropperPosition.y1 += (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio;\r\n }\r\n break;\r\n case 'topright':\r\n cropperPosition.y1 = cropperPosition.y2 - (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio;\r\n overflowX = Math.max(cropperPosition.x2 - maxSize.width, 0);\r\n overflowY = Math.max(0 - cropperPosition.y1, 0);\r\n if (overflowX > 0 || overflowY > 0) {\r\n cropperPosition.x2 -= (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX;\r\n cropperPosition.y1 += (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio;\r\n }\r\n break;\r\n case 'right':\r\n case 'bottomright':\r\n cropperPosition.y2 = cropperPosition.y1 + (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio;\r\n overflowX = Math.max(cropperPosition.x2 - maxSize.width, 0);\r\n overflowY = Math.max(cropperPosition.y2 - maxSize.height, 0);\r\n if (overflowX > 0 || overflowY > 0) {\r\n cropperPosition.x2 -= (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX;\r\n cropperPosition.y2 -= (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio;\r\n }\r\n break;\r\n case 'left':\r\n case 'bottomleft':\r\n cropperPosition.y2 = cropperPosition.y1 + (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio;\r\n overflowX = Math.max(0 - cropperPosition.x1, 0);\r\n overflowY = Math.max(cropperPosition.y2 - maxSize.height, 0);\r\n if (overflowX > 0 || overflowY > 0) {\r\n cropperPosition.x1 += (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX;\r\n cropperPosition.y2 -= (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio;\r\n }\r\n break;\r\n case 'center':\r\n cropperPosition.x2 = cropperPosition.x1 + (cropperPosition.y2 - cropperPosition.y1) * settings.aspectRatio;\r\n cropperPosition.y2 = cropperPosition.y1 + (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio;\r\n const overflowX1 = Math.max(0 - cropperPosition.x1, 0);\r\n const overflowX2 = Math.max(cropperPosition.x2 - maxSize.width, 0);\r\n const overflowY1 = Math.max(cropperPosition.y2 - maxSize.height, 0);\r\n const overflowY2 = Math.max(0 - cropperPosition.y1, 0);\r\n if (overflowX1 > 0 || overflowX2 > 0 || overflowY1 > 0 || overflowY2 > 0) {\r\n cropperPosition.x1 += (overflowY1 * settings.aspectRatio) > overflowX1\r\n ? (overflowY1 * settings.aspectRatio) : overflowX1;\r\n cropperPosition.x2 -= (overflowY2 * settings.aspectRatio) > overflowX2\r\n ? (overflowY2 * settings.aspectRatio) : overflowX2;\r\n cropperPosition.y1 += (overflowY2 * settings.aspectRatio) > overflowX2\r\n ? overflowY2 : overflowX2 / settings.aspectRatio;\r\n cropperPosition.y2 -= (overflowY1 * settings.aspectRatio) > overflowX1\r\n ? overflowY1 : overflowX1 / settings.aspectRatio;\r\n }\r\n break;\r\n }\r\n }\r\n\r\n getClientX(event: any): number {\r\n return event.touches?.[0].clientX || event.clientX || 0;\r\n }\r\n\r\n getClientY(event: any): number {\r\n return event.touches?.[0].clientY || event.clientY || 0;\r\n }\r\n}\r\n","/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nimport { ExifTransform } from '../interfaces/exif-transform.interface';\r\n\r\n// Black 2x1 JPEG, with the following meta information set:\r\n// - EXIF Orientation: 6 (Rotated 90° CCW)\r\n// Source: https://github.com/blueimp/JavaScript-Load-Image\r\nconst testAutoOrientationImageURL =\r\n 'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' +\r\n 'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' +\r\n 'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' +\r\n 'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' +\r\n 'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' +\r\n 'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==';\r\n\r\nexport function supportsAutomaticRotation(): Promise<boolean> {\r\n return new Promise((resolve) => {\r\n const img = new Image();\r\n img.onload = () => {\r\n // Check if browser supports automatic image orientation:\r\n const supported = img.width === 1 && img.height === 2;\r\n resolve(supported);\r\n };\r\n img.src = testAutoOrientationImageURL;\r\n });\r\n}\r\n\r\nexport function getTransformationsFromExifData(exifRotationOrBase64Image: number | string): ExifTransform {\r\n if (typeof exifRotationOrBase64Image === 'string') {\r\n exifRotationOrBase64Image = getExifRotation(exifRotationOrBase64Image);\r\n }\r\n switch (exifRotationOrBase64Image) {\r\n case 2:\r\n return { rotate: 0, flip: true };\r\n case 3:\r\n return { rotate: 2, flip: false };\r\n case 4:\r\n return { rotate: 2, flip: true };\r\n case 5:\r\n return { rotate: 1, flip: true };\r\n case 6:\r\n return { rotate: 1, flip: false };\r\n case 7:\r\n return { rotate: 3, flip: true };\r\n case 8:\r\n return { rotate: 3, flip: false };\r\n default:\r\n return { rotate: 0, flip: false };\r\n }\r\n}\r\n\r\nfunction getExifRotation(imageBase64: string): number {\r\n const view = new DataView(base64ToArrayBuffer(imageBase64));\r\n if (view.getUint16(0, false) !== 0xFFD8) {\r\n return -2;\r\n }\r\n const length = view.byteLength;\r\n let offset = 2;\r\n while (offset < length) {\r\n if (view.getUint16(offset + 2, false) <= 8) { return -1; }\r\n const marker = view.getUint16(offset, false);\r\n offset += 2;\r\n if (marker === 0xFFE1) {\r\n if (view.getUint32(offset += 2, false) !== 0x45786966) {\r\n return -1;\r\n }\r\n\r\n const little = view.getUint16(offset += 6, false) === 0x4949;\r\n offset += view.getUint32(offset + 4, little);\r\n const tags = view.getUint16(offset, little);\r\n offset += 2;\r\n for (let i = 0; i < tags; i++) {\r\n if (view.getUint16(offset + (i * 12), little) === 0x0112) {\r\n return view.getUint16(offset + (i * 12) + 8, little);\r\n }\r\n }\r\n // tslint:disable-next-line:no-bitwise\r\n } else if ((marker & 0xFF00) !== 0xFF00) {\r\n break;\r\n } else {\r\n offset += view.getUint16(offset, false);\r\n }\r\n }\r\n return -1;\r\n}\r\n\r\nfunction base64ToArrayBuffer(imageBase64: string): ArrayBufferLike {\r\n imageBase64 = imageBase64.replace(/^data\\:([^\\;]+)\\;base64,/gmi, '');\r\n const binaryString = atob(imageBase64);\r\n const len = binaryString.length;\r\n const bytes = new Uint8Array(len);\r\n for (let i = 0; i < len; i++) {\r\n bytes[i] = binaryString.charCodeAt(i);\r\n }\r\n return bytes.buffer;\r\n}\r\n","/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nimport { Injectable } from '@angular/core';\r\nimport { Dimensions, LoadedImage } from '../interfaces';\r\nimport { CropperSettings } from '../interfaces/cropper.settings';\r\nimport { ExifTransform } from '../interfaces/exif-transform.interface';\r\nimport { getTransformationsFromExifData, supportsAutomaticRotation } from '../utils/exif.utils';\r\n\r\ninterface LoadImageBase64 {\r\n originalImage: HTMLImageElement;\r\n originalBase64: string;\r\n}\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class LoadImageService {\r\n\r\n private autoRotateSupported: Promise<boolean> = supportsAutomaticRotation();\r\n\r\n loadImageFile(file: File, cropperSettings: CropperSettings): Promise<LoadedImage> {\r\n return new Promise((resolve, reject) => {\r\n const fileReader = new FileReader();\r\n fileReader.onload = (event: any) => {\r\n this.loadImage(event.target.result, file.type, cropperSettings)\r\n .then(resolve)\r\n .catch(reject);\r\n };\r\n fileReader.readAsDataURL(file);\r\n });\r\n }\r\n\r\n private loadImage(imageBase64: string, imageType: string, cropperSettings: CropperSettings): Promise<LoadedImage> {\r\n if (!this.isValidImageType(imageType)) {\r\n return Promise.reject(new Error('Invalid image type'));\r\n }\r\n return this.loadBase64Image(imageBase64, cropperSettings);\r\n }\r\n\r\n private isValidImageType(type: string): boolean {\r\n return /image\\/(png|jpg|jpeg|bmp|gif|tiff|webp|x-icon|vnd.microsoft.icon)/.test(type);\r\n }\r\n\r\n loadImageFromURL(url: string, cropperSettings: CropperSettings): Promise<LoadedImage> {\r\n return new Promise((resolve, reject) => {\r\n const img = new Image();\r\n img.onerror = () => reject;\r\n img.onload = () => {\r\n const canvas = document.createElement('canvas');\r\n const context = canvas.getContext('2d');\r\n canvas.width = img.width;\r\n canvas.height = img.height;\r\n context?.drawImage(img, 0, 0);\r\n this.loadBase64Image(canvas.toDataURL(), cropperSettings).then(resolve);\r\n };\r\n img.crossOrigin = 'anonymous';\r\n img.src = url;\r\n });\r\n }\r\n\r\n loadBase64Image(imageBase64: string, cropperSettings: CropperSettings): Promise<LoadedImage> {\r\n return new Promise<LoadImageBase64>((resolve, reject) => {\r\n const originalImage = new Image();\r\n originalImage.onload = () => resolve({\r\n originalImage,\r\n originalBase64: imageBase64\r\n });\r\n originalImage.onerror = reject;\r\n originalImage.src = imageBase64;\r\n }).then((res: LoadImageBase64) => this.transformImageBase64(res, cropperSettings));\r\n }\r\n\r\n private async transformImageBase64(res: LoadImageBase64, cropperSettings: CropperSettings): Promise<LoadedImage> {\r\n const autoRotate = await this.autoRotateSupported;\r\n const exifTransform = await getTransformationsFromExifData(autoRotate ? -1 : res.originalBase64);\r\n if (!res.originalImage || !res.originalImage.complete) {\r\n return Promise.reject(new Error('No image loaded'));\r\n }\r\n const loadedImage = {\r\n original: {\r\n base64: res.originalBase64,\r\n image: res.originalImage,\r\n size: {\r\n width: res.originalImage.naturalWidth,\r\n height: res.originalImage.naturalHeight\r\n }\r\n },\r\n exifTransform\r\n };\r\n return this.transformLoadedImage(loadedImage, cropperSettings);\r\n }\r\n\r\n async transformLoadedImage(loadedImage: Partial<LoadedImage>, cropperSettings: CropperSettings): Promise<LoadedImage> {\r\n // tslint:disable-next-line:no-non-null-assertion\r\n const canvasRotation = cropperSettings.canvasRotation + loadedImage.exifTransform!.rotate;\r\n const originalSize = {\r\n // tslint:disable-next-line:no-non-null-assertion\r\n width: loadedImage.original!.image.naturalWidth,\r\n // tslint:disable-next-line:no-non-null-assertion\r\n height: loadedImage.original!.image.naturalHeight\r\n };\r\n // tslint:disable-next-line:no-non-null-assertion\r\n if (canvasRotation === 0 && !loadedImage.exifTransform!.flip && !cropperSettings.containWithinAspectRatio) {\r\n return {\r\n original: {\r\n // tslint:disable-next-line:no-non-null-assertion\r\n base64: loadedImage.original!.base64,\r\n // tslint:disable-next-line:no-non-null-assertion\r\n image: loadedImage.original!.image,\r\n size: { ...originalSize }\r\n },\r\n transformed: {\r\n // tslint:disable-next-line:no-non-null-assertion\r\n base64: loadedImage.original!.base64,\r\n // tslint:disable-next-line:no-non-null-assertion\r\n image: loadedImage.original!.image,\r\n size: { ...originalSize }\r\n },\r\n // tslint:disable-next-line:no-non-null-assertion\r\n exifTransform: loadedImage.exifTransform!\r\n };\r\n }\r\n // tslint:disable-next-line:no-non-null-assertion\r\n const transformedSize = this.getTransformedSize(originalSize, loadedImage.exifTransform!, cropperSettings);\r\n const canvas = document.createElement('canvas');\r\n canvas.width = transformedSize.width;\r\n canvas.height = transformedSize.height;\r\n const ctx = canvas.getContext('2d');\r\n ctx?.setTransform(\r\n // tslint:disable-next-line:no-non-null-assertion\r\n loadedImage.exifTransform!.flip ? -1 : 1,\r\n 0,\r\n 0,\r\n 1,\r\n canvas.width / 2,\r\n canvas.height / 2\r\n );\r\n ctx?.rotate(Math.PI * (canvasRotation / 2));\r\n ctx?.drawImage(\r\n // tslint:disable-next-line:no-non-null-assertion\r\n loadedImage.original!.image,\r\n -originalSize.width / 2,\r\n -originalSize.height / 2\r\n );\r\n const transformedBase64 = canvas.toDataURL();\r\n const transformedImage = await this.loadImageFromBase64(transformedBase64);\r\n return {\r\n original: {\r\n // tslint:disable-next-line:no-non-null-assertion\r\n base64: loadedImage.original!.base64,\r\n // tslint:disable-next-line:no-non-null-assertion\r\n image: loadedImage.original!.image,\r\n size: { ...originalSize }\r\n },\r\n transformed: {\r\n base64: transformedBase64,\r\n image: transformedImage,\r\n size: {\r\n width: transformedImage.width,\r\n height: transformedImage.height\r\n }\r\n },\r\n // tslint:disable-next-line:no-non-null-assertion\r\n exifTransform: loadedImage.exifTransform!\r\n };\r\n }\r\n\r\n private loadImageFromBase64(imageBase64: string): Promise<HTMLImageElement> {\r\n return new Promise<HTMLImageElement>(((resolve, reject) => {\r\n const image = new Image();\r\n image.onload = () => resolve(image);\r\n image.onerror = reject;\r\n image.src = imageBase64;\r\n }));\r\n }\r\n\r\n private getTransformedSize(\r\n originalSize: { width: number, height: number },\r\n exifTransform: ExifTransform,\r\n cropperSettings: CropperSettings\r\n ): Dimensions {\r\n const canvasRotation = cropperSettings.canvasRotation + exifTransform.rotate;\r\n if (cropperSettings.containWithinAspectRatio) {\r\n if (canvasRotation % 2) {\r\n const minWidthToContain = originalSize.width * cropperSettings.aspectRatio;\r\n const minHeightToContain = originalSize.height / cropperSettings.aspectRatio;\r\n return {\r\n width: Math.max(originalSize.height, minWidthToContain),\r\n height: Math.max(originalSize.width, minHeightToContain)\r\n };\r\n } else {\r\n const minWidthToContain = originalSize.height * cropperSettings.aspectRatio;\r\n const minHeightToContain = originalSize.width / cropperSettings.aspectRatio;\r\n return {\r\n width: Math.max(originalSize.width, minWidthToContain),\r\n height: Math.max(originalSize.height, minHeightToContain)\r\n };\r\n }\r\n }\r\n\r\n if (canvasRotation % 2) {\r\n return {\r\n height: originalSize.width,\r\n width: originalSize.height\r\n };\r\n }\r\n return {\r\n width: originalSize.width,\r\n height: originalSize.height\r\n };\r\n }\r\n}\r\n","/**\r\n * @license\r\n * Copyright ASW (A Software World) All Rights Reserved.\r\n *\r\n * Use of this source code is governed by an MIT-style license that can be\r\n * found in the LICENSE file\r\n */\r\n\r\nimport {\r\n ChangeDetectionStrategy,\r\n ChangeDetectorRef,\r\n Component,\r\n ElementRef,\r\n EventEmitter,\r\n HostBinding,\r\n HostListener,\r\n Input,\r\n isDevMode,\r\n OnChanges,\r\n OnInit,\r\n Output,\r\n SimpleChanges,\r\n ViewChild\r\n} from '@angular/core';\r\nimport { DomSanitizer, SafeStyle, SafeUrl } from '@angular/platform-browser';\r\nimport { CropperPosition, Dimensions, ImageCroppedEvent, ImageTransform, LoadedImage, MoveStart } from './interfaces';\r\nimport { OutputFormat } from './interfaces/cropper-options.interface';\r\nimport { CropperSettings } from './interfaces/cropper.settings';\r\nimport { MoveTypes } from './interfaces/move-start.interface';\r\nimport { CropService } from './services/crop.service';\r\nimport { CropperPositionService } from './services/cropper-position.service';\r\nimport { LoadImageService } from './services/load-image.service';\r\nimport { HammerStatic } from './utils/hammer.utils';\r\nimport { getEventForKey, getInvertedPositionForKey, getPositionForKey } from './utils/keyboard.utils';\r\n\r\n@Component({\r\n selector: 'asw-image-crop',\r\n templateUrl: './image-crop.component.html',\r\n styleUrls: ['./image-crop.component.scss'],\r\n changeDetection: ChangeDetectionStrategy.OnPush\r\n})\r\n\r\nexport class AswImageCropComponent implements OnChanges, OnInit {\r\n private Hammer: HammerStatic = (window as any).Hammer || null;\r\n private settings = new CropperSettings();\r\n private setImageMaxSizeRetries = 0;\r\n private moveStart?: MoveStart;\r\n private loadedImage?: LoadedImage;\r\n\r\n safeImgDataUrl?: SafeUrl | string;\r\n safeTransformStyle?: SafeStyle | string;\r\n marginLeft: SafeStyle | string = '0px';\r\n maxSize: Dimensions = {\r\n width: 0,\r\n height: 0\r\n };\r\n moveTypes = MoveTypes;\r\n imageVisible = false;\r\n\r\n @ViewChild('wrapper', { static: true }) wrapper!: ElementRef<HTMLDivElement>;\r\n @ViewChild('sourceImage', { static: false }) sourceImage!: ElementRef<HTMLDivElement>;\r\n\r\n @Input() imageChangedEvent?: any;\r\n @Input() imageURL?: string;\r\n @Input() imageBase64?: string;\r\n @Input() imageFile?: File;\r\n\r\n @Input() format: OutputFormat = this.settings.format;\r\n @Input() transform: ImageTransform = {};\r\n @Input() maintainAspectRatio = this.settings.maintainAspectRatio;\r\n @Input() aspectRatio = this.settings.aspectRatio;\r\n @Input() resizeToWidth = this.settings.resizeToWidth;\r\n @Input() resizeToHeight = this.settings.resizeToHeight;\r\n @Input() cropperMinWidth = this.settings.cropperMinWidth;\r\n @Input() cropperMinHeight = this.settings.cropperMinHeight;\r\n @Input() cropperMaxHeight = this.settings.cropperMaxHeight;\r\n @Input() cropperMaxWidth = this.settings.cropperMaxWidth;\r\n @Input() cropperStaticWidth = this.settings.cropperStaticWidth;\r\n @Input() cropperStaticHeight = this.settings.cropperStaticHeight;\r\n @Input() canvasRotation = this.settings.canvasRotation;\r\n @Input() initialStepSize = this.settings.initialStepSize;\r\n @Input() roundCropper = this.settings.roundCropper;\r\n @Input() onlyScaleDown = this.settings.onlyScaleDown;\r\n @Input() imageQuality = this.settings.imageQuality;\r\n @Input() autoCrop = this.settings.autoCrop;\r\n @Input() backgroundColor = this.settings.backgroundColor;\r\n @Input() containWithinAspectRatio = this.settings.containWithinAspectRatio;\r\n @Input() hideResizeSquares = this.settings.hideResizeSquares;\r\n @Input() cropper: CropperPosition = {\r\n x1: -100,\r\n y1: -100,\r\n x2: 10000,\r\n y2: 10000\r\n };\r\n @HostBinding('style.text-align')\r\n @Input() alignImage: 'left' | 'center' = this.settings.alignImage;\r\n @HostBinding('class.disabled')\r\n @Input() disabled = false;\r\n\r\n @Output() imageCropped = new EventEmitter<ImageCroppedEvent>();\r\n @Output() startCropImage = new EventEmitter<void>();\r\n @Output() imageLoaded = new EventEmitter<LoadedImage>();\r\n @Output() cropperReady = new EventEmitter<Dimensions>();\r\n @Output() loadImageFailed = new EventEmitter<void>();\r\n\r\n constructor(\r\n private cropService: CropService,\r\n private cropperPositionService: CropperPositionService,\r\n private loadImageService: LoadImageService,\r\n private sanitizer: DomSanitizer,\r\n private cd: ChangeDetectorRef\r\n ) {\r\n this.reset();\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges): void {\r\n this.onChangesUpdateSettings(changes);\r\n this.onChangesInputImage(changes);\r\n\r\n if (this.loadedImage?.original.image.complete && (changes.containWithinAspectRatio || changes.canvasRotation)) {\r\n this.loadImageService\r\n .transformLoadedImage(this.loadedImage, this.settings)\r\n .then((res) => this.setLoadedImage(res))\r\n .catch((err) => this.loadImageError(err));\r\n }\r\n if (changes.cropper || changes.maintainAspectRatio || changes.aspectRatio) {\r\n this.setMaxSize();\r\n this.setCropperScaledMinSize();\r\n this.setCropperScaledMaxSize();\r\n if (this.maintainAspectRatio && (changes.maintainAspectRatio || changes.aspectRatio)) {\r\n this.resetCropperPosition();\r\n } else if (changes.cropper) {\r\n this.checkCropperPosition(false);\r\n this.doAutoCrop();\r\n }\r\n this.cd.markForCh