From 0c993f555629c562e73dae7ad54c057f49b0a1fb Mon Sep 17 00:00:00 2001 From: Martijn Willekens Date: Thu, 16 Jan 2020 22:53:27 +0100 Subject: [PATCH 1/6] Scale and rotate image --- README.md | 2 +- projects/demo-app/src/app/app.component.html | 3 +- projects/demo-app/src/app/app.component.ts | 10 ++++-- projects/ngx-image-cropper/package.json | 2 +- .../component/image-cropper.component.html | 3 +- .../component/image-cropper.component.scss | 1 + .../lib/component/image-cropper.component.ts | 31 +++++++++++-------- 7 files changed, 32 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 71428f8..bd20050 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ All inputs are optional. Either the `imageChangedEvent`, `imageBase64` or `image | ----------------------- | ----------------- | ----------- | | `imageCropped` | ImageCroppedEvent | Emits an ImageCroppedEvent each time the image is cropped | | `imageLoaded` | void | Emits when the image was loaded into the cropper | -| `cropperReady` | void | Emits when the cropper is ready to be interacted | +| `cropperReady` | Dimensions | Emits when the cropper is ready to be interacted. The Dimensions object that is returned contains the displayed image size | | `startCropImage` | void | Emits when the component started cropping the image | | `loadImageFailed` | void | Emits when a wrong file type was selected (only png, gif and jpg are allowed) | diff --git a/projects/demo-app/src/app/app.component.html b/projects/demo-app/src/app/app.component.html index 64bbf40..3e82731 100644 --- a/projects/demo-app/src/app/app.component.html +++ b/projects/demo-app/src/app/app.component.html @@ -18,11 +18,12 @@ [cropperMinWidth]="128" [onlyScaleDown]="true" [roundCropper]="false" + [transform]="transform" format="png" outputType="base64" (imageCropped)="imageCropped($event)" (imageLoaded)="imageLoaded()" - (cropperReady)="cropperReady()" + (cropperReady)="cropperReady($event)" (loadImageFailed)="loadImageFailed()" [style.display]="showCropper ? null : 'none'" [alignImage]="'left'" diff --git a/projects/demo-app/src/app/app.component.ts b/projects/demo-app/src/app/app.component.ts index a1839e8..9225123 100644 --- a/projects/demo-app/src/app/app.component.ts +++ b/projects/demo-app/src/app/app.component.ts @@ -1,5 +1,5 @@ import { Component, ViewChild } from '@angular/core'; -import { ImageCroppedEvent, ImageCropperComponent } from 'ngx-image-cropper'; +import { Dimensions, ImageCroppedEvent, ImageCropperComponent } from 'ngx-image-cropper'; @Component({ selector: 'app-root', @@ -11,6 +11,10 @@ export class AppComponent { croppedImage: any = ''; showCropper = false; containWithinAspectRatio = false; + transform = { + scale: 1.5, + rotate: 45 + }; @ViewChild(ImageCropperComponent, {static: true}) imageCropper: ImageCropperComponent; @@ -28,8 +32,8 @@ export class AppComponent { console.log('Image loaded'); } - cropperReady() { - console.log('Cropper ready'); + cropperReady(sourceImageDimensions: Dimensions) { + console.log('Cropper ready', sourceImageDimensions); } loadImageFailed() { diff --git a/projects/ngx-image-cropper/package.json b/projects/ngx-image-cropper/package.json index edb25b3..ac1d1be 100644 --- a/projects/ngx-image-cropper/package.json +++ b/projects/ngx-image-cropper/package.json @@ -1,6 +1,6 @@ { "name": "ngx-image-cropper", - "version": "2.1.1", + "version": "2.1.2", "description": "An image cropper for Angular", "author": "Martijn Willekens", "repository": { diff --git a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.html b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.html index d964369..9b2a203 100644 --- a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.html +++ b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.html @@ -5,6 +5,7 @@ *ngIf="safeImgDataUrl" [src]="safeImgDataUrl" [style.visibility]="imageVisible ? 'visible' : 'hidden'" + [style.transform]="safeTransformStyle" (load)="imageLoadedInView()" />
(); @Output() startCropImage = new EventEmitter(); @Output() imageLoaded = new EventEmitter(); - @Output() cropperReady = new EventEmitter(); + @Output() cropperReady = new EventEmitter(); @Output() loadImageFailed = new EventEmitter(); constructor(private sanitizer: DomSanitizer, @@ -110,6 +112,12 @@ export class ImageCropperComponent implements OnChanges, OnInit { if (changes.aspectRatio && this.imageVisible) { this.resetCropperPosition(); } + if (changes.transform && this.transform) { + this.safeTransformStyle = this.sanitizer.bypassSecurityTrustStyle( + 'scale(' + (this.transform.scale || 1) + ')' + + 'rotate(' + (this.transform.rotate || 0) + 'deg)' + ); + } } ngOnInit(): void { @@ -293,7 +301,7 @@ export class ImageCropperComponent implements OnChanges, OnInit { this.setMaxSize(); this.setCropperScaledMinSize(); this.resetCropperPosition(); - this.cropperReady.emit(); + this.cropperReady.emit({...this.maxSize}); this.cd.markForCheck(); } else { this.setImageMaxSizeRetries++; @@ -664,6 +672,8 @@ export class ImageCropperComponent implements OnChanges, OnInit { const width = imagePosition.x2 - imagePosition.x1; const height = imagePosition.y2 - imagePosition.y1; + console.log(this.cropper, imagePosition, this.transformedSize); + const cropCanvas = document.createElement('canvas') as HTMLCanvasElement; cropCanvas.width = width; cropCanvas.height = height; @@ -674,17 +684,12 @@ export class ImageCropperComponent implements OnChanges, OnInit { ctx.fillStyle = this.backgroundColor; ctx.fillRect(0, 0, width, height); } - ctx.drawImage( - this.transformedImage, - imagePosition.x1, - imagePosition.y1, - width, - height, - 0, - 0, - width, - height - ); + + ctx.setTransform(this.transform.scale, 0, 0, this.transform.scale, this.transformedSize.width / 2, this.transformedSize.height / 2); + ctx.translate(-imagePosition.x1 / this.transform.scale, -imagePosition.y1 / this.transform.scale); + ctx.rotate(this.transform.rotate * Math.PI / 180); + ctx.drawImage(this.transformedImage, -this.transformedSize.width / 2, -this.transformedSize.height / 2); + const output: ImageCroppedEvent = { width, height, imagePosition, From 9b365f8acf914ddc0582b4d88282883b4e4c0056 Mon Sep 17 00:00:00 2001 From: Martijn Willekens Date: Fri, 17 Jan 2020 18:55:35 +0100 Subject: [PATCH 2/6] Fix overflow and crop after rotate/scale update --- projects/demo-app/src/app/app.component.html | 7 ++++- projects/demo-app/src/app/app.component.ts | 29 ++++++++++++++++--- projects/demo-app/src/app/app.module.ts | 5 +++- .../component/image-cropper.component.html | 5 ++++ .../component/image-cropper.component.scss | 11 +++++++ .../lib/component/image-cropper.component.ts | 7 ++--- 6 files changed, 54 insertions(+), 10 deletions(-) diff --git a/projects/demo-app/src/app/app.component.html b/projects/demo-app/src/app/app.component.html index 3e82731..fa99099 100644 --- a/projects/demo-app/src/app/app.component.html +++ b/projects/demo-app/src/app/app.component.html @@ -1,12 +1,17 @@
+

+
+
+
+
({ + rotate, + scale + })) + ); @ViewChild(ImageCropperComponent, {static: true}) imageCropper: ImageCropperComponent; @@ -60,6 +71,16 @@ export class AppComponent { this.imageCropper.resetImage(); } + zoomOut() { + this.zoom -= .1; + this.zoom$.next(this.zoom); + } + + zoomIn() { + this.zoom += .1; + this.zoom$.next(this.zoom); + } + toggleContainWithinAspectRatio(){ this.containWithinAspectRatio = !this.containWithinAspectRatio; } diff --git a/projects/demo-app/src/app/app.module.ts b/projects/demo-app/src/app/app.module.ts index b7a35a1..a4ac18c 100644 --- a/projects/demo-app/src/app/app.module.ts +++ b/projects/demo-app/src/app/app.module.ts @@ -1,5 +1,6 @@ -import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ImageCropperModule } from 'ngx-image-cropper'; import { AppComponent } from './app.component'; @@ -10,6 +11,8 @@ import { AppComponent } from './app.component'; ], imports: [ BrowserModule, + FormsModule, + ReactiveFormsModule, ImageCropperModule ], providers: [], diff --git a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.html b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.html index 9b2a203..12e36e4 100644 --- a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.html +++ b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.html @@ -8,6 +8,11 @@ [style.transform]="safeTransformStyle" (load)="imageLoadedInView()" /> +
| null { - if (this.sourceImage.nativeElement && this.transformedImage != null) { + if (this.sourceImage && this.sourceImage.nativeElement && this.transformedImage != null) { this.startCropImage.emit(); const imagePosition = this.getImagePosition(); const width = imagePosition.x2 - imagePosition.x1; const height = imagePosition.y2 - imagePosition.y1; - console.log(this.cropper, imagePosition, this.transformedSize); - const cropCanvas = document.createElement('canvas') as HTMLCanvasElement; cropCanvas.width = width; cropCanvas.height = height; From 222ca9716ed4c5398a3c7ef152858e3d8100955d Mon Sep 17 00:00:00 2001 From: Martijn Willekens Date: Sun, 19 Jan 2020 11:46:44 +0100 Subject: [PATCH 3/6] Split transformations and exif correction --- projects/demo-app/src/app/app.component.html | 5 +- projects/demo-app/src/app/app.component.ts | 62 +++++----- .../lib/component/image-cropper.component.ts | 109 ++++++++---------- .../interfaces/exif-transform.interface.ts | 4 + .../image-cropped-event.interface.ts | 4 +- .../interfaces/transformations.interface.ts | 7 +- .../src/lib/utils/exif.utils.ts | 26 ++--- 7 files changed, 106 insertions(+), 111 deletions(-) create mode 100644 projects/ngx-image-cropper/src/lib/interfaces/exif-transform.interface.ts diff --git a/projects/demo-app/src/app/app.component.html b/projects/demo-app/src/app/app.component.html index fa99099..10615c7 100644 --- a/projects/demo-app/src/app/app.component.html +++ b/projects/demo-app/src/app/app.component.html @@ -11,7 +11,7 @@

- +
({ - rotate, - scale - })) - ); + transform: Transformations; @ViewChild(ImageCropperComponent, {static: true}) imageCropper: ImageCropperComponent; @@ -52,36 +41,57 @@ export class AppComponent { } rotateLeft() { - this.imageCropper.rotateLeft(); + this.canvasRotation--; } rotateRight() { - this.imageCropper.rotateRight(); + this.canvasRotation++; } flipHorizontal() { - this.imageCropper.flipHorizontal(); + this.transform = { + ...this.transform, + flipH: !this.transform.flipH + }; } flipVertical() { - this.imageCropper.flipVertical(); + this.transform = { + ...this.transform, + flipV: !this.transform.flipV + }; } resetImage() { - this.imageCropper.resetImage(); + this.scale = 1; + this.rotation = 0; + this.transform = {}; } zoomOut() { - this.zoom -= .1; - this.zoom$.next(this.zoom); + this.scale -= .1; + this.transform = { + ...this.transform, + scale: this.scale + }; } zoomIn() { - this.zoom += .1; - this.zoom$.next(this.zoom); + this.scale += .1; + this.transform = { + ...this.transform, + scale: this.scale + }; } - toggleContainWithinAspectRatio(){ + toggleContainWithinAspectRatio() { this.containWithinAspectRatio = !this.containWithinAspectRatio; } + + updateRotation() { + this.transform = { + ...this.transform, + rotate: this.rotation + }; + } } diff --git a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts index 902b9af..dd10eb6 100644 --- a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts +++ b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts @@ -1,11 +1,24 @@ import { - Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output, - SimpleChanges, ChangeDetectorRef, ChangeDetectionStrategy, NgZone, ViewChild, OnInit + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostBinding, + HostListener, + Input, + NgZone, + OnChanges, + OnInit, + Output, + SimpleChanges, + ViewChild } from '@angular/core'; -import { DomSanitizer, SafeUrl, SafeStyle } from '@angular/platform-browser'; -import { MoveStart, Dimensions, CropperPosition, ImageCroppedEvent, Transformations } from '../interfaces'; -import { getTransformationsFromExifRotation } from '../utils/exif.utils'; +import { DomSanitizer, SafeStyle, SafeUrl } from '@angular/platform-browser'; +import { CropperPosition, Dimensions, ImageCroppedEvent, MoveStart, Transformations } from '../interfaces'; +import { getTransformationsFromExifData } from '../utils/exif.utils'; import { resizeCanvas } from '../utils/resize.utils'; +import { ExifTransform } from '../interfaces/exif-transform.interface'; export type OutputType = 'base64' | 'file' | 'both'; @@ -23,10 +36,10 @@ export class ImageCropperComponent implements OnChanges, OnInit { private moveStart: MoveStart; private originalSize: Dimensions; private transformedSize: Dimensions; - private transformations: Transformations; private setImageMaxSizeRetries = 0; private cropperScaledMinWidth = 20; private cropperScaledMinHeight = 20; + private exifTransform: ExifTransform; private stepSize = 3; safeImgDataUrl: SafeUrl | string; @@ -62,12 +75,13 @@ export class ImageCropperComponent implements OnChanges, OnInit { @Input() format: 'png' | 'jpeg' | 'bmp' | 'webp' | 'ico' = 'png'; @Input() outputType: OutputType = 'base64'; @Input() maintainAspectRatio = true; - @Input() transform: any; + @Input() transformations: Transformations = {}; @Input() aspectRatio = 1; @Input() resizeToWidth = 0; @Input() resizeToHeight = 0; @Input() cropperMinWidth = 0; @Input() cropperMinHeight = 0; + @Input() canvasRotation = 0; @Input() initialStepSize = 3; @Input() roundCropper = false; @Input() onlyScaleDown = false; @@ -99,7 +113,7 @@ export class ImageCropperComponent implements OnChanges, OnInit { } ngOnChanges(changes: SimpleChanges): void { - if (this.originalImage && this.originalImage.complete && changes.containWithinAspectRatio) { + if (this.originalImage && this.originalImage.complete && (changes.containWithinAspectRatio || changes.canvasRotation)) { this.transformOriginalImage(); } if (changes.cropper) { @@ -112,10 +126,12 @@ export class ImageCropperComponent implements OnChanges, OnInit { if (changes.aspectRatio && this.imageVisible) { this.resetCropperPosition(); } - if (changes.transform && this.transform) { + if (changes.transformations) { + this.transformations = this.transformations || {}; this.safeTransformStyle = this.sanitizer.bypassSecurityTrustStyle( - 'scale(' + (this.transform.scale || 1) + ')' + - 'rotate(' + (this.transform.rotate || 0) + 'deg)' + 'scaleX(' + (this.transformations.scale || 1) * (this.transformations.flipH ? -1 : 1) + ')' + + 'scaleY(' + (this.transformations.scale || 1) * (this.transformations.flipV ? -1 : 1) + ')' + + 'rotate(' + (this.transformations.rotate || 0) + 'deg)' ); this.doAutoCrop(); } @@ -154,7 +170,6 @@ export class ImageCropperComponent implements OnChanges, OnInit { width: 0, height: 0 }; - this.transformations = {rotation: 0, flipH: false, flipV: false}; this.cropper.x1 = -100; this.cropper.y1 = -100; this.cropper.x2 = 10000; @@ -188,7 +203,7 @@ export class ImageCropperComponent implements OnChanges, OnInit { this.originalImage = new Image(); this.originalImage.onload = () => { this.originalBase64 = imageBase64; - this.transformations = getTransformationsFromExifRotation(imageBase64); + this.exifTransform = getTransformationsFromExifData(imageBase64); this.originalSize.width = this.originalImage.naturalWidth; this.originalSize.height = this.originalImage.naturalHeight; this.transformOriginalImage() @@ -200,16 +215,10 @@ export class ImageCropperComponent implements OnChanges, OnInit { }); } - private checkRotation(): void { - if (this.transformations.rotation < 0) { - this.transformations.rotation += 4; - } - this.transformations.rotation = this.transformations.rotation % 4; - } - private getTransformedSize(): Dimensions { + const canvasRotation = this.canvasRotation + this.exifTransform.rotate; if (this.containWithinAspectRatio) { - if (this.transformations.rotation % 2) { + if (canvasRotation % 2) { const minWidthToContain = this.originalSize.width * this.aspectRatio; const minHeightToContain = this.originalSize.height / this.aspectRatio; return { @@ -226,7 +235,7 @@ export class ImageCropperComponent implements OnChanges, OnInit { } } - if (this.transformations.rotation % 2) { + if (canvasRotation % 2) { return { height: this.originalSize.width, width: this.originalSize.height, @@ -238,21 +247,21 @@ export class ImageCropperComponent implements OnChanges, OnInit { }; } private transformImageBase64(): Promise { - this.checkRotation(); + const canvasRotation = this.canvasRotation + this.exifTransform.rotate; const transformedSize = this.getTransformedSize(); const canvas = document.createElement('canvas'); canvas.width = transformedSize.width; canvas.height = transformedSize.height; const ctx = canvas.getContext('2d'); ctx.setTransform( - this.transformations.flipH ? -1 : 1, + this.exifTransform.flip ? -1 : 1, 0, 0, - this.transformations.flipV ? -1 : 1, + 1, canvas.width / 2, canvas.height / 2 ); - ctx.rotate(Math.PI * (this.transformations.rotation / 2)); + ctx.rotate(Math.PI * (canvasRotation / 2)); ctx.drawImage( this.originalImage, -this.originalSize.width / 2, @@ -260,6 +269,7 @@ export class ImageCropperComponent implements OnChanges, OnInit { ); return Promise.resolve(canvas.toDataURL()); } + private setTransformedImage(transformedBase64): Promise { return new Promise((resolve, reject) => { this.transformedBase64 = transformedBase64; @@ -319,39 +329,6 @@ export class ImageCropperComponent implements OnChanges, OnInit { this.setCropperScaledMinSize(); } - rotateLeft() { - this.transformations.rotation--; - this.flipAfterRotate(); - return this.transformOriginalImage(); - } - - rotateRight() { - this.transformations.rotation++; - this.flipAfterRotate(); - return this.transformOriginalImage(); - } - - private flipAfterRotate() { - const flippedH = this.transformations.flipH; - const flippedV = this.transformations.flipV; - this.transformations.flipH = flippedV; - this.transformations.flipV = flippedH; - } - - flipHorizontal() { - this.transformations.flipH = !this.transformations.flipH; - return this.transformOriginalImage(); - } - - flipVertical() { - this.transformations.flipV = !this.transformations.flipV; - return this.transformOriginalImage(); - } - - resetImage() { - this.imageBase64 = this.originalBase64; - } - private resizeCropperPosition(): void { const sourceImageElement = this.sourceImage.nativeElement; if (this.maxSize.width !== sourceImageElement.offsetWidth || this.maxSize.height !== sourceImageElement.offsetHeight) { @@ -684,16 +661,19 @@ export class ImageCropperComponent implements OnChanges, OnInit { ctx.fillRect(0, 0, width, height); } - ctx.setTransform(this.transform.scale, 0, 0, this.transform.scale, this.transformedSize.width / 2, this.transformedSize.height / 2); - ctx.translate(-imagePosition.x1 / this.transform.scale, -imagePosition.y1 / this.transform.scale); - ctx.rotate(this.transform.rotate * Math.PI / 180); + const scaleX = (this.transformations.scale || 1) * (this.transformations.flipH ? -1 : 1); + const scaleY = (this.transformations.scale || 1) * (this.transformations.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.transformations.rotate || 0) * Math.PI / 180); ctx.drawImage(this.transformedImage, -this.transformedSize.width / 2, -this.transformedSize.height / 2); const output: ImageCroppedEvent = { width, height, imagePosition, cropperPosition: {...this.cropper}, - transform: {...this.transformations} + exifTransform: {...this.exifTransform} }; if (this.containWithinAspectRatio) { output.offsetImagePosition = this.getOffsetImagePosition(); @@ -734,12 +714,13 @@ export class ImageCropperComponent implements OnChanges, OnInit { } private getOffsetImagePosition(): CropperPosition { + const canvasRotation = this.canvasRotation + this.exifTransform.rotate; const sourceImageElement = this.sourceImage.nativeElement; const ratio = this.transformedSize.width / sourceImageElement.offsetWidth; let offsetX: number; let offsetY: number; - if (this.transformations.rotation % 2) { + if (canvasRotation % 2) { offsetX = (this.transformedSize.width - this.originalSize.height) / 2; offsetY = (this.transformedSize.height - this.originalSize.width) / 2; } else { diff --git a/projects/ngx-image-cropper/src/lib/interfaces/exif-transform.interface.ts b/projects/ngx-image-cropper/src/lib/interfaces/exif-transform.interface.ts new file mode 100644 index 0000000..ca33a12 --- /dev/null +++ b/projects/ngx-image-cropper/src/lib/interfaces/exif-transform.interface.ts @@ -0,0 +1,4 @@ +export interface ExifTransform { + rotate: number; + flip: boolean; +} diff --git a/projects/ngx-image-cropper/src/lib/interfaces/image-cropped-event.interface.ts b/projects/ngx-image-cropper/src/lib/interfaces/image-cropped-event.interface.ts index feaf509..d03dea4 100644 --- a/projects/ngx-image-cropper/src/lib/interfaces/image-cropped-event.interface.ts +++ b/projects/ngx-image-cropper/src/lib/interfaces/image-cropped-event.interface.ts @@ -1,5 +1,5 @@ import { CropperPosition } from './cropper-position.interface'; -import { Transformations } from './transformations.interface'; +import { ExifTransform } from './exif-transform.interface'; export interface ImageCroppedEvent { base64?: string | null; @@ -9,5 +9,5 @@ export interface ImageCroppedEvent { cropperPosition: CropperPosition; imagePosition: CropperPosition; offsetImagePosition?: CropperPosition; - transform: Transformations; + exifTransform: ExifTransform; } diff --git a/projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts b/projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts index 4cca30b..b0ac434 100644 --- a/projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts +++ b/projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts @@ -1,5 +1,6 @@ export interface Transformations { - rotation: number; - flipH: boolean; - flipV: boolean; + scale?: number; + rotate?: number; + flipH?: boolean; + flipV?: boolean; } diff --git a/projects/ngx-image-cropper/src/lib/utils/exif.utils.ts b/projects/ngx-image-cropper/src/lib/utils/exif.utils.ts index 825ff1e..0b5b6bb 100644 --- a/projects/ngx-image-cropper/src/lib/utils/exif.utils.ts +++ b/projects/ngx-image-cropper/src/lib/utils/exif.utils.ts @@ -1,18 +1,18 @@ -import { Transformations } from '../interfaces'; +import { ExifTransform } from '../interfaces/exif-transform.interface'; -export function getTransformationsFromExifRotation(exifRotationOrBase64Image: number | string): Transformations { +export function getTransformationsFromExifData(exifRotationOrBase64Image: number | string): ExifTransform { if (typeof exifRotationOrBase64Image === 'string') { exifRotationOrBase64Image = getExifRotation(exifRotationOrBase64Image); } switch (exifRotationOrBase64Image) { - case 2: return {rotation: 0, flipH: true, flipV: false}; - case 3: return {rotation: 2, flipH: false, flipV: false}; - case 4: return {rotation: 2, flipH: true, flipV: false}; - case 5: return {rotation: 1, flipH: true, flipV: false}; - case 6: return {rotation: 1, flipH: false, flipV: false}; - case 7: return {rotation: 3, flipH: true, flipV: false}; - case 8: return {rotation: 3, flipH: false, flipV: false}; - default: return {rotation: 0, flipH: false, flipV: false}; + 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}; } } @@ -41,11 +41,9 @@ function getExifRotation(imageBase64: string): number { return view.getUint16(offset + (i * 12) + 8, little); } } - } - else if ((marker & 0xFF00) != 0xFF00) { + } else if ((marker & 0xFF00) != 0xFF00) { break; - } - else { + } else { offset += view.getUint16(offset, false); } } From 58b054e2897dfb95713a5f17b0c55693d5fafb58 Mon Sep 17 00:00:00 2001 From: Martijn Willekens Date: Sun, 19 Jan 2020 23:21:00 +0100 Subject: [PATCH 4/6] Handle all image transformations --- projects/demo-app/src/app/app.component.ts | 18 +++++++++++++-- .../lib/component/image-cropper.component.ts | 23 +++++++++---------- .../image-cropped-event.interface.ts | 2 -- .../src/lib/interfaces/index.ts | 2 +- .../interfaces/transformations.interface.ts | 2 +- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/projects/demo-app/src/app/app.component.ts b/projects/demo-app/src/app/app.component.ts index 86372a7..6b2dc02 100644 --- a/projects/demo-app/src/app/app.component.ts +++ b/projects/demo-app/src/app/app.component.ts @@ -1,5 +1,5 @@ import { Component, ViewChild } from '@angular/core'; -import { Dimensions, ImageCroppedEvent, ImageCropperComponent, Transformations } from 'ngx-image-cropper'; +import { Dimensions, ImageCroppedEvent, ImageCropperComponent, ImageTransform } from 'ngx-image-cropper'; @Component({ selector: 'app-root', @@ -14,7 +14,7 @@ export class AppComponent { scale = 1; showCropper = false; containWithinAspectRatio = false; - transform: Transformations; + transform: ImageTransform = {}; @ViewChild(ImageCropperComponent, {static: true}) imageCropper: ImageCropperComponent; @@ -42,12 +42,25 @@ export class AppComponent { rotateLeft() { this.canvasRotation--; + this.flipAfterRotate(); } rotateRight() { this.canvasRotation++; + this.flipAfterRotate(); } + private flipAfterRotate() { + const flippedH = this.transform.flipH; + const flippedV = this.transform.flipV; + this.transform = { + ...this.transform, + flipH: flippedV, + flipV: flippedH + }; + } + + flipHorizontal() { this.transform = { ...this.transform, @@ -65,6 +78,7 @@ export class AppComponent { resetImage() { this.scale = 1; this.rotation = 0; + this.canvasRotation = 0; this.transform = {}; } diff --git a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts index dd10eb6..d0f8f28 100644 --- a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts +++ b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts @@ -15,7 +15,7 @@ import { ViewChild } from '@angular/core'; import { DomSanitizer, SafeStyle, SafeUrl } from '@angular/platform-browser'; -import { CropperPosition, Dimensions, ImageCroppedEvent, MoveStart, Transformations } from '../interfaces'; +import { CropperPosition, Dimensions, ImageCroppedEvent, MoveStart, ImageTransform } from '../interfaces'; import { getTransformationsFromExifData } from '../utils/exif.utils'; import { resizeCanvas } from '../utils/resize.utils'; import { ExifTransform } from '../interfaces/exif-transform.interface'; @@ -75,7 +75,7 @@ export class ImageCropperComponent implements OnChanges, OnInit { @Input() format: 'png' | 'jpeg' | 'bmp' | 'webp' | 'ico' = 'png'; @Input() outputType: OutputType = 'base64'; @Input() maintainAspectRatio = true; - @Input() transformations: Transformations = {}; + @Input() transform: ImageTransform = {}; @Input() aspectRatio = 1; @Input() resizeToWidth = 0; @Input() resizeToHeight = 0; @@ -126,12 +126,12 @@ export class ImageCropperComponent implements OnChanges, OnInit { if (changes.aspectRatio && this.imageVisible) { this.resetCropperPosition(); } - if (changes.transformations) { - this.transformations = this.transformations || {}; + if (changes.transform) { + this.transform = this.transform || {}; this.safeTransformStyle = this.sanitizer.bypassSecurityTrustStyle( - 'scaleX(' + (this.transformations.scale || 1) * (this.transformations.flipH ? -1 : 1) + ')' + - 'scaleY(' + (this.transformations.scale || 1) * (this.transformations.flipV ? -1 : 1) + ')' + - 'rotate(' + (this.transformations.rotate || 0) + 'deg)' + '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)' ); this.doAutoCrop(); } @@ -661,19 +661,18 @@ export class ImageCropperComponent implements OnChanges, OnInit { ctx.fillRect(0, 0, width, height); } - const scaleX = (this.transformations.scale || 1) * (this.transformations.flipH ? -1 : 1); - const scaleY = (this.transformations.scale || 1) * (this.transformations.flipV ? -1 : 1); + const scaleX = (this.transform.scale || 1) * (this.transform.flipH ? -1 : 1); + 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.transformations.rotate || 0) * Math.PI / 180); + ctx.rotate((this.transform.rotate || 0) * Math.PI / 180); ctx.drawImage(this.transformedImage, -this.transformedSize.width / 2, -this.transformedSize.height / 2); const output: ImageCroppedEvent = { width, height, imagePosition, - cropperPosition: {...this.cropper}, - exifTransform: {...this.exifTransform} + cropperPosition: {...this.cropper} }; if (this.containWithinAspectRatio) { output.offsetImagePosition = this.getOffsetImagePosition(); diff --git a/projects/ngx-image-cropper/src/lib/interfaces/image-cropped-event.interface.ts b/projects/ngx-image-cropper/src/lib/interfaces/image-cropped-event.interface.ts index d03dea4..d3d2a11 100644 --- a/projects/ngx-image-cropper/src/lib/interfaces/image-cropped-event.interface.ts +++ b/projects/ngx-image-cropper/src/lib/interfaces/image-cropped-event.interface.ts @@ -1,5 +1,4 @@ import { CropperPosition } from './cropper-position.interface'; -import { ExifTransform } from './exif-transform.interface'; export interface ImageCroppedEvent { base64?: string | null; @@ -9,5 +8,4 @@ export interface ImageCroppedEvent { cropperPosition: CropperPosition; imagePosition: CropperPosition; offsetImagePosition?: CropperPosition; - exifTransform: ExifTransform; } diff --git a/projects/ngx-image-cropper/src/lib/interfaces/index.ts b/projects/ngx-image-cropper/src/lib/interfaces/index.ts index cc9715d..56fdcaa 100644 --- a/projects/ngx-image-cropper/src/lib/interfaces/index.ts +++ b/projects/ngx-image-cropper/src/lib/interfaces/index.ts @@ -2,4 +2,4 @@ export { CropperPosition } from './cropper-position.interface'; export { Dimensions } from './dimensions.interface'; export { ImageCroppedEvent } from './image-cropped-event.interface'; export { MoveStart } from './move-start.interface'; -export { Transformations } from './transformations.interface'; +export { ImageTransform } from './transformations.interface'; diff --git a/projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts b/projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts index b0ac434..9d6866b 100644 --- a/projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts +++ b/projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts @@ -1,4 +1,4 @@ -export interface Transformations { +export interface ImageTransform { scale?: number; rotate?: number; flipH?: boolean; From b27e85ebc7a895edad0fe38af2a0b5f2416b7422 Mon Sep 17 00:00:00 2001 From: Martijn Willekens Date: Sun, 19 Jan 2020 23:28:47 +0100 Subject: [PATCH 5/6] Update readme --- README.md | 18 +++++++----------- projects/demo-app/src/app/app.component.html | 6 +++--- ...terface.ts => image-transform.interface.ts} | 0 .../src/lib/interfaces/index.ts | 2 +- 4 files changed, 11 insertions(+), 15 deletions(-) rename projects/ngx-image-cropper/src/lib/interfaces/{transformations.interface.ts => image-transform.interface.ts} (100%) diff --git a/README.md b/README.md index bd20050..7fddb7b 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ All inputs are optional. Either the `imageChangedEvent`, `imageBase64` or `image | `alignImage` | 'left' or 'center' | 'center' | Use this to align the image in the cropper either to the left or center. | | `backgroundColor` | string | | Use this to set a backgroundColor, this is useful if you upload an image of a format with transparent colors and convert it to 'jpeg' or 'bmp'. The transparent pixels will then become the set color or the default value. Enter a color HashCode or one of known HTML color names (https://www.w3schools.com/tags/ref_colornames.asp).| | `disabled` | boolean | false | Disables the component and prevents changing the cropper position | +| `canvasRotation` | number | 0 | Rotate the canvas (1 = 90deg, 2 = 180deg...) | +| `transform` | ImageTransform | {} | Flip, rotate and scale image | ### Outputs | Name | Type | Description | @@ -120,11 +122,6 @@ To gain access to the image cropper's methods use `@ViewChild(ImageCropperCompon | Name | Returns | Description | | ----------------------- | ----------------- | ----------- | -| `rotateLeft` | void | Rotates the image to the left | -| `rotateRight` | void | Rotates the image to the right | -| `flipHorizontal` | void | Flips the image horizontally | -| `flipVertical` | void | Flips the image vertically | -| `resetImage` | void | Reset the image to the original | | `crop` | ImageCroppedEvent (when `outputType` is `base64`) or Promise<ImageCroppedEvent> (when `outputType` is `file` or `both`)) | Crops the source image to the current cropper position. Accepts an output type as an argument, default is the one given in the `outputType` input (`base64`, `file` or `both`). Be sure to set `autoCrop` to `false` if you only wish to use this function directly. | ### Interfaces @@ -136,24 +133,23 @@ To gain access to the image cropper's methods use `@ViewChild(ImageCropperCompon | x2 | number | X position of second coordinate (in px) | | y2 | number | Y position of second coordinate (in px) | -#### Transformations +#### ImageTransform | Property | Type | Description | | -------- | ------- | ----------- | -| rotation | number | 0=No Rotation, 1=90°, 2=180°, 3=270° | -| flipH | boolean | Flipped Horizontally | -| flipV | boolean | Flipped Vertically | +| scale | number | Scale image (1=normal, 2=2x zoom...) | +| rotate | number | Rotation in degrees | +| flipH | boolean | Flipped horizontally | +| flipV | boolean | Flipped vertically | #### ImageCroppedEvent | Property | Type | Description | | -------------------- | ------ | ----------- | | base64 | string | Base64 string of the cropped image | -| file | Blob(File) | Blob of the cropped image | | width | number | Width of the cropped image | | height | number | Height of the cropped image | | cropperPosition | CropperPosition | Position of the cropper when it was cropped relative to the displayed image size | | imagePosition | CropperPosition | Position of the cropper when it was cropped relative to the original image size | | offsetImagePosition | CropperPosition | Position of the cropper when it was cropped relative to the original image size without padding when containWithinAspectRatio is true | -| transform | Transformations | Rotation and Flip State | ### Polyfill for IE and Edge diff --git a/projects/demo-app/src/app/app.component.html b/projects/demo-app/src/app/app.component.html index 10615c7..b21e94c 100644 --- a/projects/demo-app/src/app/app.component.html +++ b/projects/demo-app/src/app/app.component.html @@ -24,15 +24,15 @@ [onlyScaleDown]="true" [roundCropper]="false" [canvasRotation]="canvasRotation" - [transformations]="transform" + [transform]="transform" + [alignImage]="'left'" + [style.display]="showCropper ? null : 'none'" format="png" outputType="base64" (imageCropped)="imageCropped($event)" (imageLoaded)="imageLoaded()" (cropperReady)="cropperReady($event)" (loadImageFailed)="loadImageFailed()" - [style.display]="showCropper ? null : 'none'" - [alignImage]="'left'" >
diff --git a/projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts b/projects/ngx-image-cropper/src/lib/interfaces/image-transform.interface.ts similarity index 100% rename from projects/ngx-image-cropper/src/lib/interfaces/transformations.interface.ts rename to projects/ngx-image-cropper/src/lib/interfaces/image-transform.interface.ts diff --git a/projects/ngx-image-cropper/src/lib/interfaces/index.ts b/projects/ngx-image-cropper/src/lib/interfaces/index.ts index 56fdcaa..9ab634c 100644 --- a/projects/ngx-image-cropper/src/lib/interfaces/index.ts +++ b/projects/ngx-image-cropper/src/lib/interfaces/index.ts @@ -2,4 +2,4 @@ export { CropperPosition } from './cropper-position.interface'; export { Dimensions } from './dimensions.interface'; export { ImageCroppedEvent } from './image-cropped-event.interface'; export { MoveStart } from './move-start.interface'; -export { ImageTransform } from './transformations.interface'; +export { ImageTransform } from './image-transform.interface'; From d9f5885005816100f3a260d23b79c6aef5c84697 Mon Sep 17 00:00:00 2001 From: Martijn Willekens Date: Sun, 19 Jan 2020 23:54:29 +0100 Subject: [PATCH 6/6] Remove output type file --- README.md | 30 +------------ projects/demo-app/src/app/app.component.html | 1 - projects/demo-app/src/app/app.component.ts | 3 +- projects/ngx-image-cropper/package.json | 4 +- .../lib/component/image-cropper.component.ts | 44 ++----------------- .../src/lib/utils/blob.utils.ts | 11 +++++ projects/ngx-image-cropper/src/public-api.ts | 1 + 7 files changed, 22 insertions(+), 72 deletions(-) create mode 100644 projects/ngx-image-cropper/src/lib/utils/blob.utils.ts diff --git a/README.md b/README.md index 7fddb7b..89b84aa 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ Add the element to your HTML: [imageChangedEvent]="imageChangedEvent" [maintainAspectRatio]="true" [aspectRatio]="4 / 3" - [resizeToWidth]="128" format="png" (imageCropped)="imageCropped($event)" (imageLoaded)="imageLoaded()" @@ -88,7 +87,6 @@ All inputs are optional. Either the `imageChangedEvent`, `imageBase64` or `image | `imageFile` | Blob(File)| | The file you want to change (set to `null` to reset the cropper) | | `imageBase64` | string | | If you don't want to use a file input, you can set a base64 image directly and it will be loaded into the cropper | | `format` | string | png | Output format (png, jpeg, webp, bmp, ico) (not all browsers support all types, png is always supported, others are optional) | -| `outputType` | string | base64 | Output type ('base64', 'file' or 'both'). Converting the image to a Blob can be quite a heavy operation. With this option, you could choose to only get the base64 which will improve the speed of cropping significantly | | `aspectRatio` | number | 1 / 1 | The width / height ratio (e.g. 1 / 1 for a square, 4 / 3, 16 / 9 ...) | | `maintainAspectRatio` | boolean | true | Keep width and height of cropped image equal according to the aspectRatio | | `containWithinAspectRatio` | boolean | false | When set to true, padding will be added around the image to make it fit to the aspect ratio | @@ -122,7 +120,7 @@ To gain access to the image cropper's methods use `@ViewChild(ImageCropperCompon | Name | Returns | Description | | ----------------------- | ----------------- | ----------- | -| `crop` | ImageCroppedEvent (when `outputType` is `base64`) or Promise<ImageCroppedEvent> (when `outputType` is `file` or `both`)) | Crops the source image to the current cropper position. Accepts an output type as an argument, default is the one given in the `outputType` input (`base64`, `file` or `both`). Be sure to set `autoCrop` to `false` if you only wish to use this function directly. | +| `crop` | ImageCroppedEvent | Crops the source image to the current cropper position. Be sure to set `autoCrop` to `false` if you only wish to use this function directly. | ### Interfaces #### CropperPosition @@ -150,29 +148,3 @@ To gain access to the image cropper's methods use `@ViewChild(ImageCropperCompon | cropperPosition | CropperPosition | Position of the cropper when it was cropped relative to the displayed image size | | imagePosition | CropperPosition | Position of the cropper when it was cropped relative to the original image size | | offsetImagePosition | CropperPosition | Position of the cropper when it was cropped relative to the original image size without padding when containWithinAspectRatio is true | - - -### Polyfill for IE and Edge -If you wish to use the file output, you'll need to polyfill the `toBlob` method of the HTML Canvas for IE and Edge. -``` -if (!HTMLCanvasElement.prototype.toBlob) { - Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { - value: function (callback, type, quality) { - var dataURL = this.toDataURL(type, quality).split(',')[1]; - setTimeout(function() { - - var binStr = atob( dataURL ), - len = binStr.length, - arr = new Uint8Array(len); - - for (var i = 0; i < len; i++ ) { - arr[i] = binStr.charCodeAt(i); - } - - callback( new Blob( [arr], {type: type || 'image/png'} ) ); - - }); - } - }); -} -``` diff --git a/projects/demo-app/src/app/app.component.html b/projects/demo-app/src/app/app.component.html index b21e94c..e5638f2 100644 --- a/projects/demo-app/src/app/app.component.html +++ b/projects/demo-app/src/app/app.component.html @@ -28,7 +28,6 @@ [alignImage]="'left'" [style.display]="showCropper ? null : 'none'" format="png" - outputType="base64" (imageCropped)="imageCropped($event)" (imageLoaded)="imageLoaded()" (cropperReady)="cropperReady($event)" diff --git a/projects/demo-app/src/app/app.component.ts b/projects/demo-app/src/app/app.component.ts index 6b2dc02..137edbe 100644 --- a/projects/demo-app/src/app/app.component.ts +++ b/projects/demo-app/src/app/app.component.ts @@ -1,5 +1,6 @@ import { Component, ViewChild } from '@angular/core'; import { Dimensions, ImageCroppedEvent, ImageCropperComponent, ImageTransform } from 'ngx-image-cropper'; +import { base64ToFile } from 'ngx-image-cropper'; @Component({ selector: 'app-root', @@ -24,7 +25,7 @@ export class AppComponent { imageCropped(event: ImageCroppedEvent) { this.croppedImage = event.base64; - console.log(event); + console.log(event, base64ToFile(event.base64)); } imageLoaded() { diff --git a/projects/ngx-image-cropper/package.json b/projects/ngx-image-cropper/package.json index ac1d1be..641897b 100644 --- a/projects/ngx-image-cropper/package.json +++ b/projects/ngx-image-cropper/package.json @@ -1,6 +1,6 @@ { "name": "ngx-image-cropper", - "version": "2.1.2", + "version": "3.0.0", "description": "An image cropper for Angular", "author": "Martijn Willekens", "repository": { @@ -16,6 +16,8 @@ "angular 6", "angular 7", "angular 8", + "ionic", + "ionic-angular", "cropper", "image cropper" ], diff --git a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts index d0f8f28..67a7ded 100644 --- a/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts +++ b/projects/ngx-image-cropper/src/lib/component/image-cropper.component.ts @@ -20,8 +20,6 @@ import { getTransformationsFromExifData } from '../utils/exif.utils'; import { resizeCanvas } from '../utils/resize.utils'; import { ExifTransform } from '../interfaces/exif-transform.interface'; -export type OutputType = 'base64' | 'file' | 'both'; - @Component({ selector: 'image-cropper', templateUrl: './image-cropper.component.html', @@ -73,7 +71,6 @@ export class ImageCropperComponent implements OnChanges, OnInit { } @Input() format: 'png' | 'jpeg' | 'bmp' | 'webp' | 'ico' = 'png'; - @Input() outputType: OutputType = 'base64'; @Input() maintainAspectRatio = true; @Input() transform: ImageTransform = {}; @Input() aspectRatio = 1; @@ -643,7 +640,7 @@ export class ImageCropperComponent implements OnChanges, OnInit { } } - crop(outputType: OutputType = this.outputType): ImageCroppedEvent | Promise | null { + crop(): ImageCroppedEvent | null { if (this.sourceImage && this.sourceImage.nativeElement && this.transformedImage != null) { this.startCropImage.emit(); const imagePosition = this.getImagePosition(); @@ -685,7 +682,9 @@ export class ImageCropperComponent implements OnChanges, OnInit { : Math.round(height * resizeRatio); resizeCanvas(cropCanvas, output.width, output.height); } - return this.cropToOutputType(outputType, cropCanvas, output); + output.base64 = this.cropToBase64(cropCanvas); + this.imageCropped.emit(output); + return output; } } return null; @@ -744,44 +743,10 @@ export class ImageCropperComponent implements OnChanges, OnInit { return out; } - private cropToOutputType(outputType: OutputType, cropCanvas: HTMLCanvasElement, output: ImageCroppedEvent): ImageCroppedEvent | Promise { - switch (outputType) { - case 'file': - return this.cropToFile(cropCanvas) - .then((result: Blob | null) => { - output.file = result; - this.imageCropped.emit(output); - return output; - }); - case 'both': - output.base64 = this.cropToBase64(cropCanvas); - return this.cropToFile(cropCanvas) - .then((result: Blob | null) => { - output.file = result; - this.imageCropped.emit(output); - return output; - }); - default: - output.base64 = this.cropToBase64(cropCanvas); - this.imageCropped.emit(output); - return output; - } - } - private cropToBase64(cropCanvas: HTMLCanvasElement): string { return cropCanvas.toDataURL('image/' + this.format, this.getQuality()); } - private cropToFile(cropCanvas: HTMLCanvasElement): Promise { - return new Promise((resolve) => { - cropCanvas.toBlob( - (result: Blob | null) => this.zone.run(() => resolve(result)), - 'image/' + this.format, - this.getQuality() - ); - }); - } - private getQuality(): number { return Math.min(1, Math.max(0, this.imageQuality / 100)); } @@ -797,7 +762,6 @@ export class ImageCropperComponent implements OnChanges, OnInit { } } return 1; - } private getClientX(event: any): number { diff --git a/projects/ngx-image-cropper/src/lib/utils/blob.utils.ts b/projects/ngx-image-cropper/src/lib/utils/blob.utils.ts new file mode 100644 index 0000000..f0638c5 --- /dev/null +++ b/projects/ngx-image-cropper/src/lib/utils/blob.utils.ts @@ -0,0 +1,11 @@ +export function base64ToFile(image): Blob { + const split = image.split(','); + const type = split[0].replace('data:', '').replace(';base64', ''); + const byteString = atob(split[1]); + const ab = new ArrayBuffer(byteString.length); + const ia = new Uint8Array(ab); + for (let i = 0; i < byteString.length; i += 1) { + ia[i] = byteString.charCodeAt(i); + } + return new Blob([ab], {type}); +} diff --git a/projects/ngx-image-cropper/src/public-api.ts b/projects/ngx-image-cropper/src/public-api.ts index b856eac..334f9fe 100644 --- a/projects/ngx-image-cropper/src/public-api.ts +++ b/projects/ngx-image-cropper/src/public-api.ts @@ -1,3 +1,4 @@ export * from './lib/image-cropper.module'; export * from './lib/component/image-cropper.component'; export * from './lib/interfaces'; +export { base64ToFile } from './lib/utils/blob.utils';