Capacitor Camera - Compressed Image

Hi, wondering if someone could help me with an issue I’m facing.

On my app when I take a photo, the preview of the image is fine as it’s the preview from the native camera. When I submit the image to my app, the image is really compressed.

My image quality is set to 100. The height and width are set to 450x450 or 900x900, depending on if a Small/Large Image field is added to a form.

Taking a photo of text on a page is completely unreadable. Our app contractor left us so trying to get my bearings about me but getting nowhere fast.

Here is my image.component code. Apologies if there’s lack of detail as I’m on my phone doing this, I can provide more details tomorrow when I’m at my machine. Thank you in advance.

Ionic:

Ionic CLI : 7.1.1
Ionic Framework : @ionic/angular 6.5.4
@angular-devkit/build-angular : 13.3.11
@angular-devkit/schematics : 13.3.11
@angular/cli : 13.3.11
@ionic/angular-toolkit : 6.1.0

Capacitor:

Capacitor CLI : 4.6.1
@capacitor/android : 4.6.1
@capacitor/core : 4.6.1
@capacitor/ios : 4.6.1

Utility:

cordova-res : 0.15.4
native-run : 1.7.1

System:

NodeJS : v18.18.2 (C:\Program Files\nodejs\node.exe)
npm : 9.8.1
OS : Windows 10

import {Component, forwardRef, Input} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {ModalController} from '@ionic/angular';
import {AppService} from 'src/app/core/app.service';
import {FileTransferService} from 'src/app/core/file-transfer.service';
import * as Globals from '../../../../../core/globals';
import {EditImageComponent} from '../edit-image/edit-image.component';
import {HttpService} from '../../../../../core/http.service';
import {
  Camera,
  CameraDirection,
  CameraPluginPermissions,
  CameraResultType,
  CameraSource,
  ImageOptions,
} from '@capacitor/camera';

export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => ImageSingleComponent),
  multi: true,
};

@Component({
  selector: 'fc-image',
  templateUrl: './image.component.html',
  styleUrls: ['./image.component.scss'],
  providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR],
})
export class ImageSingleComponent implements ControlValueAccessor {
  public _maxWidthOrHeight = Globals.LARGE_IMAGE_SIZE;
  public stdWidthOrHeight = Globals.STD_IMAGE_SIZE;
  private _value: string = '';
  private serverFolder: string;
  private deviceFolder: string;
  public img: string;
  public initialized: boolean = false;
  public options: ImageOptions = {} as ImageOptions;

  public specificationFolderPath = 'Specifications';


  @Input() label: string = '';
  @Input() specId: number;
  @Input() docId: number;
  @Input() supplierCheckTemplateId: number;
  @Input() checklistTemplateId: number;
  @Input() questionnaireId: number;
  @Input() meetingId;
  @Input() viewOnly: boolean = false;
  @Input() comment: string = '';
  @Input() isSignature: boolean = false;
  @Input() specCompanyId: number;
  @Input() isReceived: boolean;

  @Input() set maxWidthOrHeight(value: number) {
    if (typeof value === 'number') {
      this._maxWidthOrHeight = value;

      if (this.options) {
        this.options.height = this.options.width = value;
      }
    }
  }

  get maxWidthOrHeight() {
    return this._maxWidthOrHeight;
  }

  constructor(
    private fileTransfer: FileTransferService,
    private mdl: ModalController,
    httpService: HttpService,
    // private androidPermissions: AndroidPermissions,
    private app: AppService
  ) {

    if (httpService.apiModeName == 'Live') {
      this.specificationFolderPath = "specifications";
    }
  }

  ngOnInit() {
    this.options.quality = 100;
    this.options.resultType = CameraResultType.DataUrl;
    this.options.correctOrientation = true;

    //removing these options as they are only using part of image if both together and using one drastically affects quality of the images
    //this.options.height = this.maxWidthOrHeight;
    //this.options.width = this.maxWidthOrHeight;
    this.options.source = CameraSource.Camera;

    if (this.specId && this.specId > 0) {
      if (this.isReceived) {
        this.serverFolder =
          this.specCompanyId +
          +
          '/'
          + this.specificationFolderPath +
          '/' +
          this.specId +
          '/';
      } else {
        this.serverFolder =
          Globals.currentUser().companyId +
          '/'
          + this.specificationFolderPath +
          '/' +
          this.specId +
          '/';
      }
      this.deviceFolder = this.specificationFolderPath +
        '/' + this.specId + '/';
    } else if (this.docId && this.docId > 0) {
      this.serverFolder =
        Globals.currentUser().companyId +
        '/Documents/' +
        this.docId +
        '/';
      this.deviceFolder = 'Documents/' + this.docId + '/';
    } else if (
      this.supplierCheckTemplateId &&
      this.supplierCheckTemplateId > 0
    ) {
      this.serverFolder =
        Globals.currentUser().companyId +
        '/SupplierChecks/' +
        this.supplierCheckTemplateId +
        '/';
      this.deviceFolder =
        'SupplierChecks/' + this.supplierCheckTemplateId + '/';
    } else if (
      this.checklistTemplateId &&
      this.checklistTemplateId > 0
    ) {
      this.serverFolder =
        Globals.currentUser().companyId +
        '/Checklists/' +
      this.checklistTemplateId +
        '/';
      this.deviceFolder =
        'Checklists/' + this.checklistTemplateId + '/';
    } else if (
      this.questionnaireId &&
      this.questionnaireId > 0
    ) {
      this.serverFolder =
        Globals.currentUser().companyId +
        '/Questionnaires/' +
        this.questionnaireId +
        '/';
      this.deviceFolder =
        'Questionnaires/' + this.questionnaireId + '/';
    } else if (this.meetingId && this.meetingId > 0) {
      this.serverFolder =
        Globals.currentUser().companyId +
        '/Meetings/' +
        this.meetingId +
        '/';
      this.deviceFolder = 'Meetings/' + this.meetingId + '/';
    }
  }

