@asoftwareworld/form-builder-pro
Version:
ASW Form Builder Pro 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 b
1,072 lines (1,068 loc) • 73.7 kB
JavaScript
import * as i5 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, isDevMode, HostListener, Output, HostBinding, Input, ViewChild, Optional, Inject, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import * as i4 from '@angular/platform-browser';
import { HAMMER_LOADER } from '@angular/platform-browser';
import { CropperSettings, MoveTypes } from '@asoftwareworld/form-builder-pro/api';
import { resizeCanvas, percentage, supportsAutomaticRotation, getTransformationsFromExifData, getInvertedPositionForKey, getPositionForKey, getEventForKey } from '@asoftwareworld/form-builder-pro/utils';
import { merge, fromEvent, takeUntil, first } from 'rxjs';
/**
* @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(loadedImage, cropper, settings, output, maxSize) {
const imagePosition = this.getImagePosition(loadedImage, cropper, settings, maxSize);
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 { translateH, translateV } = this.getCanvasTranslate(loadedImage, settings, maxSize);
const transformedImage = loadedImage.transformed;
ctx.setTransform(scaleX, 0, 0, scaleY, transformedImage.size.width / 2 + translateH, transformedImage.size.height / 2 + translateV);
ctx.translate(-imagePosition.x1 / scaleX, -imagePosition.y1 / scaleY);
ctx.rotate(((settings.transform.rotate || 0) * Math.PI) / 180);
ctx.drawImage(transformedImage.image, -transformedImage.size.width / 2, -transformedImage.size.height / 2);
const result = {
width,
height,
imagePosition,
cropperPosition: { ...cropper }
};
if (settings.containWithinAspectRatio) {
result.offsetImagePosition = this.getOffsetImagePosition(loadedImage, cropper, settings, maxSize);
}
const resizeRatio = this.getResizeRatio(width, height, settings);
if (resizeRatio !== 1) {
result.width = Math.round(width * resizeRatio);
result.height = settings.maintainAspectRatio ? Math.round(result.width / settings.aspectRatio) : Math.round(height * resizeRatio);
resizeCanvas(cropCanvas, result.width, result.height);
}
if (output === 'blob') {
return this.cropToBlob(result, cropCanvas, settings);
}
else {
result.base64 = cropCanvas.toDataURL('image/' + settings.format, this.getQuality(settings));
return result;
}
}
async cropToBlob(output, cropCanvas, settings) {
output.blob = await new Promise((resolve) => cropCanvas.toBlob(resolve, 'image/' + settings.format, this.getQuality(settings)));
if (output.blob) {
output.objectUrl = URL.createObjectURL(output.blob);
}
return output;
}
getCanvasTranslate(loadedImage, settings, maxSize) {
if (settings.transform.translateUnit === 'px') {
const ratio = this.getRatio(loadedImage, maxSize);
return {
translateH: (settings.transform.translateH || 0) * ratio,
translateV: (settings.transform.translateV || 0) * ratio
};
}
else {
return {
translateH: settings.transform.translateH ? percentage(settings.transform.translateH, loadedImage.transformed.size.width) : 0,
translateV: settings.transform.translateV ? percentage(settings.transform.translateV, loadedImage.transformed.size.height) : 0
};
}
}
getRatio(loadedImage, maxSize) {
return loadedImage.transformed.size.width / maxSize.width;
}
getImagePosition(loadedImage, cropper, settings, maxSize) {
const ratio = this.getRatio(loadedImage, maxSize);
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(loadedImage, cropper, settings, maxSize) {
const canvasRotation = settings.canvasRotation + loadedImage.exifTransform.rotate;
const ratio = this.getRatio(loadedImage, maxSize);
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.8", ngImport: i0, type: CropService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: CropService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", 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, maxSize) {
if (!sourceImage?.nativeElement) {
return;
}
if (settings.cropperStaticHeight && settings.cropperStaticWidth) {
cropperPosition.x1 = 0;
cropperPosition.x2 = maxSize.width > settings.cropperStaticWidth ? settings.cropperStaticWidth : maxSize.width;
cropperPosition.y1 = 0;
cropperPosition.y2 = maxSize.height > settings.cropperStaticHeight ? settings.cropperStaticHeight : maxSize.height;
}
else {
const cropperWidth = Math.min(settings.cropperScaledMaxWidth, maxSize.width);
const cropperHeight = Math.min(settings.cropperScaledMaxHeight, maxSize.height);
if (!settings.maintainAspectRatio) {
cropperPosition.x1 = 0;
cropperPosition.x2 = cropperWidth;
cropperPosition.y1 = 0;
cropperPosition.y2 = cropperHeight;
}
else if (maxSize.width / settings.aspectRatio < maxSize.height) {
cropperPosition.x1 = 0;
cropperPosition.x2 = cropperWidth;
const cropperHeightWithAspectRatio = cropperWidth / settings.aspectRatio;
cropperPosition.y1 = (maxSize.height - cropperHeightWithAspectRatio) / 2;
cropperPosition.y2 = cropperPosition.y1 + cropperHeightWithAspectRatio;
}
else {
cropperPosition.y1 = 0;
cropperPosition.y2 = cropperHeight;
const cropperWidthWithAspectRatio = cropperHeight * settings.aspectRatio;
cropperPosition.x1 = (maxSize.width - 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) {
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.8", ngImport: i0, type: CropperPositionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: CropperPositionService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", 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
*/
class LoadImageService {
autoRotateSupported = supportsAutomaticRotation();
loadImageFile(file, cropperSettings) {
return file.arrayBuffer().then((arrayBuffer) => this.checkImageTypeAndLoadImageFromArrayBuffer(arrayBuffer, file.type, cropperSettings));
}
checkImageTypeAndLoadImageFromArrayBuffer(arrayBuffer, imageType, cropperSettings) {
if (!this.isValidImageType(imageType)) {
return Promise.reject(new Error('Invalid image type'));
}
return this.loadImageFromArrayBuffer(arrayBuffer, cropperSettings);
}
isValidImageType(type) {
return /image\/(png|jpg|jpeg|bmp|gif|tiff|webp|x-icon|vnd.microsoft.icon)/.test(type);
}
loadImageFromURL(url, cropperSettings) {
return fetch(url)
.then((res) => res.arrayBuffer())
.then((buffer) => this.loadImageFromArrayBuffer(buffer, cropperSettings));
}
loadBase64Image(imageBase64, cropperSettings) {
const arrayBuffer = this.base64ToArrayBuffer(imageBase64);
return this.loadImageFromArrayBuffer(arrayBuffer, cropperSettings);
}
base64ToArrayBuffer(imageBase64) {
imageBase64 = imageBase64.replace(/^data\:([^\;]+)\;base64,/gim, '');
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;
}
loadImageFromArrayBuffer(arrayBuffer, cropperSettings) {
return new Promise((resolve, reject) => {
const blob = new Blob([arrayBuffer]);
const objectUrl = URL.createObjectURL(blob);
const originalImage = new Image();
originalImage.onload = () => resolve({
originalImage,
originalObjectUrl: objectUrl,
originalArrayBuffer: arrayBuffer
});
originalImage.onerror = reject;
originalImage.src = objectUrl;
}).then((res) => this.transformImageFromArrayBuffer(res, cropperSettings));
}
async transformImageFromArrayBuffer(res, cropperSettings) {
const autoRotate = await this.autoRotateSupported;
const exifTransform = await getTransformationsFromExifData(autoRotate ? -1 : res.originalArrayBuffer);
if (!res.originalImage || !res.originalImage.complete) {
return Promise.reject(new Error('No image loaded'));
}
const loadedImage = {
original: {
objectUrl: res.originalObjectUrl,
image: res.originalImage,
size: {
width: res.originalImage.naturalWidth,
height: res.originalImage.naturalHeight
}
},
exifTransform
};
return this.transformLoadedImage(loadedImage, cropperSettings);
}
async transformLoadedImage(loadedImage, cropperSettings) {
const canvasRotation = cropperSettings.canvasRotation + loadedImage.exifTransform.rotate;
const originalSize = {
width: loadedImage.original.image.naturalWidth,
height: loadedImage.original.image.naturalHeight
};
if (canvasRotation === 0 && !loadedImage.exifTransform.flip && !cropperSettings.containWithinAspectRatio) {
return {
original: {
objectUrl: loadedImage.original.objectUrl,
image: loadedImage.original.image,
size: { ...originalSize }
},
transformed: {
objectUrl: loadedImage.original.objectUrl,
image: loadedImage.original.image,
size: { ...originalSize }
},
exifTransform: loadedImage.exifTransform
};
}
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(loadedImage.exifTransform.flip ? -1 : 1, 0, 0, 1, canvas.width / 2, canvas.height / 2);
ctx?.rotate(Math.PI * (canvasRotation / 2));
ctx?.drawImage(loadedImage.original.image, -originalSize.width / 2, -originalSize.height / 2);
const blob = await new Promise((resolve) => canvas.toBlob(resolve, cropperSettings.format));
if (!blob) {
throw new Error('Failed to get Blob for transformed image.');
}
const objectUrl = URL.createObjectURL(blob);
const transformedImage = await this.loadImageFromObjectUrl(objectUrl);
return {
original: {
objectUrl: loadedImage.original.objectUrl,
image: loadedImage.original.image,
size: { ...originalSize }
},
transformed: {
objectUrl: objectUrl,
image: transformedImage,
size: {
width: transformedImage.width,
height: transformedImage.height
}
},
exifTransform: loadedImage.exifTransform
};
}
loadImageFromObjectUrl(objectUrl) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = reject;
image.src = objectUrl;
});
}
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.8", ngImport: i0, type: LoadImageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: LoadImageService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", 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;
zone;
hammerLoader;
settings = new CropperSettings();
setImageMaxSizeRetries = 0;
moveStart;
loadedImage;
resizedWhileHidden = false;
safeImgDataUrl;
safeTransformStyle;
marginLeft = '0px';
maxSize = {
width: 0,
height: 0
};
moveTypes = MoveTypes;
imageVisible = false;
wrapper;
sourceImage;
imageChangedEvent;
imageURL;
imageBase64;
imageFile;
imageAltText;
cropperFrameAriaLabel = this.settings.cropperFrameAriaLabel;
output = this.settings.output;
format = this.settings.format;
transform = {};
maintainAspectRatio = this.settings.maintainAspectRatio;
aspectRatio = this.settings.aspectRatio;
resetCropOnAspectRatioChange = this.settings.resetCropOnAspectRatioChange;
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;
allowMoveImage = false;
cropper = {
x1: -100,
y1: -100,
x2: 10000,
y2: 10000
};
alignImage = this.settings.alignImage;
disabled = false;
hidden = false;
imageCropped = new EventEmitter();
startCropImage = new EventEmitter();
imageLoaded = new EventEmitter();
cropperReady = new EventEmitter();
loadImageFailed = new EventEmitter();
transformChange = new EventEmitter();
constructor(cropService, cropperPositionService, loadImageService, sanitizer, cd, zone, hammerLoader) {
this.cropService = cropService;
this.cropperPositionService = cropperPositionService;
this.loadImageService = loadImageService;
this.sanitizer = sanitizer;
this.cd = cd;
this.zone = zone;
this.hammerLoader = hammerLoader;
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 && (this.resetCropOnAspectRatioChange || !this.aspectRatioIsCorrect()) && (changes['maintainAspectRatio'] || changes['aspectRatio'])) {
this.resetCropperPosition();
}
else if (changes['cropper']) {
this.checkCropperPosition(false);
this.doAutoCrop();
}
}
if (changes['transform']) {
this.transform = this.transform || {};
this.setCssTransform();
this.doAutoCrop();
}
if (changes['hidden'] && this.resizedWhileHidden && !this.hidden) {
setTimeout(() => {
this.onResize();
this.resizedWhileHidden = false;
});
}
}
onChangesUpdateSettings(changes) {
this.settings.setOptionsFromChanges(changes);
if (this.settings.cropperStaticHeight && this.settings.cropperStaticWidth) {
this.hideResizeSquares = true;
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() {
const translateUnit = this.transform?.translateUnit || '%';
this.safeTransformStyle = this.sanitizer.bypassSecurityTrustStyle(`translate(${this.transform.translateH || 0}${translateUnit}, ${this.transform.translateV || 0}${translateUnit})` +
' 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)');
}
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.objectUrl);
this.cd.markForCheck();
}
loadImageError(error) {
console.log(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;
}
if (this.hidden) {
this.resizedWhileHidden = true;
}
else {
const oldMaxSize = { ...this.maxSize };
this.setMaxSize();
this.resizeCropperPosition(oldMaxSize);
this.setCropperScaledMinSize();
this.setCropperScaledMaxSize();
}
}
async activatePinchGesture() {
// Loads HammerJS via angular APIs if configured
await this.hammerLoader?.();
const Hammer = window?.['Hammer'] || null;
if (Hammer) {
const hammer = new 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(oldMaxSize) {
if (oldMaxSize.width !== this.maxSize.width || oldMaxSize.height !== this.maxSize.height) {
this.cropper.x1 = (this.cropper.x1 * this.maxSize.width) / oldMaxSize.width;
this.cropper.x2 = (this.cropper.x2 * this.maxSize.width) / oldMaxSize.width;
this.cropper.y1 = (this.cropper.y1 * this.maxSize.height) / oldMaxSize.height;
this.cropper.y2 = (this.cropper.y2 * this.maxSize.height) / oldMaxSize.height;
}
}
resetCropperPosition() {
this.cropperPositionService.resetCropperPosition(this.sourceImage, this.cropper, this.settings, this.maxSize);
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.handleMouseMove(moveEvent);
this.handleMouseUp();
}
startMove(event, moveType, position = null) {
if (this.disabled || (this.moveStart?.active && this.moveStart?.type === MoveTypes.Pinch) || (moveType === MoveTypes.Drag && !this.allowMoveImage)) {
return;
}
if (event.preventDefault) {
event.preventDefault();
}
this.moveStart = {
active: true,
type: moveType,
position,
transform: { ...this.transform },
clientX: this.cropperPositionService.getClientX(event),
clientY: this.cropperPositionService.getClientY(event),
...this.cropper
};
this.initMouseMove();
}
initMouseMove() {
merge(fromEvent(document, 'mousemove'), fromEvent(document, 'touchmove'))
.pipe(takeUntil(merge(fromEvent(document, 'mouseup'), fromEvent(document, 'touchend')).pipe(first())))
.subscribe({
next: (event) => this.zone.run(() => {
this.handleMouseMove(event);
this.cd.markForCheck();
}),
complete: () => this.zone.run(() => {
this.handleMouseUp();
this.cd.markForCheck();
})
});
}
startPinch(event) {
if (!this.safeImgDataUrl) {
return;
}
if (event.preventDefault) {
event.preventDefault();
}
this.moveStart = {
active: true,
type: MoveTypes.Pinch,
position: 'center',
clientX: this.cropper.x1 + (this.cropper.x2 - this.cropper.x1) / 2,
clientY: this.cropper.y1 + (this.cropper.y2 - this.cropper.y1) / 2,
...this.cropper
};
}
handleMouseMove(event) {
if (this.moveStart.active) {
if (event.stopPropagation) {
event.stopPropagation();
}
if (event.preventDefault) {
event.preventDefault();
}
if (this.moveStart.type === MoveTypes.Move) {
this.cropperPositionService.move(event, this.moveStart, this.cropper);
this.checkCropperPosition(true);
}
else if (this.moveStart.type === MoveTypes.Resize) {
if (!this.cropperStaticWidth && !this.cropperStaticHeight) {
this.cropperPositionService.resize(event, this.moveStart, this.cropper, this.maxSize, this.settings);
}
this.checkCropperPosition(false);
}
else if (this.moveStart.type === MoveTypes.Drag) {
const diffX = this.cropperPositionService.getClientX(event) - this.moveStart.clientX;
const diffY = this.cropperPositionService.getClientY(event) - this.moveStart.clientY;
this.transform = {
...this.transform,
translateH: (this.moveStart.transform?.translateH || 0) + diffX,
translateV: (this.moveStart.transform?.translateV || 0) + diffY
};
this.setCssTransform();
}
}
}
onPinch(event) {
if (this.moveStart.active) {
if (event.stopPropagation) {
event.stopPropagation();
}
if (event.preventDefault) {
event.preventDefault();
}
if (this.moveStart.type === MoveTypes.Pinch) {
this.cropperPositionService.resize(event, this.moveStart, this.cropper, this.maxSize, this.settings);
this.checkCropperPosition(false);
}
this.cd.markForCheck();
}
}
setMaxSize() {
if (this.sourceImage) {
const sourceImageStyle = getComputedStyle(this.sourceImage.nativeElement);
this.maxSize.width = parseFloat(sourceImageStyle.width);
this.maxSize.height = parseFloat(sourceImageStyle.height);
this.marginLeft = this.sanitizer.bypassSecurityTrustStyle('calc(50% - ' + this.maxSize.width / 2 + 'px)');
}
}
setCropperScaledMinSize() {
if (this.loadedImage?.transformed?.image) {
this.setCropperScaledMinWidth();
this.setCropperScaledMinHeight();
}
else {
this.settings.cropperScaledMinWidth = 20;
this.settings.cropperScaledMinHeight = 20;
}
}
setCropperScaledMinWidth() {
this.settings.cropperScaledMinWidth = this.cropperMinWidth > 0 ? Math.max(20, (this.cropperMinWidth / this.loadedImage.transformed.image.width) * this.maxSize.width) : 20;
}
setCropperScaledMinHeight() {
if (this.maintainAspectRatio) {
this.settings.cropperScaledMinHeight = Math.max(20, this.settings.cropperScaledMinWidth / this.aspectRatio);
}
else if (this.cropperMinHeight > 0) {
this.settings.cropperScaledMinHeight = Math.max(20, (this.cropperMinHeight / this.loadedImage.transformed.image.height) * this.maxSize.height);
}
else {
this.settings.cropperScaledMinHeight = 20;
}
}
setCropperScaledMaxSize() {
if (this.loadedImage?.transformed?.image) {
const ratio = this.loadedImage.transformed.size.width / this.maxSize.width;
this.settings.cropperScaledMaxWidth = this.cropperMaxWidth > 20 ? this.cropperMaxWidth / ratio : this.maxSize.width;
this.settings.cropperScaledMaxHeight = this.cropperMaxHeight > 20 ? this.cropperMaxHeight / ratio : this.maxSize.height;
if (this.maintainAspectRatio) {
if (this.settings.cropperScaledMaxWidth > this.settings.cropperScaledMaxHeight * this.aspectRatio) {
this.settings.cropperScaledMaxWidth = this.settings.cropperScaledMaxHeight * this.aspectRatio;
}
else if (this.settings.cropperScaledMaxWidth < this.settings.cropperScaledMaxHeight * this.aspectRatio) {
this.settings.cropperScaledMaxHeight = this.settings.cropperScaledMaxWidth / this.aspectRatio;
}
}
}
else {
this.settings.cropperScaledMaxWidth = this.maxSize.width;
this.settings.cropperScaledMaxHeight = this.maxSize.height;
}
}
checkCropperPosition(maintainSize = false) {
if (this.cropper.x1 < 0) {
this.cropper.x2 -= maintainSize ? this.cropper.x1 : 0;
this.cropper.x1 = 0;
}
if (this.cropper.y1 < 0) {
this.cropper.y2 -= maintainSize ? this.cropper.y1 : 0;
this.cropper.y1 = 0;
}
if (this.cropper.x2 > this.maxSize.width) {
this.cropper.x1 -= maintainSize ? this.cropper.x2 - this.maxSize.width : 0;
this.cropper.x2 = this.maxSize.width;
}
if (this.cropper.y2 > this.maxSize.height) {
this.cropper.y1 -= maintainSize ? this.cropper.y2 - this.maxSize.height : 0;
this.cropper.y2 = this.maxSize.height;
}
}
handleMouseUp() {
if (this.moveStart.active) {
this.moveStart.active = false;
if (this.moveStart?.type === MoveTypes.Drag) {
this.transformChange.emit(this.transform);
}
else {
this.doAutoCrop();
}
}
}
pinchStop() {
if (this.moveStart.active) {
this.moveStart.active = false;
this.doAutoCrop();
}
}
doAutoCrop() {
if (this.autoCrop) {
void this.crop();
}
}
crop(output = this.settings.output) {
if (this.loadedImage?.transformed?.image != null) {
this.startCropImage.emit();
if (output === 'blob') {
return this.cropToBlob();
}
else if (output === 'base64') {
return this.cropToBase64();
}
}
return null;
}
cropToBlob() {
return new Promise((resolve, reject) => this.zone.run(async () => {
const result = await this.cropService.crop(this.loadedImage, this.cropper, this.settings, 'blob', this.maxSize);
if (result) {
this.imageCropped.emit(result);
resolve(result);
}
else {
reject('Crop image failed');
}
}));
}
cropToBase64() {
const result = this.cropService.crop(this.loadedImage, this.cropper, this.settings, 'base64', this.maxSize);
if (result) {
this.imageCropped.emit(result);
return result;
}
return null;
}
aspectRatioIsCorrect() {
const currentCropAspectRatio = (this.cropper.x2 - this.cropper.x1) / (this.cropper.y2 - this.cropper.y1);
return currentCropAspectRatio === this.aspectRatio;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: AswImageCropComponent, deps: [{ token: CropService }, { token: CropperPositionService }, { token: LoadImageService }, { token: i4.DomSanitizer }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: HAMMER_LOADER, optional: true }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.8", t