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,134 lines (1,123 loc) 79 kB
import * as i5 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Injectable, EventEmitter, isDevMode, Component, ChangeDetectionStrategy, ViewChild, Input, HostBinding, Output, HostListener, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import * as i4 from '@angular/platform-browser'; /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ class CropperSettings { // From options format = 'png'; maintainAspectRatio = true; transform = {}; aspectRatio = 1; resizeToWidth = 0; resizeToHeight = 0; cropperMinWidth = 0; cropperMinHeight = 0; cropperMaxHeight = 0; cropperMaxWidth = 0; cropperStaticWidth = 0; cropperStaticHeight = 0; canvasRotation = 0; initialStepSize = 3; roundCropper = false; onlyScaleDown = false; imageQuality = 92; autoCrop = true; backgroundColor = null; containWithinAspectRatio = false; hideResizeSquares = false; alignImage = 'center'; // Internal cropperScaledMinWidth = 20; cropperScaledMinHeight = 20; cropperScaledMaxWidth = 20; cropperScaledMaxHeight = 20; stepSize = this.initialStepSize; setOptions(options) { Object.keys(options) .filter((k) => k in this) .forEach((k) => this[k] = options[k]); this.validateOptions(); } setOptionsFromChanges(changes) { Object.keys(changes) .filter((k) => k in this) .forEach((k) => this[k] = changes[k].currentValue); this.validateOptions(); } validateOptions() { if (this.maintainAspectRatio && !this.aspectRatio) { throw new Error('`aspectRatio` should > 0 when `maintainAspectRatio` is enabled'); } } } /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ var MoveTypes; (function (MoveTypes) { MoveTypes["Move"] = "move"; MoveTypes["Resize"] = "resize"; MoveTypes["Pinch"] = "pinch"; })(MoveTypes || (MoveTypes = {})); /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ function getPositionForKey(key) { switch (key) { case 'ArrowUp': return 'top'; case 'ArrowRight': return 'right'; case 'ArrowDown': return 'bottom'; case 'ArrowLeft': default: return 'left'; } } function getInvertedPositionForKey(key) { switch (key) { case 'ArrowUp': return 'bottom'; case 'ArrowRight': return 'left'; case 'ArrowDown': return 'top'; case 'ArrowLeft': default: return 'right'; } } function getEventForKey(key, stepSize) { switch (key) { case 'ArrowUp': return { clientX: 0, clientY: stepSize * -1 }; case 'ArrowRight': return { clientX: stepSize, clientY: 0 }; case 'ArrowDown': return { clientX: 0, clientY: stepSize }; case 'ArrowLeft': default: return { clientX: stepSize * -1, clientY: 0 }; } } /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ function percentage(percent, totalValue) { return (percent / 100) * totalValue; } /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ function resizeCanvas(canvas, width, height) { const WIDTH_SOURCE = canvas.width; const HEIGHT_SOURCE = canvas.height; width = Math.round(width); height = Math.round(height); const RATIO_W = WIDTH_SOURCE / width; const RATIO_H = HEIGHT_SOURCE / height; const RATIO_W_HALF = Math.ceil(RATIO_W / 2); const RATIO_H_HALF = Math.ceil(RATIO_H / 2); const ctx = canvas.getContext('2d'); if (ctx) { const img = ctx.getImageData(0, 0, WIDTH_SOURCE, HEIGHT_SOURCE); const img2 = ctx.createImageData(width, height); const data = img.data; const data2 = img2.data; for (let j = 0; j < height; j++) { for (let i = 0; i < width; i++) { const x2 = (i + j * width) * 4; const CENTER_Y = j * RATIO_H; let weight = 0; let weights = 0; let WEIGHTS_ALPHA = 0; let GX_R = 0; let GX_G = 0; let GX_B = 0; let GX_A = 0; const XX_START = Math.floor(i * RATIO_W); const YY_START = Math.floor(j * RATIO_H); let XX_STOP = Math.ceil((i + 1) * RATIO_W); let YY_STOP = Math.ceil((j + 1) * RATIO_H); XX_STOP = Math.min(XX_STOP, WIDTH_SOURCE); YY_STOP = Math.min(YY_STOP, HEIGHT_SOURCE); for (let yy = YY_START; yy < YY_STOP; yy++) { const dy = Math.abs(CENTER_Y - yy) / RATIO_H_HALF; const CENTER_X = i * RATIO_W; const w0 = dy * dy; // pre-calc part of w for (let xx = XX_START; xx < XX_STOP; xx++) { const dx = Math.abs(CENTER_X - xx) / RATIO_W_HALF; const w = Math.sqrt(w0 + dx * dx); if (w >= 1) { // pixel too far continue; } // hermite filter weight = 2 * w * w * w - 3 * w * w + 1; const POS_X = 4 * (xx + yy * WIDTH_SOURCE); // alpha GX_A += weight * data[POS_X + 3]; WEIGHTS_ALPHA += weight; // colors if (data[POS_X + 3] < 255) { weight = weight * data[POS_X + 3] / 250; } GX_R += weight * data[POS_X]; GX_G += weight * data[POS_X + 1]; GX_B += weight * data[POS_X + 2]; weights += weight; } } data2[x2] = GX_R / weights; data2[x2 + 1] = GX_G / weights; data2[x2 + 2] = GX_B / weights; data2[x2 + 3] = GX_A / WEIGHTS_ALPHA; } } canvas.width = width; canvas.height = height; // draw ctx.putImageData(img2, 0, 0); } } /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ class CropService { crop(sourceImage, loadedImage, cropper, settings) { const imagePosition = this.getImagePosition(sourceImage, loadedImage, cropper, settings); const width = imagePosition.x2 - imagePosition.x1; const height = imagePosition.y2 - imagePosition.y1; const cropCanvas = document.createElement('canvas'); cropCanvas.width = width; cropCanvas.height = height; const ctx = cropCanvas.getContext('2d'); if (!ctx) { return null; } if (settings.backgroundColor != null) { ctx.fillStyle = settings.backgroundColor; ctx.fillRect(0, 0, width, height); } const scaleX = (settings.transform.scale || 1) * (settings.transform.flipH ? -1 : 1); const scaleY = (settings.transform.scale || 1) * (settings.transform.flipV ? -1 : 1); const transformedImage = loadedImage.transformed; ctx.setTransform(scaleX, 0, 0, scaleY, transformedImage.size.width / 2, transformedImage.size.height / 2); ctx.translate(-imagePosition.x1 / scaleX, -imagePosition.y1 / scaleY); ctx.rotate((settings.transform.rotate || 0) * Math.PI / 180); const translateH = settings.transform.translateH ? percentage(settings.transform.translateH, transformedImage.size.width) : 0; const translateV = settings.transform.translateV ? percentage(settings.transform.translateV, transformedImage.size.height) : 0; ctx.drawImage(transformedImage.image, translateH - transformedImage.size.width / 2, translateV - transformedImage.size.height / 2); const output = { width, height, imagePosition, cropperPosition: { ...cropper } }; if (settings.containWithinAspectRatio) { output.offsetImagePosition = this.getOffsetImagePosition(sourceImage, loadedImage, cropper, settings); } const resizeRatio = this.getResizeRatio(width, height, settings); if (resizeRatio !== 1) { output.width = Math.round(width * resizeRatio); output.height = settings.maintainAspectRatio ? Math.round(output.width / settings.aspectRatio) : Math.round(height * resizeRatio); resizeCanvas(cropCanvas, output.width, output.height); } output.base64 = cropCanvas.toDataURL('image/' + settings.format, this.getQuality(settings)); return output; } getImagePosition(sourceImage, loadedImage, cropper, settings) { const sourceImageElement = sourceImage.nativeElement; const ratio = loadedImage.transformed.size.width / sourceImageElement.offsetWidth; const out = { x1: Math.round(cropper.x1 * ratio), y1: Math.round(cropper.y1 * ratio), x2: Math.round(cropper.x2 * ratio), y2: Math.round(cropper.y2 * ratio) }; if (!settings.containWithinAspectRatio) { out.x1 = Math.max(out.x1, 0); out.y1 = Math.max(out.y1, 0); out.x2 = Math.min(out.x2, loadedImage.transformed.size.width); out.y2 = Math.min(out.y2, loadedImage.transformed.size.height); } return out; } getOffsetImagePosition(sourceImage, loadedImage, cropper, settings) { const canvasRotation = settings.canvasRotation + loadedImage.exifTransform.rotate; const sourceImageElement = sourceImage.nativeElement; const ratio = loadedImage.transformed.size.width / sourceImageElement.offsetWidth; let offsetX; let offsetY; if (canvasRotation % 2) { offsetX = (loadedImage.transformed.size.width - loadedImage.original.size.height) / 2; offsetY = (loadedImage.transformed.size.height - loadedImage.original.size.width) / 2; } else { offsetX = (loadedImage.transformed.size.width - loadedImage.original.size.width) / 2; offsetY = (loadedImage.transformed.size.height - loadedImage.original.size.height) / 2; } const out = { x1: Math.round(cropper.x1 * ratio) - offsetX, y1: Math.round(cropper.y1 * ratio) - offsetY, x2: Math.round(cropper.x2 * ratio) - offsetX, y2: Math.round(cropper.y2 * ratio) - offsetY }; if (!settings.containWithinAspectRatio) { out.x1 = Math.max(out.x1, 0); out.y1 = Math.max(out.y1, 0); out.x2 = Math.min(out.x2, loadedImage.transformed.size.width); out.y2 = Math.min(out.y2, loadedImage.transformed.size.height); } return out; } getResizeRatio(width, height, settings) { const ratioWidth = settings.resizeToWidth / width; const ratioHeight = settings.resizeToHeight / height; const ratios = new Array(); if (settings.resizeToWidth > 0) { ratios.push(ratioWidth); } if (settings.resizeToHeight > 0) { ratios.push(ratioHeight); } const result = ratios.length === 0 ? 1 : Math.min(...ratios); if (result > 1 && !settings.onlyScaleDown) { return result; } return Math.min(result, 1); } getQuality(settings) { return Math.min(1, Math.max(0, settings.imageQuality / 100)); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.11", ngImport: i0, type: CropService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.11", ngImport: i0, type: CropService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.11", ngImport: i0, type: CropService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ class CropperPositionService { resetCropperPosition(sourceImage, cropperPosition, settings) { if (!sourceImage?.nativeElement) { return; } const sourceImageElement = sourceImage.nativeElement; if (settings.cropperStaticHeight && settings.cropperStaticWidth) { cropperPosition.x1 = 0; cropperPosition.x2 = sourceImageElement.offsetWidth > settings.cropperStaticWidth ? settings.cropperStaticWidth : sourceImageElement.offsetWidth; cropperPosition.y1 = 0; cropperPosition.y2 = sourceImageElement.offsetHeight > settings.cropperStaticHeight ? settings.cropperStaticHeight : sourceImageElement.offsetHeight; } else { const cropperWidth = Math.min(settings.cropperScaledMaxWidth, sourceImageElement.offsetWidth); const cropperHeight = Math.min(settings.cropperScaledMaxHeight, sourceImageElement.offsetHeight); if (!settings.maintainAspectRatio) { cropperPosition.x1 = 0; cropperPosition.x2 = cropperWidth; cropperPosition.y1 = 0; cropperPosition.y2 = cropperHeight; } else if (sourceImageElement.offsetWidth / settings.aspectRatio < sourceImageElement.offsetHeight) { cropperPosition.x1 = 0; cropperPosition.x2 = cropperWidth; const cropperHeightWithAspectRatio = cropperWidth / settings.aspectRatio; cropperPosition.y1 = (sourceImageElement.offsetHeight - cropperHeightWithAspectRatio) / 2; cropperPosition.y2 = cropperPosition.y1 + cropperHeightWithAspectRatio; } else { cropperPosition.y1 = 0; cropperPosition.y2 = cropperHeight; const cropperWidthWithAspectRatio = cropperHeight * settings.aspectRatio; cropperPosition.x1 = (sourceImageElement.offsetWidth - cropperWidthWithAspectRatio) / 2; cropperPosition.x2 = cropperPosition.x1 + cropperWidthWithAspectRatio; } } } move(event, moveStart, cropperPosition) { const diffX = this.getClientX(event) - moveStart.clientX; const diffY = this.getClientY(event) - moveStart.clientY; cropperPosition.x1 = moveStart.x1 + diffX; cropperPosition.y1 = moveStart.y1 + diffY; cropperPosition.x2 = moveStart.x2 + diffX; cropperPosition.y2 = moveStart.y2 + diffY; } resize(event, moveStart, cropperPosition, maxSize, settings) { const moveX = this.getClientX(event) - moveStart.clientX; const moveY = this.getClientY(event) - moveStart.clientY; switch (moveStart.position) { case 'left': cropperPosition.x1 = Math.min(Math.max(moveStart.x1 + moveX, cropperPosition.x2 - settings.cropperScaledMaxWidth), cropperPosition.x2 - settings.cropperScaledMinWidth); break; case 'topleft': cropperPosition.x1 = Math.min(Math.max(moveStart.x1 + moveX, cropperPosition.x2 - settings.cropperScaledMaxWidth), cropperPosition.x2 - settings.cropperScaledMinWidth); cropperPosition.y1 = Math.min(Math.max(moveStart.y1 + moveY, cropperPosition.y2 - settings.cropperScaledMaxHeight), cropperPosition.y2 - settings.cropperScaledMinHeight); break; case 'top': cropperPosition.y1 = Math.min(Math.max(moveStart.y1 + moveY, cropperPosition.y2 - settings.cropperScaledMaxHeight), cropperPosition.y2 - settings.cropperScaledMinHeight); break; case 'topright': cropperPosition.x2 = Math.max(Math.min(moveStart.x2 + moveX, cropperPosition.x1 + settings.cropperScaledMaxWidth), cropperPosition.x1 + settings.cropperScaledMinWidth); cropperPosition.y1 = Math.min(Math.max(moveStart.y1 + moveY, cropperPosition.y2 - settings.cropperScaledMaxHeight), cropperPosition.y2 - settings.cropperScaledMinHeight); break; case 'right': cropperPosition.x2 = Math.max(Math.min(moveStart.x2 + moveX, cropperPosition.x1 + settings.cropperScaledMaxWidth), cropperPosition.x1 + settings.cropperScaledMinWidth); break; case 'bottomright': cropperPosition.x2 = Math.max(Math.min(moveStart.x2 + moveX, cropperPosition.x1 + settings.cropperScaledMaxWidth), cropperPosition.x1 + settings.cropperScaledMinWidth); cropperPosition.y2 = Math.max(Math.min(moveStart.y2 + moveY, cropperPosition.y1 + settings.cropperScaledMaxHeight), cropperPosition.y1 + settings.cropperScaledMinHeight); break; case 'bottom': cropperPosition.y2 = Math.max(Math.min(moveStart.y2 + moveY, cropperPosition.y1 + settings.cropperScaledMaxHeight), cropperPosition.y1 + settings.cropperScaledMinHeight); break; case 'bottomleft': cropperPosition.x1 = Math.min(Math.max(moveStart.x1 + moveX, cropperPosition.x2 - settings.cropperScaledMaxWidth), cropperPosition.x2 - settings.cropperScaledMinWidth); cropperPosition.y2 = Math.max(Math.min(moveStart.y2 + moveY, cropperPosition.y1 + settings.cropperScaledMaxHeight), cropperPosition.y1 + settings.cropperScaledMinHeight); break; case 'center': const scale = event.scale; const newWidth = Math.min(Math.max(settings.cropperScaledMinWidth, (Math.abs(moveStart.x2 - moveStart.x1)) * scale), settings.cropperScaledMaxWidth); const newHeight = Math.min(Math.max(settings.cropperScaledMinHeight, (Math.abs(moveStart.y2 - moveStart.y1)) * scale), settings.cropperScaledMaxHeight); cropperPosition.x1 = moveStart.clientX - newWidth / 2; cropperPosition.x2 = moveStart.clientX + newWidth / 2; cropperPosition.y1 = moveStart.clientY - newHeight / 2; cropperPosition.y2 = moveStart.clientY + newHeight / 2; if (cropperPosition.x1 < 0) { cropperPosition.x2 -= cropperPosition.x1; cropperPosition.x1 = 0; } else if (cropperPosition.x2 > maxSize.width) { cropperPosition.x1 -= (cropperPosition.x2 - maxSize.width); cropperPosition.x2 = maxSize.width; } if (cropperPosition.y1 < 0) { cropperPosition.y2 -= cropperPosition.y1; cropperPosition.y1 = 0; } else if (cropperPosition.y2 > maxSize.height) { cropperPosition.y1 -= (cropperPosition.y2 - maxSize.height); cropperPosition.y2 = maxSize.height; } break; } if (settings.maintainAspectRatio) { // tslint:disable-next-line:no-non-null-assertion this.checkAspectRatio(moveStart.position, cropperPosition, maxSize, settings); } } checkAspectRatio(position, cropperPosition, maxSize, settings) { let overflowX = 0; let overflowY = 0; switch (position) { case 'top': cropperPosition.x2 = cropperPosition.x1 + (cropperPosition.y2 - cropperPosition.y1) * settings.aspectRatio; overflowX = Math.max(cropperPosition.x2 - maxSize.width, 0); overflowY = Math.max(0 - cropperPosition.y1, 0); if (overflowX > 0 || overflowY > 0) { cropperPosition.x2 -= (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX; cropperPosition.y1 += (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio; } break; case 'bottom': cropperPosition.x2 = cropperPosition.x1 + (cropperPosition.y2 - cropperPosition.y1) * settings.aspectRatio; overflowX = Math.max(cropperPosition.x2 - maxSize.width, 0); overflowY = Math.max(cropperPosition.y2 - maxSize.height, 0); if (overflowX > 0 || overflowY > 0) { cropperPosition.x2 -= (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX; cropperPosition.y2 -= (overflowY * settings.aspectRatio) > overflowX ? overflowY : (overflowX / settings.aspectRatio); } break; case 'topleft': cropperPosition.y1 = cropperPosition.y2 - (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio; overflowX = Math.max(0 - cropperPosition.x1, 0); overflowY = Math.max(0 - cropperPosition.y1, 0); if (overflowX > 0 || overflowY > 0) { cropperPosition.x1 += (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX; cropperPosition.y1 += (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio; } break; case 'topright': cropperPosition.y1 = cropperPosition.y2 - (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio; overflowX = Math.max(cropperPosition.x2 - maxSize.width, 0); overflowY = Math.max(0 - cropperPosition.y1, 0); if (overflowX > 0 || overflowY > 0) { cropperPosition.x2 -= (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX; cropperPosition.y1 += (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio; } break; case 'right': case 'bottomright': cropperPosition.y2 = cropperPosition.y1 + (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio; overflowX = Math.max(cropperPosition.x2 - maxSize.width, 0); overflowY = Math.max(cropperPosition.y2 - maxSize.height, 0); if (overflowX > 0 || overflowY > 0) { cropperPosition.x2 -= (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX; cropperPosition.y2 -= (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio; } break; case 'left': case 'bottomleft': cropperPosition.y2 = cropperPosition.y1 + (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio; overflowX = Math.max(0 - cropperPosition.x1, 0); overflowY = Math.max(cropperPosition.y2 - maxSize.height, 0); if (overflowX > 0 || overflowY > 0) { cropperPosition.x1 += (overflowY * settings.aspectRatio) > overflowX ? (overflowY * settings.aspectRatio) : overflowX; cropperPosition.y2 -= (overflowY * settings.aspectRatio) > overflowX ? overflowY : overflowX / settings.aspectRatio; } break; case 'center': cropperPosition.x2 = cropperPosition.x1 + (cropperPosition.y2 - cropperPosition.y1) * settings.aspectRatio; cropperPosition.y2 = cropperPosition.y1 + (cropperPosition.x2 - cropperPosition.x1) / settings.aspectRatio; const overflowX1 = Math.max(0 - cropperPosition.x1, 0); const overflowX2 = Math.max(cropperPosition.x2 - maxSize.width, 0); const overflowY1 = Math.max(cropperPosition.y2 - maxSize.height, 0); const overflowY2 = Math.max(0 - cropperPosition.y1, 0); if (overflowX1 > 0 || overflowX2 > 0 || overflowY1 > 0 || overflowY2 > 0) { cropperPosition.x1 += (overflowY1 * settings.aspectRatio) > overflowX1 ? (overflowY1 * settings.aspectRatio) : overflowX1; cropperPosition.x2 -= (overflowY2 * settings.aspectRatio) > overflowX2 ? (overflowY2 * settings.aspectRatio) : overflowX2; cropperPosition.y1 += (overflowY2 * settings.aspectRatio) > overflowX2 ? overflowY2 : overflowX2 / settings.aspectRatio; cropperPosition.y2 -= (overflowY1 * settings.aspectRatio) > overflowX1 ? overflowY1 : overflowX1 / settings.aspectRatio; } break; } } getClientX(event) { return event.touches?.[0].clientX || event.clientX || 0; } getClientY(event) { return event.touches?.[0].clientY || event.clientY || 0; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.11", ngImport: i0, type: CropperPositionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.11", ngImport: i0, type: CropperPositionService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.11", ngImport: i0, type: CropperPositionService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ // Black 2x1 JPEG, with the following meta information set: // - EXIF Orientation: 6 (Rotated 90° CCW) // Source: https://github.com/blueimp/JavaScript-Load-Image const testAutoOrientationImageURL = 'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' + 'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' + 'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' + 'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' + 'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' + 'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q=='; function supportsAutomaticRotation() { return new Promise((resolve) => { const img = new Image(); img.onload = () => { // Check if browser supports automatic image orientation: const supported = img.width === 1 && img.height === 2; resolve(supported); }; img.src = testAutoOrientationImageURL; }); } function getTransformationsFromExifData(exifRotationOrBase64Image) { if (typeof exifRotationOrBase64Image === 'string') { exifRotationOrBase64Image = getExifRotation(exifRotationOrBase64Image); } switch (exifRotationOrBase64Image) { case 2: return { rotate: 0, flip: true }; case 3: return { rotate: 2, flip: false }; case 4: return { rotate: 2, flip: true }; case 5: return { rotate: 1, flip: true }; case 6: return { rotate: 1, flip: false }; case 7: return { rotate: 3, flip: true }; case 8: return { rotate: 3, flip: false }; default: return { rotate: 0, flip: false }; } } function getExifRotation(imageBase64) { const view = new DataView(base64ToArrayBuffer(imageBase64)); if (view.getUint16(0, false) !== 0xFFD8) { return -2; } const length = view.byteLength; let offset = 2; while (offset < length) { if (view.getUint16(offset + 2, false) <= 8) { return -1; } const marker = view.getUint16(offset, false); offset += 2; if (marker === 0xFFE1) { if (view.getUint32(offset += 2, false) !== 0x45786966) { return -1; } const little = view.getUint16(offset += 6, false) === 0x4949; offset += view.getUint32(offset + 4, little); const tags = view.getUint16(offset, little); offset += 2; for (let i = 0; i < tags; i++) { if (view.getUint16(offset + (i * 12), little) === 0x0112) { return view.getUint16(offset + (i * 12) + 8, little); } } // tslint:disable-next-line:no-bitwise } else if ((marker & 0xFF00) !== 0xFF00) { break; } else { offset += view.getUint16(offset, false); } } return -1; } function base64ToArrayBuffer(imageBase64) { imageBase64 = imageBase64.replace(/^data\:([^\;]+)\;base64,/gmi, ''); const binaryString = atob(imageBase64); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; } /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ class LoadImageService { autoRotateSupported = supportsAutomaticRotation(); loadImageFile(file, cropperSettings) { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = (event) => { this.loadImage(event.target.result, file.type, cropperSettings) .then(resolve) .catch(reject); }; fileReader.readAsDataURL(file); }); } loadImage(imageBase64, imageType, cropperSettings) { if (!this.isValidImageType(imageType)) { return Promise.reject(new Error('Invalid image type')); } return this.loadBase64Image(imageBase64, cropperSettings); } isValidImageType(type) { return /image\/(png|jpg|jpeg|bmp|gif|tiff|webp|x-icon|vnd.microsoft.icon)/.test(type); } loadImageFromURL(url, cropperSettings) { return new Promise((resolve, reject) => { const img = new Image(); img.onerror = () => reject; img.onload = () => { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; context?.drawImage(img, 0, 0); this.loadBase64Image(canvas.toDataURL(), cropperSettings).then(resolve); }; img.crossOrigin = 'anonymous'; img.src = url; }); } loadBase64Image(imageBase64, cropperSettings) { return new Promise((resolve, reject) => { const originalImage = new Image(); originalImage.onload = () => resolve({ originalImage, originalBase64: imageBase64 }); originalImage.onerror = reject; originalImage.src = imageBase64; }).then((res) => this.transformImageBase64(res, cropperSettings)); } async transformImageBase64(res, cropperSettings) { const autoRotate = await this.autoRotateSupported; const exifTransform = await getTransformationsFromExifData(autoRotate ? -1 : res.originalBase64); if (!res.originalImage || !res.originalImage.complete) { return Promise.reject(new Error('No image loaded')); } const loadedImage = { original: { base64: res.originalBase64, image: res.originalImage, size: { width: res.originalImage.naturalWidth, height: res.originalImage.naturalHeight } }, exifTransform }; return this.transformLoadedImage(loadedImage, cropperSettings); } async transformLoadedImage(loadedImage, cropperSettings) { // tslint:disable-next-line:no-non-null-assertion const canvasRotation = cropperSettings.canvasRotation + loadedImage.exifTransform.rotate; const originalSize = { // tslint:disable-next-line:no-non-null-assertion width: loadedImage.original.image.naturalWidth, // tslint:disable-next-line:no-non-null-assertion height: loadedImage.original.image.naturalHeight }; // tslint:disable-next-line:no-non-null-assertion if (canvasRotation === 0 && !loadedImage.exifTransform.flip && !cropperSettings.containWithinAspectRatio) { return { original: { // tslint:disable-next-line:no-non-null-assertion base64: loadedImage.original.base64, // tslint:disable-next-line:no-non-null-assertion image: loadedImage.original.image, size: { ...originalSize } }, transformed: { // tslint:disable-next-line:no-non-null-assertion base64: loadedImage.original.base64, // tslint:disable-next-line:no-non-null-assertion image: loadedImage.original.image, size: { ...originalSize } }, // tslint:disable-next-line:no-non-null-assertion exifTransform: loadedImage.exifTransform }; } // tslint:disable-next-line:no-non-null-assertion const transformedSize = this.getTransformedSize(originalSize, loadedImage.exifTransform, cropperSettings); const canvas = document.createElement('canvas'); canvas.width = transformedSize.width; canvas.height = transformedSize.height; const ctx = canvas.getContext('2d'); ctx?.setTransform( // tslint:disable-next-line:no-non-null-assertion loadedImage.exifTransform.flip ? -1 : 1, 0, 0, 1, canvas.width / 2, canvas.height / 2); ctx?.rotate(Math.PI * (canvasRotation / 2)); ctx?.drawImage( // tslint:disable-next-line:no-non-null-assertion loadedImage.original.image, -originalSize.width / 2, -originalSize.height / 2); const transformedBase64 = canvas.toDataURL(); const transformedImage = await this.loadImageFromBase64(transformedBase64); return { original: { // tslint:disable-next-line:no-non-null-assertion base64: loadedImage.original.base64, // tslint:disable-next-line:no-non-null-assertion image: loadedImage.original.image, size: { ...originalSize } }, transformed: { base64: transformedBase64, image: transformedImage, size: { width: transformedImage.width, height: transformedImage.height } }, // tslint:disable-next-line:no-non-null-assertion exifTransform: loadedImage.exifTransform }; } loadImageFromBase64(imageBase64) { return new Promise(((resolve, reject) => { const image = new Image(); image.onload = () => resolve(image); image.onerror = reject; image.src = imageBase64; })); } getTransformedSize(originalSize, exifTransform, cropperSettings) { const canvasRotation = cropperSettings.canvasRotation + exifTransform.rotate; if (cropperSettings.containWithinAspectRatio) { if (canvasRotation % 2) { const minWidthToContain = originalSize.width * cropperSettings.aspectRatio; const minHeightToContain = originalSize.height / cropperSettings.aspectRatio; return { width: Math.max(originalSize.height, minWidthToContain), height: Math.max(originalSize.width, minHeightToContain) }; } else { const minWidthToContain = originalSize.height * cropperSettings.aspectRatio; const minHeightToContain = originalSize.width / cropperSettings.aspectRatio; return { width: Math.max(originalSize.width, minWidthToContain), height: Math.max(originalSize.height, minHeightToContain) }; } } if (canvasRotation % 2) { return { height: originalSize.width, width: originalSize.height }; } return { width: originalSize.width, height: originalSize.height }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.11", ngImport: i0, type: LoadImageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.11", ngImport: i0, type: LoadImageService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.11", ngImport: i0, type: LoadImageService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); /** * @license * Copyright ASW (A Software World) All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file */ class AswImageCropComponent { cropService; cropperPositionService; loadImageService; sanitizer; cd; Hammer = window.Hammer || null; settings = new CropperSettings(); setImageMaxSizeRetries = 0; moveStart; loadedImage; safeImgDataUrl; safeTransformStyle; marginLeft = '0px'; maxSize = { width: 0, height: 0 }; moveTypes = MoveTypes; imageVisible = false; wrapper; sourceImage; imageChangedEvent; imageURL; imageBase64; imageFile; format = this.settings.format; transform = {}; maintainAspectRatio = this.settings.maintainAspectRatio; aspectRatio = this.settings.aspectRatio; resizeToWidth = this.settings.resizeToWidth; resizeToHeight = this.settings.resizeToHeight; cropperMinWidth = this.settings.cropperMinWidth; cropperMinHeight = this.settings.cropperMinHeight; cropperMaxHeight = this.settings.cropperMaxHeight; cropperMaxWidth = this.settings.cropperMaxWidth; cropperStaticWidth = this.settings.cropperStaticWidth; cropperStaticHeight = this.settings.cropperStaticHeight; canvasRotation = this.settings.canvasRotation; initialStepSize = this.settings.initialStepSize; roundCropper = this.settings.roundCropper; onlyScaleDown = this.settings.onlyScaleDown; imageQuality = this.settings.imageQuality; autoCrop = this.settings.autoCrop; backgroundColor = this.settings.backgroundColor; containWithinAspectRatio = this.settings.containWithinAspectRatio; hideResizeSquares = this.settings.hideResizeSquares; cropper = { x1: -100, y1: -100, x2: 10000, y2: 10000 }; alignImage = this.settings.alignImage; disabled = false; imageCropped = new EventEmitter(); startCropImage = new EventEmitter(); imageLoaded = new EventEmitter(); cropperReady = new EventEmitter(); loadImageFailed = new EventEmitter(); constructor(cropService, cropperPositionService, loadImageService, sanitizer, cd) { this.cropService = cropService; this.cropperPositionService = cropperPositionService; this.loadImageService = loadImageService; this.sanitizer = sanitizer; this.cd = cd; this.reset(); } ngOnChanges(changes) { this.onChangesUpdateSettings(changes); this.onChangesInputImage(changes); if (this.loadedImage?.original.image.complete && (changes.containWithinAspectRatio || changes.canvasRotation)) { this.loadImageService .transformLoadedImage(this.loadedImage, this.settings) .then((res) => this.setLoadedImage(res)) .catch((err) => this.loadImageError(err)); } if (changes.cropper || changes.maintainAspectRatio || changes.aspectRatio) { this.setMaxSize(); this.setCropperScaledMinSize(); this.setCropperScaledMaxSize(); if (this.maintainAspectRatio && (changes.maintainAspectRatio || changes.aspectRatio)) { this.resetCropperPosition(); } else if (changes.cropper) { this.checkCropperPosition(false); this.doAutoCrop(); } this.cd.markForCheck(); } if (changes.transform) { this.transform = this.transform || {}; this.setCssTransform(); this.doAutoCrop(); } } onChangesUpdateSettings(changes) { this.settings.setOptionsFromChanges(changes); if (this.settings.cropperStaticHeight && this.settings.cropperStaticWidth) { this.settings.setOptions({ hideResizeSquares: true, cropperMinWidth: this.settings.cropperStaticWidth, cropperMinHeight: this.settings.cropperStaticHeight, cropperMaxHeight: this.settings.cropperStaticHeight, cropperMaxWidth: this.settings.cropperStaticWidth, maintainAspectRatio: false }); } } onChangesInputImage(changes) { if (changes.imageChangedEvent || changes.imageURL || changes.imageBase64 || changes.imageFile) { this.reset(); } if (changes.imageChangedEvent && this.isValidImageChangedEvent()) { this.loadImageFile(this.imageChangedEvent.target.files[0]); } if (changes.imageURL && this.imageURL) { this.loadImageFromURL(this.imageURL); } if (changes.imageBase64 && this.imageBase64) { this.loadBase64Image(this.imageBase64); } if (changes.imageFile && this.imageFile) { this.loadImageFile(this.imageFile); } } isValidImageChangedEvent() { return this.imageChangedEvent?.target?.files?.length > 0; } setCssTransform() { this.safeTransformStyle = this.sanitizer.bypassSecurityTrustStyle('scaleX(' + (this.transform.scale || 1) * (this.transform.flipH ? -1 : 1) + ')' + 'scaleY(' + (this.transform.scale || 1) * (this.transform.flipV ? -1 : 1) + ')' + 'rotate(' + (this.transform.rotate || 0) + 'deg)' + `translate(${this.transform.translateH || 0}%, ${this.transform.translateV || 0}%)`); } ngOnInit() { this.settings.stepSize = this.initialStepSize; this.activatePinchGesture(); } reset() { this.imageVisible = false; this.loadedImage = undefined; this.safeImgDataUrl = 'data:image/png;base64,iVBORw0KGg' + 'oAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAU' + 'AAarVyFEAAAAASUVORK5CYII='; this.moveStart = { active: false, type: null, position: null, x1: 0, y1: 0, x2: 0, y2: 0, clientX: 0, clientY: 0 }; this.maxSize = { width: 0, height: 0 }; this.cropper.x1 = -100; this.cropper.y1 = -100; this.cropper.x2 = 10000; this.cropper.y2 = 10000; } loadImageFile(file) { this.loadImageService .loadImageFile(file, this.settings) .then((res) => this.setLoadedImage(res)) .catch((err) => this.loadImageError(err)); } loadBase64Image(imageBase64) { this.loadImageService .loadBase64Image(imageBase64, this.settings) .then((res) => this.setLoadedImage(res)) .catch((err) => this.loadImageError(err)); } loadImageFromURL(url) { this.loadImageService .loadImageFromURL(url, this.settings) .then((res) => this.setLoadedImage(res)) .catch((err) => this.loadImageError(err)); } setLoadedImage(loadedImage) { this.loadedImage = loadedImage; this.safeImgDataUrl = this.sanitizer.bypassSecurityTrustResourceUrl(loadedImage.transformed.base64); this.cd.markForCheck(); } loadImageError(error) { console.error(error); this.loadImageFailed.emit(); } imageLoadedInView() { if (this.loadedImage != null) { this.imageLoaded.emit(this.loadedImage); this.setImageMaxSizeRetries = 0; setTimeout(() => this.checkImageMaxSizeRecursively()); } } checkImageMaxSizeRecursively() { if (this.setImageMaxSizeRetries > 40) { this.loadImageFailed.emit(); } else if (this.sourceImageLoaded()) { this.setMaxSize(); this.setCropperScaledMinSize(); this.setCropperScaledMaxSize(); this.resetCropperPosition(); this.cropperReady.emit({ ...this.maxSize }); this.cd.markForCheck(); } else { this.setImageMaxSizeRetries++; setTimeout(() => this.checkImageMaxSizeRecursively(), 50); } } sourceImageLoaded() { return this.sourceImage?.nativeElement?.offsetWidth > 0; } onResize() { if (!this.loadedImage) { return; } this.resizeCropperPosition(); this.setMaxSize(); this.setCropperScaledMinSize(); this.setCropperScaledMaxSize(); } activatePinchGesture() { if (this.Hammer) { const hammer = new this.Hammer(this.wrapper.nativeElement); hammer.get('pinch').set({ enable: true }); hammer.on('pinchmove', this.onPinch.bind(this)); hammer.on('pinchend', this.pinchStop.bind(this)); hammer.on('pinchstart', this.startPinch.bind(this)); } else if (isDevMode()) { console.warn('[AswImageCropper] Could not find HammerJS - Pinch Gesture won\'t work'); } } resizeCropperPosition() { const sourceImageElement = this.sourceImage.nativeElement; if (this.maxSize.width !== sourceImageElement.offsetWidth || this.maxSize.height !== sourceImageElement.offsetHeight) { this.cropper.x1 = this.cropper.x1 * sourceImageElement.offsetWidth / this.maxSize.width; this.cropper.x2 = this.cropper.x2 * sourceImageElement.offsetWidth / this.maxSize.width; this.cropper.y1 = this.cropper.y1 * sourceImageElement.offsetHeight / this.maxSize.height; this.cropper.y2 = this.cropper.y2 * sourceImageElement.offsetHeight / this.maxSize.height; } } resetCropperPosition() { this.cropperPositionService.resetCropperPosition(this.sourceImage, this.cropper, this.settings); this.doAutoCrop(); this.imageVisible = true; } keyboardAccess(event) { this.changeKeyboardStepSize(event); this.keyboardMoveCropper(event); } changeKeyboardStepSize(event) { const key = +event.key; if (key >= 1 && key <= 9) { this.settings.stepSize = key; } } keyboardMoveCropper(event) { const keyboardWhiteList = ['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft']; if (!(keyboardWhiteList.includes(event.key))) { return; } const moveType = event.shiftKey ? MoveTypes.Resize : MoveTypes.Move; const position = event.altKey ? getInvertedPositionForKey(event.key) : getPositionForKey(event.key); const moveEvent = getEventForKey(event.key, this.settings.stepSize); event.preventDefault(); event.stopPropagation(); this.startMove({ clientX: 0, clientY: 0 }, moveType, position); this.moveImg(moveEvent); this.moveStop(); } startMove(event, moveType, position = null) { if (this.moveStart?.active && this.moveStart?.type === MoveTypes.Pinch) { return; } if (event.preventDefault) { event.preventDefault(); } this.moveStart = { active: true, type: moveType, position, clientX: this.cropperPositionService.getClientX(event), clientY: this.cropperPositionService.getClientY(event), ...this.cropper };