  async openCamera(type: CameraSource = CameraSource.Camera) {
    try {
      const notGrantedErr =
        'Read and write file system permissions must be granted to add images from the camera';
      try {
        let permissions:CameraPluginPermissions;
        if(type===CameraSource.Camera){
          permissions = {permissions:['camera']};

        }
        else {
          permissions = {permissions:['photos']};
        }
        await this.getPermission(permissions);
      } catch (err) {

        if (err === 'cordova_not_available') {
          this.app.showAlert('Error', err);
        }
        else {
          this.app.showAlert('Error', notGrantedErr);
        }
        throw err;
      }

      this.options.source = type;
      let imageData;
      if(type === CameraSource.Photos){
        /* this is a massive work around as the photo gallery is always getting permission denied no matter the permissions or requests*/
        imageData = await Camera.pickImages(this.options);
        if(imageData && imageData.photos) {
          imageData = imageData.photos[0];
          let res = await fetch(imageData.webPath);
          let blob = await res.blob();
          let  reader: FileReader = new FileReader();
          reader.onloadend = (fileLoadedEvent:any) =>{
            let imgSrcData = fileLoadedEvent.target.result;
            this.img = imgSrcData;
            this.value = imgSrcData;
          }
          reader.readAsDataURL(blob);
        }
      }
      else {
        this.options.saveToGallery = type == CameraSource.Camera;
        this.options.direction = CameraDirection.Rear;
        imageData = await Camera.getPhoto(this.options);
        this.img = imageData.dataUrl;
        this.value = imageData.dataUrl;
      }
    } catch (err) {
      console.log(err);
    }
  }

  private async getPermission(permission: CameraPluginPermissions) {

    let status = await Camera.checkPermissions();
    if ((permission.permissions[0] ==='camera' && status.camera!=='granted') || (permission.permissions[0] ==='photos' && status.photos!=='granted') ) {
      status = await Camera.requestPermissions(permission); // may throw exception

    }

    if (!status) {
      throw 'error';
    }

  }

  openGallery() {
    this.openCamera(CameraSource.Photos);
  }

  private isBase64(str: string) {
    str = str.substr(str.indexOf(',') + 1);

    try {
      return btoa(atob(str)) == str;
    } catch (err) {
      return false;
    }
  }

  clearImg() {
    this.img = this.value = '';
  }

  /**
   * Called if img src is broken
   * Image not found on local device so try and download from server
   * @param img
   */
  updateImg() {
    this.serverFolder = this.serverFolder || '';
    this.img = this.serverFolder  + this.value;

    this.fileTransfer.downloadSecureFiles(
      this.serverFolder + this.value,
      this.deviceFolder + this.value
    );
  }

  async editImg() {
    let mdl = await this.mdl.create({
      component: EditImageComponent,
      cssClass: 'edit-image-mdl',
      componentProps: { image: this.img },
    });

    mdl.present();

    const { data } = await mdl.onWillDismiss();
    if (data) {
      this.img = data;
      this.value = data;
    }
  }

  get value(): any {
    return this._value;
  }

  set value(v: any) {
    if (v !== this._value) {
      this._value = v;
      this.onChange(v);
    }
  }

  async writeValue(value: any) {
    this._value = value;
    if (!this.initialized && value) {
      if (this.isBase64(this.value)) {
        this.img = this.value;
      }
      else {
        this.img = await this.fileTransfer.getSecuredFullPath(
          this.value
        );
      }
      this.initialized = true;
    }
    this.onChange(value);
  }

  onChange = (_) => { };
  onTouched = () => { };
  registerOnChange(fn: (_: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }
}