Mawi137 / ngx-image-cropper

An image cropper for Angular
MIT License
786 stars 210 forks source link

How to replay the cropping process on an array of images? #615

Closed SoulDee closed 9 months ago

SoulDee commented 10 months ago

I'm trying to crop a GIF image, but the output format is PNG. After some consideration, I came up with a method, which is to obtain a PNG image array from the GIF image through another process. Then, apply the cropping process of ngx-image-cropper to each image to obtain a cropped array.

At first, my idea was to obtain an array of images, then replace the images. However, when I tried opening another image, the component's state was reset. The source code confirmed my suspicion, so this approach cannot be used to crop an array of images.

loiddy commented 10 months ago

How the app works now, I reckon you can...

  1. Deep clone the transform and cropper (cropper position) inputs after you have cropped your first img in the array. If there's problems with your cropper input not being updated to the latest values, cropping triggers the ImageCroppedEvent that sends an object with data including the cropperPosition. Make sure to deep clone these variables so the property values aren't updated later on. They are objects. {...obj} works.
  2. Load the next img in the array.
  3. Wait till the cropper is ready.
  4. Send in the copied transform and cropper inputs. If mantainAspect ratio was true before, make it false. Having it as true triggers a "new" cropper, overriding the cropper you send in.
  5. Crop or use auto crop and repeat with the next img.

If you want to see the cropper load the next img showing the state of the previous img, keep the cropper visibly hidden till you get the cropperReady event, pass in the previous cropper and transform and then change it back to visible.

I haven't tried this. But looking at how the app is now it should work. Happy to look at it if it doesn't work and you set up an example. Best of luck :)

SoulDee commented 9 months ago

How the app works now, I reckon you can...

  1. Deep clone the transform and cropper (cropper position) inputs after you have cropped your first img in the array. If there's problems with your cropper input not being updated to the latest values, cropping triggers the ImageCroppedEvent that sends an object with data including the cropperPosition. Make sure to deep clone these variables so the property values aren't updated later on. They are objects. {...obj} works.
  2. Load the next img in the array.
  3. Wait till the cropper is ready.
  4. Send in the copied transform and cropper inputs. If mantainAspect ratio was true before, make it false. Having it as true triggers a "new" cropper, overriding the cropper you send in.
  5. Crop or use auto crop and repeat with the next img.

If you want to see the cropper load the next img showing the state of the previous img, keep the cropper visibly hidden till you get the cropperReady event, pass in the previous cropper and transform and then change it back to visible.

I haven't tried this. But looking at how the app is now it should work. Happy to look at it if it doesn't work and you set up an example. Best of luck :)

Thank you for your help. Now my app is working, but there's a small issue. The new cropper doesn't work in cropperReady, but the transform works. After adding a setTimeout, the new cropper works well. I have provided code in the reply below.

Anyway, it does work now! (^◡^)

SoulDee commented 9 months ago
import {Component, ViewChild} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser';
import {CropperPosition, ImageCropperComponent, ImageTransform} from 'ngx-image-cropper';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="loadImage()">Load Image</button>
    <button (click)="zoomIn()">Zoom In</button>
    <button (click)="zoomOut()">Zoom Out</button>
    <button (click)="crop()">Run Crop Gif</button>

    <div class="cropper-wrapper">
      <image-cropper
        #imageCropperRef
        [imageURL]="imageUrl"
        [autoCrop]="false"
        [maintainAspectRatio]="false"
        [(transform)]="transform"
        [cropper]="cropper"
        (cropperReady)="cropperReady()"
      ></image-cropper>
    </div>

    <ng-container *ngIf="!loading">
      <div *ngFor="let frame of croppedImages">
        <img [src]="frame" />
      </div>
    </ng-container>
  `,
  styles: [
    `
      image-cropper {
        max-height: 50vh;
      }

      .cropper-wrapper {
        min-height: 300px;
        position: relative;
      }
    `,
  ],
})
export class AppComponent {
  @ViewChild('imageCropperRef') imageCropperRef?: ImageCropperComponent;

  croppedImages: any[] = [];

  imageUrl?: string;
  imageUrls = ['assets/cat_1.jpg', 'assets/cat_2.jpg', 'assets/cat_3.jpg'];
  loadCursor = 0;

  scale = 1;
  transform: ImageTransform = {
    translateUnit: 'px',
  };

  cropper: CropperPosition = {
    x1: 0,
    y1: 0,
    x2: 160,
    y2: 160,
  };

  loading = false;

  cacheTransform: ImageTransform = {
    translateUnit: 'px',
  };

  cacheCropper: CropperPosition = {
    x1: 0,
    y1: 0,
    x2: 160,
    y2: 160,
  };

  constructor(private sanitizer: DomSanitizer) {}

  loadImage() {
    if (this.loadCursor < this.imageUrls.length) {
      this.imageUrl = this.imageUrls[this.loadCursor];
    }
  }

  addCropped() {
    const event = this.imageCropperRef?.crop('base64'); // Using Blob will return a Promise.

    if (event) {
      const url = this.sanitizer.bypassSecurityTrustUrl(event.objectUrl || event.base64 || '');
      this.croppedImages.push(url);
    }
  }

  cropperReady() {
    if (this.loading) {
      this.transform = {...this.cacheTransform};
      this.cropper = {...this.cacheCropper};

      setTimeout(() => {
        // If there is no setTimeout, the changes to the cropper will not take effect.
        this.addCropped();
        this.loadNextImage();
      }, 0);
    }
  }

  loadNextImage() {
    this.loadCursor++;

    if (this.loadCursor >= this.imageUrls.length) {
      this.loadCursor = 0;
      this.loading = false;
    }

    this.loadImage();
  }

  zoomOut() {
    this.scale -= 0.1;
    this.transform = {
      ...this.transform,
      scale: this.scale,
    };
  }

  zoomIn() {
    this.scale += 0.1;
    this.transform = {
      ...this.transform,
      scale: this.scale,
    };
  }

  crop() {
    this.loading = true;
    this.croppedImages = [];
    this.cacheTransform = {...this.transform};
    this.cacheCropper = {...this.cropper};
    this.addCropped();
    this.loadNextImage();
  }
}
loiddy commented 9 months ago

not sure why that's happening but happy you found a solution for it