UNPKG

ngx-image-cropper-upgraded

Version:
1,186 lines 170 kB
/** * @fileoverview added by tsickle * Generated from: lib/component/image-cropper.component.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, isDevMode, Output, ViewChild } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { getTransformationsFromExifData, supportsAutomaticRotation } from '../utils/exif.utils'; import { resizeCanvas } from '../utils/resize.utils'; import { MoveTypes } from '../interfaces/move-start.interface'; export class ImageCropperComponent { /** * @param {?} sanitizer * @param {?} cd */ constructor(sanitizer, cd) { this.sanitizer = sanitizer; this.cd = cd; this.Hammer = typeof window !== 'undefined' ? (/** @type {?} */ (((/** @type {?} */ (window))).Hammer)) : null; this.setImageMaxSizeRetries = 0; this.cropperScaledMinWidth = 20; this.cropperScaledMinHeight = 20; this.cropperScaledMaxWidth = 20; this.cropperScaledMaxHeight = 20; this.exifTransform = { rotate: 0, flip: false }; this.autoRotateSupported = supportsAutomaticRotation(); this.stepSize = 3; this.marginLeft = '0px'; this.imageVisible = false; this.moveTypes = MoveTypes; this.format = 'png'; this.maintainAspectRatio = true; this.transform = {}; this.aspectRatio = 1; this.resizeToWidth = 0; this.resizeToHeight = 0; this.cropperMinWidth = 0; this.cropperMinHeight = 0; this.cropperMaxHeight = 0; this.cropperMaxWidth = 0; this.cropperStaticWidth = 0; this.cropperStaticHeight = 0; this.canvasRotation = 0; this.initialStepSize = 3; this.roundCropper = false; this.onlyScaleDown = false; this.imageQuality = 92; this.autoCrop = true; this.containWithinAspectRatio = false; this.hideResizeSquares = false; this.cropper = { x1: -100, y1: -100, x2: 10000, y2: 10000 }; this.alignImage = 'center'; this.disabled = false; this.imageCropped = new EventEmitter(); this.startCropImage = new EventEmitter(); this.imageLoaded = new EventEmitter(); this.cropperReady = new EventEmitter(); this.loadImageFailed = new EventEmitter(); this.initCropper(); } /** * @param {?} changes * @return {?} */ ngOnChanges(changes) { if (this.cropperStaticHeight && this.cropperStaticWidth) { this.hideResizeSquares = true; this.cropperMinWidth = this.cropperStaticWidth; this.cropperMinHeight = this.cropperStaticHeight; this.cropperMaxHeight = this.cropperStaticHeight; this.cropperMaxWidth = this.cropperStaticWidth; this.maintainAspectRatio = false; } this.onChangesInputImage(changes); if (this.originalImage && this.originalImage.complete && this.exifTransform && (changes.containWithinAspectRatio || changes.canvasRotation)) { this.transformOriginalImage(); } if (changes.cropper) { this.setMaxSize(); this.setCropperScaledMinSize(); this.setCropperScaledMaxSize(); this.checkCropperPosition(false); this.doAutoCrop(); this.cd.markForCheck(); } if (changes.aspectRatio && this.imageVisible) { this.resetCropperPosition(); } if (changes.transform) { this.transform = this.transform || {}; this.setCssTransform(); this.doAutoCrop(); } } /** * @private * @param {?} changes * @return {?} */ onChangesInputImage(changes) { if (changes.imageChangedEvent || changes.imageURL || changes.imageBase64 || changes.imageFile) { this.initCropper(); } 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); } } /** * @private * @return {?} */ isValidImageChangedEvent() { return this.imageChangedEvent && this.imageChangedEvent.target && this.imageChangedEvent.target.files && this.imageChangedEvent.target.files.length > 0; } /** * @private * @return {?} */ 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)'); } /** * @return {?} */ ngOnInit() { this.stepSize = this.initialStepSize; this.activatePinchGesture(); } /** * @private * @return {?} */ initCropper() { this.imageVisible = false; this.transformedImage = null; 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.originalSize = { width: 0, height: 0 }; this.transformedSize = { width: 0, height: 0 }; this.cropper.x1 = -100; this.cropper.y1 = -100; this.cropper.x2 = 10000; this.cropper.y2 = 10000; } /** * @private * @param {?} imageBase64 * @param {?} imageType * @return {?} */ loadImage(imageBase64, imageType) { if (this.isValidImageType(imageType)) { this.loadBase64Image(imageBase64); } else { this.loadImageFailed.emit(); } } /** * @private * @param {?} file * @return {?} */ loadImageFile(file) { /** @type {?} */ const fileReader = new FileReader(); fileReader.onload = (/** * @param {?} event * @return {?} */ (event) => this.loadImage(event.target.result, file.type)); fileReader.readAsDataURL(file); } /** * @private * @param {?} type * @return {?} */ isValidImageType(type) { return /image\/(png|jpg|jpeg|bmp|gif|tiff|webp)/.test(type); } /** * @private * @param {?} imageBase64 * @return {?} */ loadBase64Image(imageBase64) { this.autoRotateSupported .then((/** * @param {?} supported * @return {?} */ (supported) => this.checkExifAndLoadBase64Image(imageBase64, supported))) .then((/** * @return {?} */ () => this.transformOriginalImage())) .catch((/** * @param {?} error * @return {?} */ (error) => { this.loadImageFailed.emit(); this.originalImage = null; this.originalBase64 = null; console.error(error); })); } /** * @private * @param {?} imageBase64 * @param {?} autoRotateSupported * @return {?} */ checkExifAndLoadBase64Image(imageBase64, autoRotateSupported) { return new Promise((/** * @param {?} resolve * @param {?} reject * @return {?} */ (resolve, reject) => { this.originalImage = new Image(); this.originalImage.onload = (/** * @return {?} */ () => { this.originalBase64 = imageBase64; this.exifTransform = getTransformationsFromExifData(autoRotateSupported ? -1 : imageBase64); this.originalSize.width = this.originalImage.naturalWidth; this.originalSize.height = this.originalImage.naturalHeight; resolve(); }); this.originalImage.onerror = reject; this.originalImage.src = imageBase64; })); } /** * @private * @param {?} url * @return {?} */ loadImageFromURL(url) { /** @type {?} */ const img = new Image(); img.onerror = (/** * @return {?} */ () => this.loadImageFailed.emit()); img.onload = (/** * @return {?} */ () => { /** @type {?} */ const canvas = document.createElement('canvas'); /** @type {?} */ const context = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; context.drawImage(img, 0, 0); this.loadBase64Image(canvas.toDataURL()); }); img.crossOrigin = 'anonymous'; img.src = url; } /** * @private * @return {?} */ transformOriginalImage() { if (!this.originalImage || !this.originalImage.complete || !this.exifTransform) { return Promise.reject(new Error('No image loaded')); } /** @type {?} */ const transformedBase64 = this.transformImageBase64(); return this.setTransformedImage(transformedBase64); } /** * @private * @return {?} */ transformImageBase64() { /** @type {?} */ const canvasRotation = this.canvasRotation + this.exifTransform.rotate; if (canvasRotation === 0 && !this.exifTransform.flip && !this.containWithinAspectRatio) { return this.originalBase64; } /** @type {?} */ const transformedSize = this.getTransformedSize(); /** @type {?} */ const canvas = document.createElement('canvas'); canvas.width = transformedSize.width; canvas.height = transformedSize.height; /** @type {?} */ const ctx = canvas.getContext('2d'); ctx.setTransform(this.exifTransform.flip ? -1 : 1, 0, 0, 1, canvas.width / 2, canvas.height / 2); ctx.rotate(Math.PI * (canvasRotation / 2)); ctx.drawImage(this.originalImage, -this.originalSize.width / 2, -this.originalSize.height / 2); return canvas.toDataURL(); } /** * @private * @return {?} */ getTransformedSize() { /** @type {?} */ const canvasRotation = this.canvasRotation + this.exifTransform.rotate; if (this.containWithinAspectRatio) { if (canvasRotation % 2) { /** @type {?} */ const minWidthToContain = this.originalSize.width * this.aspectRatio; /** @type {?} */ const minHeightToContain = this.originalSize.height / this.aspectRatio; return { width: Math.max(this.originalSize.height, minWidthToContain), height: Math.max(this.originalSize.width, minHeightToContain), }; } else { /** @type {?} */ const minWidthToContain = this.originalSize.height * this.aspectRatio; /** @type {?} */ const minHeightToContain = this.originalSize.width / this.aspectRatio; return { width: Math.max(this.originalSize.width, minWidthToContain), height: Math.max(this.originalSize.height, minHeightToContain), }; } } if (canvasRotation % 2) { return { height: this.originalSize.width, width: this.originalSize.height, }; } return { width: this.originalSize.width, height: this.originalSize.height, }; } /** * @private * @param {?} transformedBase64 * @return {?} */ setTransformedImage(transformedBase64) { return new Promise((/** * @param {?} resolve * @return {?} */ (resolve) => { this.transformedBase64 = transformedBase64; this.safeImgDataUrl = this.sanitizer.bypassSecurityTrustResourceUrl(transformedBase64); this.transformedImage = new Image(); this.transformedImage.onload = (/** * @return {?} */ () => { this.transformedSize.width = this.transformedImage.naturalWidth; this.transformedSize.height = this.transformedImage.naturalHeight; this.cd.markForCheck(); resolve(); }); this.transformedImage.src = this.transformedBase64; })); } /** * @return {?} */ imageLoadedInView() { if (this.transformedImage != null) { this.imageLoaded.emit(this.transformedImage); this.setImageMaxSizeRetries = 0; setTimeout((/** * @return {?} */ () => this.checkImageMaxSizeRecursively())); } } /** * @private * @return {?} */ checkImageMaxSizeRecursively() { if (this.setImageMaxSizeRetries > 40) { this.loadImageFailed.emit(); } else if (this.sourceImageLoaded()) { this.setMaxSize(); this.setCropperScaledMinSize(); this.setCropperScaledMaxSize(); this.resetCropperPosition(); this.cropperReady.emit(Object.assign({}, this.maxSize)); this.cd.markForCheck(); } else { this.setImageMaxSizeRetries++; setTimeout((/** * @return {?} */ () => this.checkImageMaxSizeRecursively()), 50); } } /** * @private * @return {?} */ sourceImageLoaded() { return this.sourceImage && this.sourceImage.nativeElement && this.sourceImage.nativeElement.offsetWidth > 0; } /** * @return {?} */ onResize() { this.resizeCropperPosition(); this.setMaxSize(); this.setCropperScaledMinSize(); this.setCropperScaledMaxSize(); } /** * @private * @return {?} */ activatePinchGesture() { if (this.Hammer) { /** @type {?} */ 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('[NgxImageCropper] Could not find HammerJS - Pinch Gesture won\'t work'); } } /** * @private * @return {?} */ resizeCropperPosition() { /** @type {?} */ 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; } } /** * @return {?} */ resetCropperPosition() { /** @type {?} */ const sourceImageElement = this.sourceImage.nativeElement; if (this.cropperStaticHeight && this.cropperStaticWidth) { this.cropper.x1 = 0; this.cropper.x2 = sourceImageElement.offsetWidth > this.cropperStaticWidth ? this.cropperStaticWidth : sourceImageElement.offsetWidth; this.cropper.y1 = 0; this.cropper.y2 = sourceImageElement.offsetHeight > this.cropperStaticHeight ? this.cropperStaticHeight : sourceImageElement.offsetHeight; } else { /** @type {?} */ const cropperWidth = Math.min(this.cropperScaledMaxWidth, sourceImageElement.offsetWidth); /** @type {?} */ const cropperHeight = Math.min(this.cropperScaledMaxHeight, sourceImageElement.offsetHeight); if (!this.maintainAspectRatio) { this.cropper.x1 = 0; this.cropper.x2 = cropperWidth; this.cropper.y1 = 0; this.cropper.y2 = cropperHeight; } else if (cropperWidth / this.aspectRatio < cropperHeight) { this.cropper.x1 = 0; this.cropper.x2 = cropperWidth; /** @type {?} */ const cropperHeightWithAspectRatio = cropperWidth / this.aspectRatio; this.cropper.y1 = (cropperHeight - cropperHeightWithAspectRatio) / 2; this.cropper.y2 = this.cropper.y1 + cropperHeightWithAspectRatio; } else { this.cropper.y1 = 0; this.cropper.y2 = cropperHeight; /** @type {?} */ const cropperWidthWithAspectRatio = cropperHeight * this.aspectRatio; this.cropper.x1 = (cropperWidth - cropperWidthWithAspectRatio) / 2; this.cropper.x2 = this.cropper.x1 + cropperWidthWithAspectRatio; } } this.doAutoCrop(); this.imageVisible = true; } /** * @param {?} event * @return {?} */ keyboardAccess(event) { this.changeKeyboardStepSize(event); this.keyboardMoveCropper(event); } /** * @private * @param {?} event * @return {?} */ changeKeyboardStepSize(event) { if (event.key >= '1' && event.key <= '9') { this.stepSize = +event.key; return; } } /** * @private * @param {?} event * @return {?} */ keyboardMoveCropper(event) { /** @type {?} */ const keyboardWhiteList = ['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft']; if (!(keyboardWhiteList.includes(event.key))) { return; } /** @type {?} */ const moveType = event.shiftKey ? MoveTypes.Resize : MoveTypes.Move; /** @type {?} */ const position = event.altKey ? this.getInvertedPositionForKey(event.key) : this.getPositionForKey(event.key); /** @type {?} */ const moveEvent = this.getEventForKey(event.key, this.stepSize); event.preventDefault(); event.stopPropagation(); this.startMove({ clientX: 0, clientY: 0 }, moveType, position); this.moveImg(moveEvent); this.moveStop(); } /** * @private * @param {?} key * @return {?} */ getPositionForKey(key) { switch (key) { case 'ArrowUp': return 'top'; case 'ArrowRight': return 'right'; case 'ArrowDown': return 'bottom'; case 'ArrowLeft': default: return 'left'; } } /** * @private * @param {?} key * @return {?} */ getInvertedPositionForKey(key) { switch (key) { case 'ArrowUp': return 'bottom'; case 'ArrowRight': return 'left'; case 'ArrowDown': return 'top'; case 'ArrowLeft': default: return 'right'; } } /** * @private * @param {?} key * @param {?} stepSize * @return {?} */ 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 }; } } /** * @param {?} event * @param {?} moveType * @param {?=} position * @return {?} */ startMove(event, moveType, position = null) { if (this.moveStart && this.moveStart.active && this.moveStart.type === MoveTypes.Pinch) { return; } if (event.preventDefault) { event.preventDefault(); } this.moveStart = Object.assign({ active: true, type: moveType, position, clientX: this.getClientX(event), clientY: this.getClientY(event) }, this.cropper); } /** * @param {?} event * @return {?} */ startPinch(event) { if (!this.safeImgDataUrl) { return; } if (event.preventDefault) { event.preventDefault(); } this.moveStart = Object.assign({ 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); } /** * @param {?} event * @return {?} */ moveImg(event) { if (this.moveStart.active) { if (event.stopPropagation) { event.stopPropagation(); } if (event.preventDefault) { event.preventDefault(); } if (this.moveStart.type === MoveTypes.Move) { this.move(event); this.checkCropperPosition(true); } else if (this.moveStart.type === MoveTypes.Resize) { if (!this.cropperStaticWidth && !this.cropperStaticHeight) { this.resize(event); } this.checkCropperPosition(false); } this.cd.detectChanges(); } } /** * @param {?} event * @return {?} */ onPinch(event) { if (this.moveStart.active) { if (event.stopPropagation) { event.stopPropagation(); } if (event.preventDefault) { event.preventDefault(); } if (this.moveStart.type === MoveTypes.Pinch) { this.resize(event); this.checkCropperPosition(false); } this.cd.detectChanges(); } } /** * @private * @return {?} */ setMaxSize() { if (this.sourceImage) { /** @type {?} */ const sourceImageElement = this.sourceImage.nativeElement; this.maxSize.width = sourceImageElement.offsetWidth; this.maxSize.height = sourceImageElement.offsetHeight; this.marginLeft = this.sanitizer.bypassSecurityTrustStyle('calc(50% - ' + this.maxSize.width / 2 + 'px)'); } } /** * @private * @return {?} */ setCropperScaledMinSize() { if (this.transformedImage) { this.setCropperScaledMinWidth(); this.setCropperScaledMinHeight(); } else { this.cropperScaledMinWidth = 20; this.cropperScaledMinHeight = 20; } } /** * @private * @return {?} */ setCropperScaledMinWidth() { this.cropperScaledMinWidth = this.cropperMinWidth > 0 ? Math.max(20, this.cropperMinWidth / this.transformedImage.width * this.maxSize.width) : 20; } /** * @private * @return {?} */ setCropperScaledMinHeight() { if (this.maintainAspectRatio) { this.cropperScaledMinHeight = Math.max(20, this.cropperScaledMinWidth / this.aspectRatio); } else if (this.cropperMinHeight > 0) { this.cropperScaledMinHeight = Math.max(20, this.cropperMinHeight / this.transformedImage.height * this.maxSize.height); } else { this.cropperScaledMinHeight = 20; } } /** * @private * @return {?} */ setCropperScaledMaxSize() { /** @type {?} */ const sourceImageElement = this.sourceImage.nativeElement; /** @type {?} */ const ratio = this.transformedSize.width / sourceImageElement.offsetWidth; this.cropperScaledMaxWidth = this.cropperMaxWidth > 20 ? this.cropperMaxWidth : this.maxSize.width; this.cropperScaledMaxHeight = this.cropperMaxHeight > 20 ? this.cropperMaxHeight : this.maxSize.height; if (ratio != 1 && (this.cropperMaxWidth > 20 || this.cropperMaxHeight > 20)) { this.cropperScaledMaxWidth = this.cropperScaledMaxWidth / ratio; this.cropperScaledMaxHeight = this.cropperScaledMaxHeight / ratio; } if (this.transformedImage && this.maintainAspectRatio) { if (this.cropperScaledMaxWidth > this.cropperScaledMaxHeight * this.aspectRatio) { this.cropperScaledMaxWidth = this.cropperScaledMaxHeight * this.aspectRatio; } else if (this.cropperScaledMaxWidth < this.cropperScaledMaxHeight * this.aspectRatio) { this.cropperScaledMaxHeight = this.cropperScaledMaxWidth / this.aspectRatio; } } } /** * @private * @param {?=} maintainSize * @return {?} */ 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; } } /** * @return {?} */ moveStop() { if (this.moveStart.active) { this.moveStart.active = false; this.doAutoCrop(); } } /** * @return {?} */ pinchStop() { if (this.moveStart.active) { this.moveStart.active = false; this.doAutoCrop(); } } /** * @private * @param {?} event * @return {?} */ move(event) { /** @type {?} */ const diffX = this.getClientX(event) - this.moveStart.clientX; /** @type {?} */ const diffY = this.getClientY(event) - this.moveStart.clientY; this.cropper.x1 = this.moveStart.x1 + diffX; this.cropper.y1 = this.moveStart.y1 + diffY; this.cropper.x2 = this.moveStart.x2 + diffX; this.cropper.y2 = this.moveStart.y2 + diffY; } /** * @private * @param {?} event * @return {?} */ resize(event) { /** @type {?} */ var diffX = this.getClientX(event) - this.moveStart.clientX; /** @type {?} */ var diffY = this.getClientY(event) - this.moveStart.clientY; switch (this.moveStart.position) { case 'left': this.cropper.x1 = Math.min(Math.max(this.moveStart.x1 + diffX, this.cropper.x2 - this.cropperScaledMaxWidth), this.cropper.x2 - this.cropperScaledMinWidth); break; case 'topleft': this.cropper.x1 = Math.min(Math.max(this.moveStart.x1 + diffX, this.cropper.x2 - this.cropperScaledMaxWidth), this.cropper.x2 - this.cropperScaledMinWidth); this.cropper.y1 = Math.min(Math.max(this.moveStart.y1 + diffY, this.cropper.y2 - this.cropperScaledMaxHeight), this.cropper.y2 - this.cropperScaledMinHeight); break; case 'top': this.cropper.y1 = Math.min(Math.max(this.moveStart.y1 + diffY, this.cropper.y2 - this.cropperScaledMaxHeight), this.cropper.y2 - this.cropperScaledMinHeight); break; case 'topright': this.cropper.x2 = Math.max(Math.min(this.moveStart.x2 + diffX, this.cropper.x1 + this.cropperScaledMaxWidth), this.cropper.x1 + this.cropperScaledMinWidth); this.cropper.y1 = Math.min(Math.max(this.moveStart.y1 + diffY, this.cropper.y2 - this.cropperScaledMaxHeight), this.cropper.y2 - this.cropperScaledMinHeight); break; case 'right': this.cropper.x2 = Math.max(Math.min(this.moveStart.x2 + diffX, this.cropper.x1 + this.cropperScaledMaxWidth), this.cropper.x1 + this.cropperScaledMinWidth); break; case 'bottomright': this.cropper.x2 = Math.max(Math.min(this.moveStart.x2 + diffX, this.cropper.x1 + this.cropperScaledMaxWidth), this.cropper.x1 + this.cropperScaledMinWidth); this.cropper.y2 = Math.max(Math.min(this.moveStart.y2 + diffY, this.cropper.y1 + this.cropperScaledMaxHeight), this.cropper.y1 + this.cropperScaledMinHeight); break; case 'bottom': this.cropper.y2 = Math.max(Math.min(this.moveStart.y2 + diffY, this.cropper.y1 + this.cropperScaledMaxHeight), this.cropper.y1 + this.cropperScaledMinHeight); break; case 'bottomleft': this.cropper.x1 = Math.min(Math.max(this.moveStart.x1 + diffX, this.cropper.x2 - this.cropperScaledMaxWidth), this.cropper.x2 - this.cropperScaledMinWidth); this.cropper.y2 = Math.max(Math.min(this.moveStart.y2 + diffY, this.cropper.y1 + this.cropperScaledMaxHeight), this.cropper.y1 + this.cropperScaledMinHeight); break; case 'center': /** @type {?} */ const scale = event.scale; /** @type {?} */ const newWidth = (Math.abs(this.moveStart.x2 - this.moveStart.x1)) * scale; /** @type {?} */ const newHeight = (Math.abs(this.moveStart.y2 - this.moveStart.y1)) * scale; /** @type {?} */ const x1 = this.cropper.x1; /** @type {?} */ const y1 = this.cropper.y1; this.cropper.x1 = Math.min(Math.max(this.moveStart.clientX - (newWidth / 2), this.cropper.x2 - this.cropperScaledMaxWidth / 2), this.cropper.x2 - this.cropperScaledMinWidth); this.cropper.y1 = Math.min(Math.max(this.moveStart.clientY - (newHeight / 2), this.cropper.y2 - this.cropperScaledMaxHeight / 2), this.cropper.y2 - this.cropperScaledMinHeight); this.cropper.x2 = Math.max(Math.min(this.moveStart.clientX + (newWidth / 2), x1 + this.cropperScaledMaxWidth / 2), x1 + this.cropperScaledMinWidth); this.cropper.y2 = Math.max(Math.min(this.moveStart.clientY + (newHeight / 2), y1 + this.cropperScaledMaxHeight / 2), y1 + this.cropperScaledMinHeight); break; } if (this.maintainAspectRatio) { this.checkAspectRatio(); } } /** * @private * @return {?} */ checkAspectRatio() { /** @type {?} */ let overflowX = 0; /** @type {?} */ let overflowY = 0; switch (this.moveStart.position) { case 'top': this.cropper.x2 = this.cropper.x1 + (this.cropper.y2 - this.cropper.y1) * this.aspectRatio; overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); overflowY = Math.max(0 - this.cropper.y1, 0); if (overflowX > 0 || overflowY > 0) { this.cropper.x2 -= (overflowY * this.aspectRatio) > overflowX ? (overflowY * this.aspectRatio) : overflowX; this.cropper.y1 += (overflowY * this.aspectRatio) > overflowX ? overflowY : overflowX / this.aspectRatio; } break; case 'bottom': this.cropper.x2 = this.cropper.x1 + (this.cropper.y2 - this.cropper.y1) * this.aspectRatio; overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); overflowY = Math.max(this.cropper.y2 - this.maxSize.height, 0); if (overflowX > 0 || overflowY > 0) { this.cropper.x2 -= (overflowY * this.aspectRatio) > overflowX ? (overflowY * this.aspectRatio) : overflowX; this.cropper.y2 -= (overflowY * this.aspectRatio) > overflowX ? overflowY : (overflowX / this.aspectRatio); } break; case 'topleft': this.cropper.y1 = this.cropper.y2 - (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; overflowX = Math.max(0 - this.cropper.x1, 0); overflowY = Math.max(0 - this.cropper.y1, 0); if (overflowX > 0 || overflowY > 0) { this.cropper.x1 += (overflowY * this.aspectRatio) > overflowX ? (overflowY * this.aspectRatio) : overflowX; this.cropper.y1 += (overflowY * this.aspectRatio) > overflowX ? overflowY : overflowX / this.aspectRatio; } break; case 'topright': this.cropper.y1 = this.cropper.y2 - (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); overflowY = Math.max(0 - this.cropper.y1, 0); if (overflowX > 0 || overflowY > 0) { this.cropper.x2 -= (overflowY * this.aspectRatio) > overflowX ? (overflowY * this.aspectRatio) : overflowX; this.cropper.y1 += (overflowY * this.aspectRatio) > overflowX ? overflowY : overflowX / this.aspectRatio; } break; case 'right': case 'bottomright': this.cropper.y2 = this.cropper.y1 + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); overflowY = Math.max(this.cropper.y2 - this.maxSize.height, 0); if (overflowX > 0 || overflowY > 0) { this.cropper.x2 -= (overflowY * this.aspectRatio) > overflowX ? (overflowY * this.aspectRatio) : overflowX; this.cropper.y2 -= (overflowY * this.aspectRatio) > overflowX ? overflowY : overflowX / this.aspectRatio; } break; case 'left': case 'bottomleft': this.cropper.y2 = this.cropper.y1 + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; overflowX = Math.max(0 - this.cropper.x1, 0); overflowY = Math.max(this.cropper.y2 - this.maxSize.height, 0); if (overflowX > 0 || overflowY > 0) { this.cropper.x1 += (overflowY * this.aspectRatio) > overflowX ? (overflowY * this.aspectRatio) : overflowX; this.cropper.y2 -= (overflowY * this.aspectRatio) > overflowX ? overflowY : overflowX / this.aspectRatio; } break; case 'center': this.cropper.x2 = this.cropper.x1 + (this.cropper.y2 - this.cropper.y1) * this.aspectRatio; this.cropper.y2 = this.cropper.y1 + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; /** @type {?} */ const overflowX1 = Math.max(0 - this.cropper.x1, 0); /** @type {?} */ const overflowX2 = Math.max(this.cropper.x2 - this.maxSize.width, 0); /** @type {?} */ const overflowY1 = Math.max(this.cropper.y2 - this.maxSize.height, 0); /** @type {?} */ const overflowY2 = Math.max(0 - this.cropper.y1, 0); if (overflowX1 > 0 || overflowX2 > 0 || overflowY1 > 0 || overflowY2 > 0) { this.cropper.x1 += (overflowY1 * this.aspectRatio) > overflowX1 ? (overflowY1 * this.aspectRatio) : overflowX1; this.cropper.x2 -= (overflowY2 * this.aspectRatio) > overflowX2 ? (overflowY2 * this.aspectRatio) : overflowX2; this.cropper.y1 += (overflowY2 * this.aspectRatio) > overflowX2 ? overflowY2 : overflowX2 / this.aspectRatio; this.cropper.y2 -= (overflowY1 * this.aspectRatio) > overflowX1 ? overflowY1 : overflowX1 / this.aspectRatio; } break; } } /** * @private * @return {?} */ doAutoCrop() { if (this.autoCrop) { this.crop(); } } /** * @return {?} */ crop() { if (this.sourceImage && this.sourceImage.nativeElement && this.transformedImage != null) { this.startCropImage.emit(); /** @type {?} */ const imagePosition = this.getImagePosition(); /** @type {?} */ const imgWidth = imagePosition.x2 - imagePosition.x1; /** @type {?} */ const imgHeight = imagePosition.y2 - imagePosition.y1; /** @type {?} */ const sourceImageElement = this.sourceImage.nativeElement; /** @type {?} */ const ratio = this.transformedSize.width / sourceImageElement.offsetWidth; /** @type {?} */ const scaledMaxWidthWithRatio = this.cropperScaledMaxWidth * ratio; /** @type {?} */ const scaledMaxHeightWithRatio = this.cropperScaledMaxHeight * ratio; /** @type {?} */ const width = imgWidth > scaledMaxWidthWithRatio ? scaledMaxHeightWithRatio : imgWidth; /** @type {?} */ const height = imgHeight > scaledMaxHeightWithRatio ? scaledMaxHeightWithRatio : imgHeight; /** @type {?} */ const cropCanvas = (/** @type {?} */ (document.createElement('canvas'))); cropCanvas.width = width; cropCanvas.height = height; /** @type {?} */ const ctx = cropCanvas.getContext('2d'); if (ctx) { if (this.backgroundColor != null) { ctx.fillStyle = this.backgroundColor; ctx.fillRect(0, 0, width, height); } /** @type {?} */ const scaleX = (this.transform.scale || 1) * (this.transform.flipH ? -1 : 1); /** @type {?} */ const scaleY = (this.transform.scale || 1) * (this.transform.flipV ? -1 : 1); ctx.setTransform(scaleX, 0, 0, scaleY, this.transformedSize.width / 2, this.transformedSize.height / 2); ctx.translate(-imagePosition.x1 / scaleX, -imagePosition.y1 / scaleY); ctx.rotate((this.transform.rotate || 0) * Math.PI / 180); ctx.drawImage(this.transformedImage, -this.transformedSize.width / 2, -this.transformedSize.height / 2); /** @type {?} */ const output = { width, height, imagePosition, cropperPosition: Object.assign({}, this.cropper) }; if (this.containWithinAspectRatio) { output.offsetImagePosition = this.getOffsetImagePosition(); } /** @type {?} */ const resizeRatio = this.getResizeRatio(width, height); if (resizeRatio !== 1) { output.width = Math.round(width * resizeRatio); output.height = this.maintainAspectRatio ? Math.round(output.width / this.aspectRatio) : Math.round(height * resizeRatio); resizeCanvas(cropCanvas, output.width, output.height); } output.base64 = this.cropToBase64(cropCanvas); this.imageCropped.emit(output); return output; } } return null; } /** * @private * @return {?} */ getImagePosition() { /** @type {?} */ const sourceImageElement = this.sourceImage.nativeElement; /** @type {?} */ const ratio = this.transformedSize.width / sourceImageElement.offsetWidth; /** @type {?} */ const out = { x1: Math.round(this.cropper.x1 * ratio), y1: Math.round(this.cropper.y1 * ratio), x2: Math.round(this.cropper.x2 * ratio), y2: Math.round(this.cropper.y2 * ratio) }; if (!this.containWithinAspectRatio) { out.x1 = Math.max(out.x1, 0); out.y1 = Math.max(out.y1, 0); out.x2 = Math.min(out.x2, this.transformedSize.width); out.y2 = Math.min(out.y2, this.transformedSize.height); } return out; } /** * @private * @return {?} */ getOffsetImagePosition() { /** @type {?} */ const canvasRotation = this.canvasRotation + this.exifTransform.rotate; /** @type {?} */ const sourceImageElement = this.sourceImage.nativeElement; /** @type {?} */ const ratio = this.transformedSize.width / sourceImageElement.offsetWidth; /** @type {?} */ let offsetX; /** @type {?} */ let offsetY; if (canvasRotation % 2) { offsetX = (this.transformedSize.width - this.originalSize.height) / 2; offsetY = (this.transformedSize.height - this.originalSize.width) / 2; } else { offsetX = (this.transformedSize.width - this.originalSize.width) / 2; offsetY = (this.transformedSize.height - this.originalSize.height) / 2; } /** @type {?} */ const out = { x1: Math.round(this.cropper.x1 * ratio) - offsetX, y1: Math.round(this.cropper.y1 * ratio) - offsetY, x2: Math.round(this.cropper.x2 * ratio) - offsetX, y2: Math.round(this.cropper.y2 * ratio) - offsetY }; if (!this.containWithinAspectRatio) { out.x1 = Math.max(out.x1, 0); out.y1 = Math.max(out.y1, 0); out.x2 = Math.min(out.x2, this.transformedSize.width); out.y2 = Math.min(out.y2, this.transformedSize.height); } return out; } /** * @private * @param {?} cropCanvas * @return {?} */ cropToBase64(cropCanvas) { return cropCanvas.toDataURL('image/' + this.format, this.getQuality()); } /** * @private * @return {?} */ getQuality() { return Math.min(1, Math.max(0, this.imageQuality / 100)); } /** * @param {?} width * @param {?} height * @return {?} */ getResizeRatio(width, height) { /** @type {?} */ const ratioWidth = this.resizeToWidth / width; /** @type {?} */ const ratioHeight = this.resizeToHeight / height; /** @type {?} */ const ratios = new Array(); if (this.resizeToWidth > 0) { ratios.push(ratioWidth); } if (this.resizeToHeight > 0) { ratios.push(ratioHeight); } /** @type {?} */ const result = ratios.length === 0 ? 1 : Math.min(...ratios); if (result > 1 && !this.onlyScaleDown) { return result; } return Math.min(result, 1); } /** * @private * @param {?} event * @return {?} */ getClientX(event) { return (event.touches && event.touches[0] ? event.touches[0].clientX : event.clientX) || 0; } /** * @private * @param {?} event * @return {?} */ getClientY(event) { return (event.touches && event.touches[0] ? event.touches[0].clientY : event.clientY) || 0; } } ImageCropperComponent.decorators = [ { type: Component, args: [{ selector: 'image-cropper', template: "<div [style.background]=\"imageVisible && backgroundColor\"\n #wrapper\n>\n <img\n #sourceImage\n class=\"source-image\"\n *ngIf=\"safeImgDataUrl\"\n [src]=\"safeImgDataUrl\"\n [style.visibility]=\"imageVisible ? 'visible' : 'hidden'\"\n [style.transform]=\"safeTransformStyle\"\n (load)=\"imageLoadedInView()\"\n />\n <div\n class=\"overlay\"\n [style.width.px]=\"maxSize.width\"\n [style.height.px]=\"maxSize.height\"\n [style.margin-left]=\"alignImage === 'center' ? marginLeft : null\"\n ></div>\n <div class=\"cropper\"\n *ngIf=\"imageVisible\"\n [class.rounded]=\"roundCropper\"\n [style.top.px]=\"cropper.y1\"\n [style.left.px]=\"cropper.x1\"\n [style.width.px]=\"cropper.x2 - cropper.x1\"\n [style.height.px]=\"cropper.y2 - cropper.y1\"\n [style.margin-left]=\"alignImage === 'center' ? marginLeft : null\"\n [style.visibility]=\"imageVisible ? 'visible' : 'hidden'\"\n (keydown)=\"keyboardAccess($event)\"\n tabindex=\"0\"\n >\n <div\n (mousedown)=\"startMove($event, moveTypes.Move)\"\n (touchstart)=\"startMove($event, moveTypes.Move)\"\n class=\"move\">\n </div>\n <ng-container *ngIf=\"!hideResizeSquares\">\n <span class=\"resize topleft\"\n (mousedown)=\"startMove($event, moveTypes.Resize, 'topleft')\"\n (touchstart)=\"startMove($event, moveTypes.Resize, 'topleft')\">\n <span class=\"square\"></span>\n </span>\n <span class=\"resize top\">\n <span class=\"square\"></span>\n </span>\n <span class=\"resize topright\"\n (mousedown)=\"startMove($event, moveTypes.Resize, 'topright')\"\n (touchstart)=\"startMove($event, moveTypes.Resize, 'topright')\">\n <span class=\"square\"></span>\n </span>\n <span class=\"resize right\">\n <span class=\"square\"></span>\n </span>\n <span class=\"resize bottomright\"\n (mousedown)=\"startMove($event, moveTypes.Resize, 'bottomright')\"\n (touchstart)=\"startMove($event, moveTypes.Resize, 'bottomright')\">\n <span class=\"square\"></span>\n </span>\n <span class=\"resize bottom\">\n <span class=\"square\"></span>\n </span>\n <span class=\"resize bottomleft\"\n (mousedown)=\"startMove($event, moveTypes.Resize, 'bottomleft')\"\n (touchstart)=\"startMove($event, moveTypes.Resize, 'bottomleft')\">\n <span class=\"square\"></span>\n </span>\n <span class=\"resize left\">\n <span class=\"square\"></span>\n </span>\n <span class=\"resize-bar top\"\n (mousedown)=\"startMove($event, moveTypes.Resize, 'top')\"\n (touchstart)=\"startMove($event, moveTypes.Resize, 'top')\">\n </span>\n <span class=\"resize-bar right\"\n (mousedown)=\"startMove($event, moveTypes.Resize, 'right')\"\n (touchstart)=\"startMove($event, moveTypes.Resize, 'right')\">\n </span>\n <span class=\"resize-bar bottom\"\n (mousedown)=\"startMove($event, moveTypes.Resize, 'bottom')\"\n (touchstart)=\"startMove($event, moveTypes.Resize, 'bottom')\">\n </span>\n <span class=\"resize-bar left\"\n (mousedown)=\"startMove($event, moveTypes.Resize, 'left')\"\n (touchstart)=\"startMove($event, moveTypes.Resize, 'left')\">\n </span>\n </ng-container>\n </div>\n</div>\n", changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:flex;position:relative;width:100%;max-width:100%;max-height:100%;overflow:hidden;padding:5px;text-align:center}:host>div{width:100%;position:relative}:host>div img.source-image{max-width:100%;max-height:100%;transform-origin:center}:host .overlay{position:absolute;pointer-events:none;touch-action:none;outline:var(--cropper-overlay-color,#fff) solid 100vw;top:0;left:0}:host .cropper{position:absolute;display:flex;color:#53535c;background:0 0;outline:rgba(255,255,255,.3) solid 100vw;outline:var(--cropper-outline-color,rgba(255,255,255,.3)) solid 100vw;touch-action:none}:host .cropper:after{position:absolute;content:\"\";top:0;bottom:0;left:0;right:0;pointer-events:none;border:1px dashed;opacity:.75;color:inherit;z-index:1}:host .cropper .move{width:100%;cursor:move;border:1px solid rgba(255,255,255,.5)}:host .cropper:focus .move{border-color:#1e90ff;border-width:2px}:host .cropper .resize{position:absolute;display:inline-block;line-height:6px;padding