@acorex/components 20.1.37 → 20.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ import { AXLoadingSpinnerComponent, AXLoadingModule } from '@acorex/components/l
6
6
  import * as i2$1 from '@acorex/components/toolbar';
7
7
  import { AXToolBarModule } from '@acorex/components/toolbar';
8
8
  import * as i0 from '@angular/core';
9
- import { signal, Injectable, inject, output, ViewEncapsulation, Component, ChangeDetectionStrategy, input, NgZone, ElementRef, viewChild, Renderer2, PLATFORM_ID, effect, afterNextRender, computed, linkedSignal, HostBinding, contentChild, forwardRef, NgModule } from '@angular/core';
9
+ import { signal, Injectable, inject, output, ViewEncapsulation, Component, ChangeDetectionStrategy, input, NgZone, ElementRef, viewChild, Renderer2, PLATFORM_ID, effect, afterNextRender, linkedSignal, contentChild, forwardRef, HostBinding, NgModule } from '@angular/core';
10
10
  import * as i1 from '@angular/forms';
11
11
  import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
12
12
  import { classes } from 'polytype';
@@ -31,8 +31,8 @@ class AXImageEditorService {
31
31
  this.penType = signal(null, ...(ngDevMode ? [{ debugName: "penType" }] : []));
32
32
  this.rotate = signal({ state: 0, userInteract: false }, ...(ngDevMode ? [{ debugName: "rotate" }] : []));
33
33
  this.isImageLoad = signal(false, ...(ngDevMode ? [{ debugName: "isImageLoad" }] : []));
34
- this.newImage = signal(null, ...(ngDevMode ? [{ debugName: "newImage" }] : []));
35
- this.initialImage = signal(null, ...(ngDevMode ? [{ debugName: "initialImage" }] : []));
34
+ this.newImage = null;
35
+ this.initialImage = null;
36
36
  this.imageBlob = signal([], ...(ngDevMode ? [{ debugName: "imageBlob" }] : []));
37
37
  this.undoPressed = signal(false, ...(ngDevMode ? [{ debugName: "undoPressed" }] : []));
38
38
  this.redoPressed = signal(false, ...(ngDevMode ? [{ debugName: "redoPressed" }] : []));
@@ -424,28 +424,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImpor
424
424
  class AXImageEditorViewComponent extends MXBaseComponent {
425
425
  /** @ignore */
426
426
  resizeWindow() {
427
- this.canvasResponsive(this.service.newImage());
427
+ this.canvasResponsive(this.service.newImage);
428
428
  }
429
429
  /** @ignore */
430
430
  canvasResponsive(image) {
431
- if (!this.parent.imageEditorContainer())
432
- return;
433
- const parentWidth = this.parent.imageEditorContainer().nativeElement.clientWidth;
434
- const imageWidth = this.service.initialImage().width;
435
- if (parentWidth < imageWidth) {
436
- this.getHostElement().style.width = `${parentWidth}px`;
437
- this.getHostElement().style.height = `${parentWidth * this.imageAspect()}px`;
438
- this.canvasElem().nativeElement.width = parentWidth;
439
- this.canvasElem().nativeElement.height = parentWidth * this.imageAspect();
440
- this.ctx().drawImage(image, 0, 0, parentWidth, parentWidth * this.imageAspect());
431
+ const imageWidth = image.width;
432
+ const imageHeight = image.height;
433
+ this.originalWidth.set(imageWidth);
434
+ this.originalHeight.set(imageHeight);
435
+ if (imageWidth > imageHeight) {
436
+ this.canvasElem().nativeElement.width = imageWidth;
437
+ this.canvasElem().nativeElement.height = imageWidth;
441
438
  }
442
439
  else {
443
- this.getHostElement().style.width = `${imageWidth}px`;
444
- this.getHostElement().style.height = `${imageWidth / this.imageAspect()}px`;
445
- this.canvasElem().nativeElement.width = imageWidth;
446
- this.canvasElem().nativeElement.height = imageWidth / this.imageAspect();
447
- this.ctx().drawImage(image, 0, 0, imageWidth, imageWidth / this.imageAspect());
440
+ this.canvasElem().nativeElement.width = imageHeight;
441
+ this.canvasElem().nativeElement.height = imageHeight;
448
442
  }
443
+ this.canvasElem().nativeElement.style.width = '100%';
444
+ requestAnimationFrame(() => {
445
+ //recalculate width after canvas size change
446
+ const recalculateWidth = this.canvasElem().nativeElement.getClientRects()[0]?.width;
447
+ let scale;
448
+ if (imageWidth > imageHeight) {
449
+ scale = this.originalWidth() / recalculateWidth;
450
+ }
451
+ else {
452
+ scale = this.originalHeight() / recalculateWidth;
453
+ }
454
+ this.scale.set(scale);
455
+ this.canvasElem().nativeElement.width = recalculateWidth;
456
+ this.canvasElem().nativeElement.height = recalculateWidth;
457
+ if (imageWidth > imageHeight) {
458
+ this.ctx().drawImage(image, 0, recalculateWidth / 2 - this.originalHeight() / scale / 2, recalculateWidth, this.originalHeight() / scale);
459
+ }
460
+ else {
461
+ this.ctx().drawImage(image, recalculateWidth / 2 - this.originalWidth() / scale / 2, 0, this.originalWidth() / scale, recalculateWidth);
462
+ }
463
+ });
449
464
  }
450
465
  /** @ignore */
451
466
  constructor() {
@@ -479,29 +494,21 @@ class AXImageEditorViewComponent extends MXBaseComponent {
479
494
  /** @ignore */
480
495
  this.renderer = inject(Renderer2);
481
496
  /** @ignore */
482
- this.imageAspect = computed(() => {
483
- return this.service.initialImage().width / this.service.initialImage().height;
484
- }, ...(ngDevMode ? [{ debugName: "imageAspect" }] : []));
485
- /** @ignore */
486
497
  this.imageEditorHistory = signal([], ...(ngDevMode ? [{ debugName: "imageEditorHistory" }] : []));
487
498
  /** @ignore */
488
499
  this.imageEditorHistoryPointer = linkedSignal(() => this.imageEditorHistory().length - 1);
500
+ this.originalWidth = signal(0, ...(ngDevMode ? [{ debugName: "originalWidth" }] : []));
501
+ this.originalHeight = signal(0, ...(ngDevMode ? [{ debugName: "originalHeight" }] : []));
502
+ this.scale = signal(0, ...(ngDevMode ? [{ debugName: "scale" }] : []));
489
503
  afterNextRender(() => {
490
504
  this.resizeEventListener = this.renderer.listen(window, 'resize', this.resizeWindow.bind(this));
491
- //load image
492
- this.service.initialImage.set(new Image());
493
- this.service.initialImage().src = this.src();
494
- this.service.initialImage().crossOrigin = 'anonymous';
495
- this.imageLoadEventListener = this.renderer.listen(this.service.initialImage(), 'load', this.imageLoaded.bind(this));
496
505
  //set canvas options
497
506
  this.ctx.set(this.canvasElem().nativeElement.getContext('2d', { willReadFrequently: true }));
498
507
  this.ctx().lineJoin = 'round';
499
508
  this.ctx().lineCap = 'round';
500
509
  });
501
510
  effect(() => {
502
- if (this.parent.imageEditorContainer() && this.service.isImageLoad()) {
503
- this.canvasResponsive(this.service.initialImage());
504
- }
511
+ this.setInitialImage(this.src());
505
512
  });
506
513
  effect(() => {
507
514
  if (!this.service.undoPressed()) {
@@ -528,22 +535,27 @@ class AXImageEditorViewComponent extends MXBaseComponent {
528
535
  this.imageLoadEventListener();
529
536
  }
530
537
  }
531
- /** @ignore */
532
- imageLoaded() {
533
- this.imageEditorHistory.set([this.service.initialImage()]);
534
- this.service.newImage.set(this.service.initialImage());
535
- this.service.isImageLoad.set(true);
538
+ setInitialImage(src) {
539
+ this.service.initialImage = new Image();
540
+ this.service.initialImage.src = src;
541
+ this.service.initialImage.crossOrigin = 'anonymous';
542
+ this.imageLoadEventListener = this.renderer.listen(this.service.initialImage, 'load', () => {
543
+ this.imageEditorHistory.set([this.service.initialImage]);
544
+ this.service.newImage = this.service.initialImage;
545
+ this.service.isImageLoad.set(true);
546
+ this.canvasResponsive(this.service.initialImage);
547
+ });
536
548
  }
537
549
  /** @ignore */
538
550
  imageChanged() {
539
551
  if (this.service.undoPressed() && this.imageEditorHistoryPointer() >= 1) {
540
552
  this.imageEditorHistoryPointer.update((prev) => prev - 1);
541
- this.service.newImage.set(this.imageEditorHistory()[this.imageEditorHistoryPointer()]);
553
+ this.service.newImage = this.imageEditorHistory()[this.imageEditorHistoryPointer()];
542
554
  this.canvasResponsive(this.imageEditorHistory()[this.imageEditorHistoryPointer()]);
543
555
  }
544
556
  if (this.service.redoPressed() && this.imageEditorHistoryPointer() < this.imageEditorHistory().length - 1) {
545
557
  this.imageEditorHistoryPointer.update((prev) => prev + 1);
546
- this.service.newImage.set(this.imageEditorHistory()[this.imageEditorHistoryPointer()]);
558
+ this.service.newImage = this.imageEditorHistory()[this.imageEditorHistoryPointer()];
547
559
  this.canvasResponsive(this.imageEditorHistory()[this.imageEditorHistoryPointer()]);
548
560
  }
549
561
  this.service.undoPressed.set(false);
@@ -565,10 +577,6 @@ class AXImageEditorViewComponent extends MXBaseComponent {
565
577
  }
566
578
  }
567
579
  /** @ignore */
568
- getBoundingCanvasHandler() {
569
- return this.canvasElem().nativeElement.getBoundingClientRect();
570
- }
571
- /** @ignore */
572
580
  mouseDownHandler(e) {
573
581
  this.isMouseDown.set(true);
574
582
  if (!this.service.penType())
@@ -604,7 +612,8 @@ class AXImageEditorViewComponent extends MXBaseComponent {
604
612
  this.canvasElem().nativeElement.width = cropRect.width;
605
613
  this.canvasElem().nativeElement.height = cropRect.height;
606
614
  this.ctx().putImageData(imageData, 0, 0);
607
- this.saveImageChange();
615
+ const cropImage = this.canvasElem().nativeElement.toDataURL('image/jpeg', 1);
616
+ this.setInitialImage(cropImage);
608
617
  this.service.activeToolState.set(null);
609
618
  }
610
619
  /** @ignore */
@@ -612,9 +621,19 @@ class AXImageEditorViewComponent extends MXBaseComponent {
612
621
  this.ctx().globalAlpha = 1;
613
622
  this.ctx().save();
614
623
  this.ctx().clearRect(0, 0, this.canvasElem().nativeElement.width, this.canvasElem().nativeElement.height);
615
- this.ctx().translate(this.service.newImage().width / 2, this.service.newImage().height / 2);
616
- this.ctx().rotate((this.service.rotate().state * Math.PI) / 180);
617
- this.ctx().drawImage(this.service.newImage(), -this.service.newImage().width / 2, -this.service.newImage().height / 2, this.service.newImage().width, this.service.newImage().height);
624
+ // Calculate scale factor to fit rotated image within canvas
625
+ const rotationAngle = (this.service.rotate().state * Math.PI) / 180;
626
+ const cos = Math.abs(Math.cos(rotationAngle));
627
+ const sin = Math.abs(Math.sin(rotationAngle));
628
+ const rotatedWidth = this.service.newImage.width * cos + this.service.newImage.height * sin;
629
+ const rotatedHeight = this.service.newImage.width * sin + this.service.newImage.height * cos;
630
+ const scaleX = this.canvasElem().nativeElement.width / rotatedWidth;
631
+ const scaleY = this.canvasElem().nativeElement.height / rotatedHeight;
632
+ const scale = Math.min(scaleX, scaleY);
633
+ this.ctx().translate(this.canvasElem().nativeElement.width / 2, this.canvasElem().nativeElement.height / 2);
634
+ this.ctx().rotate(rotationAngle);
635
+ this.ctx().scale(scale, scale);
636
+ this.ctx().drawImage(this.service.newImage, -this.service.newImage.width / 2, -this.service.newImage.height / 2);
618
637
  this.ctx().restore();
619
638
  this.saveImageChange();
620
639
  this.service.rotate.update((prev) => {
@@ -626,7 +645,7 @@ class AXImageEditorViewComponent extends MXBaseComponent {
626
645
  const newImage = new Image();
627
646
  newImage.crossOrigin = 'anonymous';
628
647
  newImage.src = this.canvasElem().nativeElement.toDataURL('image/jpeg', 1);
629
- this.service.newImage.set(newImage);
648
+ this.service.newImage = newImage;
630
649
  this.imageEditorHistory.update((prev) => [...prev, newImage]);
631
650
  const tempBlob = [];
632
651
  this.canvasElem().nativeElement.toBlob((blob) => {
@@ -640,20 +659,13 @@ class AXImageEditorViewComponent extends MXBaseComponent {
640
659
  }, 'image/webp', 1);
641
660
  this.service.imageBlob.set(tempBlob);
642
661
  }
643
- /** @ignore */
644
- get __hostStyle() {
645
- return `width: ${this.service.initialImage()?.width}px; height: ${this.service.initialImage()?.height}px`;
646
- }
647
662
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: AXImageEditorViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
648
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: AXImageEditorViewComponent, isStandalone: true, selector: "ax-image-editor-view", inputs: { showGrid: { classPropertyName: "showGrid", publicName: "showGrid", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style": "this.__hostStyle" } }, providers: [{ provide: AXComponent, useExisting: AXImageEditorViewComponent }], viewQueries: [{ propertyName: "canvasElem", first: true, predicate: ["canvasElem"], descendants: true, isSignal: true }, { propertyName: "cropperWindow", first: true, predicate: ["f"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<canvas\n tabindex=\"1\"\n (pointerdown)=\"mouseDownHandler($event)\"\n (pointerup)=\"mouseUpHandler()\"\n (pointermove)=\"mouseMoveHandler($event)\"\n class=\"ax-canvas-element\"\n #canvasElem\n></canvas>\n\n@if (this.service.activeToolState() === 'crop') {\n <ax-image-editor-cropper-window [showGrid]=\"showGrid()\" #f></ax-image-editor-cropper-window>\n}\n", styles: ["ax-image-editor-view{position:relative;display:flex;justify-content:center;align-items:center;background-color:#fff;width:100%;touch-action:none;overflow:hidden}ax-image-editor-view .ax-canvas-element{cursor:crosshair}ax-image-editor-view .ax-crop-save{position:absolute;top:0;right:0;margin:1rem}\n"], dependencies: [{ kind: "component", type: AXImageEditorCropperWindowComponent, selector: "ax-image-editor-cropper-window", inputs: ["showGrid"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
663
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.4", type: AXImageEditorViewComponent, isStandalone: true, selector: "ax-image-editor-view", inputs: { showGrid: { classPropertyName: "showGrid", publicName: "showGrid", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: AXComponent, useExisting: AXImageEditorViewComponent }], viewQueries: [{ propertyName: "canvasElem", first: true, predicate: ["canvasElem"], descendants: true, isSignal: true }, { propertyName: "cropperWindow", first: true, predicate: ["f"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<canvas\n tabindex=\"1\"\n (pointerdown)=\"mouseDownHandler($event)\"\n (pointerup)=\"mouseUpHandler()\"\n (pointermove)=\"mouseMoveHandler($event)\"\n class=\"ax-canvas-element\"\n #canvasElem\n></canvas>\n\n@if (this.service.activeToolState() === 'crop') {\n <ax-image-editor-cropper-window [showGrid]=\"showGrid()\" #f></ax-image-editor-cropper-window>\n}\n", styles: ["ax-image-editor-view{position:relative;display:flex;justify-content:center;align-items:center;background-color:#fff;width:100%;touch-action:none;overflow:hidden}ax-image-editor-view .ax-canvas-element{cursor:crosshair;background-color:#000}ax-image-editor-view .ax-crop-save{position:absolute;top:0;right:0;margin:1rem}\n"], dependencies: [{ kind: "component", type: AXImageEditorCropperWindowComponent, selector: "ax-image-editor-cropper-window", inputs: ["showGrid"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
649
664
  }
650
665
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: AXImageEditorViewComponent, decorators: [{
651
666
  type: Component,
652
- args: [{ selector: 'ax-image-editor-view', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [AXImageEditorCropperWindowComponent], providers: [{ provide: AXComponent, useExisting: AXImageEditorViewComponent }], template: "<canvas\n tabindex=\"1\"\n (pointerdown)=\"mouseDownHandler($event)\"\n (pointerup)=\"mouseUpHandler()\"\n (pointermove)=\"mouseMoveHandler($event)\"\n class=\"ax-canvas-element\"\n #canvasElem\n></canvas>\n\n@if (this.service.activeToolState() === 'crop') {\n <ax-image-editor-cropper-window [showGrid]=\"showGrid()\" #f></ax-image-editor-cropper-window>\n}\n", styles: ["ax-image-editor-view{position:relative;display:flex;justify-content:center;align-items:center;background-color:#fff;width:100%;touch-action:none;overflow:hidden}ax-image-editor-view .ax-canvas-element{cursor:crosshair}ax-image-editor-view .ax-crop-save{position:absolute;top:0;right:0;margin:1rem}\n"] }]
653
- }], ctorParameters: () => [], propDecorators: { __hostStyle: [{
654
- type: HostBinding,
655
- args: ['style']
656
- }] } });
667
+ args: [{ selector: 'ax-image-editor-view', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [AXImageEditorCropperWindowComponent], providers: [{ provide: AXComponent, useExisting: AXImageEditorViewComponent }], template: "<canvas\n tabindex=\"1\"\n (pointerdown)=\"mouseDownHandler($event)\"\n (pointerup)=\"mouseUpHandler()\"\n (pointermove)=\"mouseMoveHandler($event)\"\n class=\"ax-canvas-element\"\n #canvasElem\n></canvas>\n\n@if (this.service.activeToolState() === 'crop') {\n <ax-image-editor-cropper-window [showGrid]=\"showGrid()\" #f></ax-image-editor-cropper-window>\n}\n", styles: ["ax-image-editor-view{position:relative;display:flex;justify-content:center;align-items:center;background-color:#fff;width:100%;touch-action:none;overflow:hidden}ax-image-editor-view .ax-canvas-element{cursor:crosshair;background-color:#000}ax-image-editor-view .ax-crop-save{position:absolute;top:0;right:0;margin:1rem}\n"] }]
668
+ }], ctorParameters: () => [] });
657
669
 
658
670
  /**
659
671
  * paint container.
@@ -674,7 +686,7 @@ class AXImageEditorContainerComponent extends classes((MXInputBaseValueComponent
674
686
  * @returns void - No return value. The edited image is saved and the onValueChanged event is emitted.
675
687
  */
676
688
  save() {
677
- if (this.service.initialImage() !== this.service.newImage()) {
689
+ if (this.service.initialImage !== this.service.newImage) {
678
690
  this.commitValue(this.service.imageBlob(), true);
679
691
  }
680
692
  else {