Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web Camera - use file input #1856

Merged
merged 16 commits into from
Jul 7, 2020
8 changes: 7 additions & 1 deletion core/src/core-plugin-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ export interface CameraOptions {
* iOS only: The presentation style of the Camera. Defaults to fullscreen.
*/
presentationStyle?: 'fullscreen' | 'popover';

/**
* Web only: whether to use the PWA Element experience or file input. The default
* is to use file input experience. Learn more about PWA Elements: https://capacitor.ionicframework.com/docs/pwa-elements/
mlynch marked this conversation as resolved.
Show resolved Hide resolved
*/
webUsePWAElements?: boolean;
}

export enum CameraSource {
Expand Down Expand Up @@ -347,7 +353,7 @@ export interface CameraPhoto {
*/
exif?: any;
/**
* The format of the image. Currently, only "jpeg" is supported.
* The format of the image, ex: jpeg, png, gif.
imhoffd marked this conversation as resolved.
Show resolved Hide resolved
*/
format: string;
}
Expand Down
117 changes: 95 additions & 22 deletions core/src/web/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import {
CameraPlugin,
CameraPhoto,
CameraOptions,
CameraResultType
CameraResultType,
CameraDirection,
CameraSource
} from '../core-plugin-definitions';

//import '@ionic/pwa-elements';

export class CameraPluginWeb extends WebPlugin implements CameraPlugin {
constructor() {
super({
Expand All @@ -18,36 +18,109 @@ export class CameraPluginWeb extends WebPlugin implements CameraPlugin {
}

async getPhoto(options: CameraOptions): Promise<CameraPhoto> {
options;

return new Promise<CameraPhoto>(async (resolve, reject) => {
const cameraModal: any = document.createElement('pwa-camera-modal');
document.body.appendChild(cameraModal);
await cameraModal.componentOnReady();
cameraModal.addEventListener('onPhoto', async (e: any) => {
const photo = e.detail;

if (photo === null) {
reject('User cancelled photos app');
} else if (photo instanceof Error) {
reject(photo.message);
if (options.webUsePWAElements === true && options.source === CameraSource.Camera) {
const cameraModal: any = document.createElement('pwa-camera-modal');
imhoffd marked this conversation as resolved.
Show resolved Hide resolved
document.body.appendChild(cameraModal);
try {
await cameraModal.componentOnReady();
cameraModal.addEventListener('onPhoto', async (e: any) => {
const photo = e.detail;

if (photo === null) {
reject('User cancelled photos app');
} else if (photo instanceof Error) {
reject(photo.message);
} else {
resolve(await this._getCameraPhoto(photo, options));
}

cameraModal.dismiss();
document.body.removeChild(cameraModal);
});

cameraModal.present();
} catch (e) {
this.fileInputExperience(options, resolve, reject);
}
} else {
this.fileInputExperience(options, resolve, reject);
}
});
}

private fileInputExperience(options: CameraOptions, resolve: any, reject: any) {
let input = document.querySelector('#_capacitor-camera-input') as HTMLInputElement;

const cleanup = () => {
input.parentNode && input.parentNode.removeChild(input);
};

if (!input) {
input = document.createElement('input') as HTMLInputElement;
input.id = '_capacitor-camera-input';
input.type = 'file';
document.body.appendChild(input);
}

input.accept = 'image/*';
(input as any).capture = true;

if (options.source === CameraSource.Photos || options.source === CameraSource.Prompt) {
input.removeAttribute('capture');
} else if (options.direction === CameraDirection.Front) {
(input as any).capture = 'user';
} else if (options.direction === CameraDirection.Rear) {
(input as any).capture = 'environment';
}

input.addEventListener('change', (_e: any) => {
console.log('CHANGE', _e);
mlynch marked this conversation as resolved.
Show resolved Hide resolved
const file = input.files[0];
let format = 'jpeg';
if (file.type === 'image/png') {
format = 'png';
}
if (file.type === 'image/gif') {
format = 'gif';
}

const reader = new FileReader();
reader.addEventListener('load', () => {
if (options.resultType === CameraResultType.DataUrl) {
resolve({
dataUrl: reader.result,
format
} as CameraPhoto);
} else if (options.resultType === CameraResultType.Base64) {
const b64 = (reader.result as string).split(',')[1];
resolve({
base64String: b64,
format
} as CameraPhoto);
} else {
resolve(await this._getCameraPhoto(photo, options));
reject('Unsupported result type for this platform');
}

cameraModal.dismiss();
document.body.removeChild(cameraModal);
cleanup();
});

cameraModal.present();
if (options.resultType === CameraResultType.DataUrl || options.resultType === CameraResultType.Base64) {
reader.readAsDataURL(file);
} else {
reject('Camera result type not supported on this platform. Use DataUrl or Base64');
mlynch marked this conversation as resolved.
Show resolved Hide resolved
cleanup();
}
});

input.click();
}

private _getCameraPhoto(photo: Blob, options: CameraOptions) {
return new Promise<CameraPhoto>((resolve, reject) => {
var reader = new FileReader();
var format = photo.type.split('/')[1]
if (options.resultType == CameraResultType.Uri) {
var format = photo.type.split('/')[1];
if (options.resultType === CameraResultType.Uri) {
resolve({
webPath: URL.createObjectURL(photo),
format: format
Expand All @@ -56,7 +129,7 @@ export class CameraPluginWeb extends WebPlugin implements CameraPlugin {
reader.readAsDataURL(photo);
reader.onloadend = () => {
const r = reader.result as string;
if (options.resultType == CameraResultType.DataUrl) {
if (options.resultType === CameraResultType.DataUrl) {
resolve({
dataUrl: r,
format: format
Expand Down
3 changes: 3 additions & 0 deletions example/src/pages/camera/camera.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
<ion-content padding>
<img [src]="image" />
<button (click)="getPhoto()" ion-button color="primary">Prompt</button>
<button (click)="getPhotoPWAElements()" ion-button color="primary">Prompt (PWA Elements)</button>
<button (click)="getPhotoFront()" ion-button color="primary">From Camera (front facing)</button>
<button (click)="takePicture()" ion-button color="primary">From Camera</button>
<button (click)="getFromPhotos()" ion-button color="primary">From Photos</button>
<button (click)="getBase64()" ion-button color="primary">Prompt (b64)</button>
<button (click)="takePictureScaled()" ion-button color="primary">Scaled</button>
<button (click)="takePictureCorrected()" ion-button color="primary">Orientation Corrected</button>
<button (click)="takePictureFile()" ion-button color="primary">File URI</button>
Expand Down
33 changes: 33 additions & 0 deletions example/src/pages/camera/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import {
Plugins,
CameraDirection,
CameraResultType,
CameraSource,
FilesystemDirectory
Expand Down Expand Up @@ -36,10 +37,42 @@ export class CameraPage {
}

async getPhoto() {
const image = await Plugins.Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.DataUrl
})
console.log('Got image back', image.path, image.webPath, image.format, image.exif);
this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image && (image.dataUrl));
}

async getPhotoFront() {
const image = await Plugins.Camera.getPhoto({
quality: 90,
allowEditing: true,
direction: CameraDirection.Front,
resultType: CameraResultType.DataUrl
})
console.log('Got image back', image.path, image.webPath, image.format, image.exif);
this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image && (image.dataUrl));
}

async getBase64() {
const image = await Plugins.Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Base64
})
console.log('Got image back', image.base64String, image.format);
this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image && (`data:${image.format};base64,${image.base64String}`));
}

async getPhotoPWAElements() {
const image = await Plugins.Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.DataUrl,
webUsePWAElements: true
})
console.log('Got image back', image.path, image.webPath, image.format, image.exif);
this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image && (image.dataUrl));
Expand Down