Ionic CanvasPainting with p5js + Saving Images with Capacitor Filesystem API


Ionicにp5js
を入れて絵を簡単に描けるようにしました。
またCapacitorのFilesystem APIを使って描いた画像をスマホに保存できるようにしました。

main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { defineCustomElements } from '@ionic/pwa-elements/loader';

// Call the element loader after the platform has been bootstrapped
defineCustomElements(window);
if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.log(err));

home.page.html

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
    p5Paint
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">p5Paint</ion-title>
    </ion-toolbar>
  </ion-header>

  <div id="canvasContainer" class="canvas-container"></div>
 <ion-range min="2" max="30" color="primary" [(ngModel)]="lineWidth">
   <ion-icon size="small" slot="start" name="brush"></ion-icon>
 </ion-range>
 <input type="color" [(ngModel)]="strokeColor" name="head" value="0">
 <ion-button expand="full" (click)="addPhotoToGallery()">
 <ion-icon slot="start" name="download"></ion-icon>
 save image
</ion-button>
<ion-grid>
  <ion-row>
    <ion-col size="6"
    *ngFor="let photo of photoService.photos; index as position">
  <ion-img src="{{ photo.base64 ? photo.base64 : photo.webviewPath }}">
  </ion-img>
</ion-col>
  </ion-row>
</ion-grid>
</ion-content>

home.page.ts

import { Component, ElementRef, OnInit } from '@angular/core';
import { PhotoService } from '../services/photo.service';
import * as p5 from 'p5';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
  photos = this.photoService.photos;

 
  canvasX = 400;
  canvasY = 250;
  lineWidth = 3;
  strokeColor=0;
  constructor(private el: ElementRef,public photoService: PhotoService) { }
  ngOnInit() {

    const p5obj = new p5(p => {
      p.setup = () => { this.setup(p); };
      p.draw = () => { this.draw(p); };
    }, this.el.nativeElement);
    this.photoService.loadSaved();
  }
  setup(p) {
    const c = document.querySelector('#canvasContainer');
    p.createCanvas(this.canvasX, this.canvasY)
      .parent(c);
      p.background(220);
  }
  draw(p) {
    p.strokeWeight(this.lineWidth);
    p.stroke(this.strokeColor);
    if(p.mouseIsPressed){
      p.line(p.mouseX,p.mouseY,p.pmouseX,p.pmouseY);
    }
  }

  addPhotoToGallery() {
    this.photoService.addNewToGallery();
  }
}

photo.service.ts

import { Injectable } from '@angular/core';
import { Plugins,  Capacitor, FilesystemDirectory } from '@capacitor/core';

const { Filesystem, Storage } = Plugins;
@Injectable({
  providedIn: 'root'
})
export class PhotoService {
  public photos: Photo[] = [];
  private PHOTO_STORAGE: string = "photos";
  dataUrl:any;
    // other code
  
    constructor() { }
  
    public async addNewToGallery() {
      // canvas get
      const c:any=document.querySelector('#canvasContainer canvas');
      this.dataUrl=c.toDataURL();
  
   // Save the picture and add it to photo collection
   const savedImageFile = await this.savePicture(this.dataUrl);
   this.photos.unshift(savedImageFile);
  
   Storage.set({
    key: this.PHOTO_STORAGE,
    value: JSON.stringify(this.photos.map(p => {
            // Don't save the base64 representation of the photo data, 
            // since it's already saved on the Filesystem
            const photoCopy = { ...p };
            delete photoCopy.base64;
  
            return photoCopy;
            }))
  });
    }
    public async loadSaved() {
      // Retrieve cached photo array data
      const photos = await Storage.get({ key: this.PHOTO_STORAGE });
      this.photos = JSON.parse(photos.value) || [];
      console.log(this.photos);
      // more to come...
    }
  
    private async savePicture(cameraPhoto: any) { 
        // Convert photo to base64 format, required by Filesystem API to save
    const base64Data = await this.readAsBase64(cameraPhoto);
  
    // Write the file to the data directory
    const fileName = new Date().getTime() + '.jpeg';
    const savedFile = await Filesystem.writeFile({
      path: fileName,
      data: base64Data,
      directory: FilesystemDirectory.Data
    });
  
    // Use webPath to display the new image instead of base64 since it's
    // already loaded into memory
    return {
      filepath: fileName,
      webviewPath: this.dataUrl
    };
    }
  
    private async readAsBase64(cameraPhoto: any) {
      // Fetch the photo, read as a blob, then convert to base64 format
      const response = await fetch(cameraPhoto.webPath!);
      const blob = await response.blob();
    
      return await this.convertBlobToBase64(blob) as string;  
    }
    
    convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => {
      const reader = new FileReader;
      reader.onerror = reject;
      reader.onload = () => {
          resolve(reader.result);
      };
      reader.readAsDataURL(blob);
    });
  }

interface Photo {
  filepath: string;
  webviewPath: string;
  base64?: string;
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です