Aller au contenu principal

Comment prendre et modifier des photos dans une application Capacitor

Prendre et retoucher des photos dans une app Capacitor avec le plugin Camera 8 API : capture, sélection depuis la galerie, recadrage, aperçu en direct, compression, EXIF et uploads.

Prendre et modifier une photo avec l’appareil photo dans une application Capacitor

Pour prendre et retoucher des photos dans une app Capacitor, utilisez @capacitor/camera 8.1 ou version ultérieure : Camera.takePhoto() ouvre l’appareil photo, editable: 'in-app' ajoute une étape de recadrage et de rotation, et chooseFromGallery() sélectionne des photos existantes via le sélecteur système. Pour un écran d’appareil photo personnalisé avec vos propres contrôles, utilisez @capgo/camera-preview. Ce guide aborde la capture, l’édition, les autorisations, la compression, les métadonnées, l’enregistrement et l’envoi, avec code pour Capacitor 8.

Choisissez le bon outil

Besoin Plugin Pourquoi
Prendre une photo avec l’interface caméra du système @capacitor/camera takePhoto Aucune interface personnalisée à maintenir, fonctionne partout
Laisser l’utilisateur recadrer ou pivoter @capacitor/camera editable, editPhoto, editURIPhoto Éditeur intégré dans l’app sur iOS et Android
Sélectionner une ou plusieurs photos ou vidéos @capacitor/camera chooseFromGallery Utilise le Photo Picker d’Android et PHPicker, sans permission large
Custom camera screen (overlays, ID card frame, face guide) @capgo/camera-preview Aperçu natif que vous contrôlez depuis HTML et JS
Parcourez toute la bibliothèque dans votre propre grille @capgo/capacitor-photo-library Accès paginé à la bibliothèque avec miniatures
Réduire, redimensionner ou convertir HEIC @capgo/capacitor-file-compressor Encodeur natif, supprime l’EXIF
Envoyer de grandes photos de manière fiable @capgo/capacitor-uploader Upload natif en arrière-plan avec progression

Installez et configurez le plugin Camera

bun add @capacitor/camera
bunx cap sync

Assurez-vous d’avoir la version 8.1 ou plus récente. Les méthodes de ce guide (takePhoto, chooseFromGallery, editPhoto, editURIPhoto) ont été ajoutées en 8.1, et CameraErrorCode en 8.2. Les anciennes getPhoto et pickImages sont dépréciées.

iOS

Ajoutez ces clés à ios/App/App/Info.plist. Apple rejette les builds avec des chaînes vagues, donc expliquez la raison réelle :

<key>NSCameraUsageDescription</key>
<string>Take a photo for your profile or attach it to a report.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Choose existing photos to attach.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save photos you take to your library.</string>

Android

La capture et la sélection depuis la galerie ne nécessitent aucune autorisation. Le plugin lance l’activité caméra du système et utilise le sélecteur de photos Android sur les appareils Android 11+ qui le prennent en charge, avec repli sur ACTION_OPEN_DOCUMENT ailleurs. Pour obtenir le sélecteur rétroporté sur les anciens appareils disposant des services Google Play, ajoutez ceci dans <application> dans AndroidManifest.xml. L’extrait utilise le tools namespace, donc ajoutez également xmlns:tools="http://schemas.android.com/tools" à la racine <manifest> élément s'il n'existe pas encore (un nouveau projet Capacitor déclare uniquement xmlns:android):

<service android:name="com.google.android.gms.metadata.ModuleDependencies"
    android:enabled="false"
    android:exported="false"
    tools:ignore="MissingClass">
    <intent-filter>
        <action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
    </intent-filter>
    <meta-data android:name="photopicker_activity:0:required" android:value="" />
</service>

Seules les saveToGallery: true needs storage permissions, and only on old Android versions:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />

Évitez d'ajouter READ_MEDIA_IMAGES sauf si le cœur de votre application est une galerie ou un éditeur. La politique de Google Play sur les permissions photo et vidéo impose aux applications ayant des besoins ponctuels en photos d'utiliser le sélecteur ; les builds qui déclarent la permission sans justification sont rejetés.

Prendre une photo

import { Camera, CameraDirection, CameraErrorCode, EncodingType } from '@capacitor/camera';

export async function takePicture() {
  try {
    const photo = await Camera.takePhoto({
      quality: 85,
      targetWidth: 2048,
      targetHeight: 2048,
      correctOrientation: true,
      encodingType: EncodingType.JPEG,
      cameraDirection: CameraDirection.Rear, // iOS and web
      includeMetadata: true,
    });

    // webPath works directly in <img src>
    document.querySelector<HTMLImageElement>('#preview')!.src = photo.webPath!;
    return photo;
  } catch (e: any) {
    if (e.code === CameraErrorCode.TakePhotoCancelled) return null;
    if (e.code === CameraErrorCode.CameraPermissionDenied) {
      // show a screen that explains how to enable the camera in Settings
      return null;
    }
    throw e;
  }
}

Ce que vous récupérez est un MediaResult:

  • uri: URI de fichier natif de l'image complète (pas sur le web). Transmettez-l’à Filesystem, à un uploader, ou editURIPhoto.
  • webPath: une URL que la WebView peut charger. Utilisez-la pour les aperçus plutôt que le base64.
  • thumbnail: une vignette base64 en natif, l'image complète sur le web.
  • metadata: taille, format, résolution, date de création et EXIF lorsque includeMetadata est à true.
  • saved: si saveToGallery a réussi.

targetWidth et targetHeight doivent être définis ensemble. C'est la méthode la plus économique pour éviter d'uploader des photos de 12 MP quand vous n'avez besoin que d'une image de 2048 pixels.

Modifier la photo : recadrer et pivoter

Modifier juste après la capture

Définissez editable et l'utilisateur se retrouve dans un éditeur avant que la promesse ne soit résolue :

const photo = await Camera.takePhoto({
  quality: 85,
  editable: 'in-app', // 'in-app' | 'external' | 'no'
});

'external' transmet l'image à un autre éditeur installé sur Android (par exemple Google Photos) et bascule sur l'éditeur intégré. iOS ne propose pas d'édition externe et utilise toujours 'in-app'.

Modifier une photo existante

Pour éditer une photo sélectionnée ou que vous avez stockée précédemment, passez son URI :

const edited = await Camera.editURIPhoto({
  uri: photo.uri!,
  saveToGallery: false,
  includeMetadata: false,
});

Si vous ne disposez que de base64 (par exemple depuis un canvas ou une réponse serveur), utilisez editPhoto. L'entrée doit être du base64 brut sans data: préfixe :

const { outputImage } = await Camera.editPhoto({ inputImage: base64 });
img.src = `data:image/jpeg;base64,${outputImage}`;

Aucune des deux méthodes n'est disponible sur le web. Si vous avez besoin de filtres, de stickers, de texte ou de dessin libre, l'éditeur intégré ne suffit pas. Affichez l'image dans un <canvas> et appliquer les modifications, ou utiliser un outil de recadrage JavaScript comme Cropper.js pour des ratios fixes (avatars, par exemple). L’export via canvas («canvas.toBlob(cb, 'image/jpeg', 0.85)) vous fournit un Blob que vous pouvez envoyer directement.

import { Camera, MediaTypeSelection } from '@capacitor/camera';

const { results } = await Camera.chooseFromGallery({
  mediaType: MediaTypeSelection.Photo,
  allowMultipleSelection: true,
  limit: 5,            // Android 13+ and iOS
  quality: 85,
  targetWidth: 2048,
  targetHeight: 2048,
});

const urls = results.map((r) => r.webPath);

Pour une seule photo, allowMultipleSelection: false , ce qui editable: 'in-app' gives you pick-then-crop in one call. Set mediaType: MediaTypeSelection.All pour accepter aussi les vidéos.

On iOS, users can grant limited access. pickLimitedLibraryPhotos() leur permet de modifier la sélection et getLimitedLibraryPhotos() retourne ce que vous voyez. Avec le sélecteur système, vous avez rarement besoin de l’un ou l’autre, car les éléments sélectionnés sont toujours lisibles.

If your app shows the user’s library inside your own UI (a photo printing app, a backup app), use @capgo/capacitor-photo-library:

import { PhotoLibrary } from '@capgo/capacitor-photo-library';

const { state } = await PhotoLibrary.requestAuthorization();
if (state === 'authorized' || state === 'limited') {
  const { assets, hasMore } = await PhotoLibrary.getLibrary({
    limit: 60,
    offset: 0,
    thumbnailWidth: 256,
    thumbnailHeight: 256,
  });
  // assets[i].thumbnail?.webPath for the grid, getPhotoUrl({ id }) for full size
}

Le même plugin pickMedia() ouvre le sélecteur système et copie la sélection dans le cache, si vous souhaitez un seul plugin pour les deux flux. Rappelez-vous la politique Google Play mentionnée ci-dessus avant de demander l’accès complet à la bibliothèque sur Android.

Créer un écran caméra personnalisé

L’interface caméra système ne peut pas afficher un cadre de carte d’identité, un contour de document ou votre marque. @capgo/camera-preview affiche le flux caméra natif et vous permet de dessiner l’interface en HTML :

bun add @capgo/camera-preview
bunx cap sync
import { CameraPreview } from '@capgo/camera-preview';

await CameraPreview.start({
  position: 'rear',
  toBack: true,          // preview behind the WebView
  aspectRatio: '4:3',
  storeToFile: true,     // capture() returns a file path instead of base64
  disableAudio: true,
});

await CameraPreview.setFlashMode({ flashMode: 'auto' });
await CameraPreview.setZoom({ level: 2 });

const { value: filePath, exif } = await CameraPreview.capture({ quality: 85 });

await CameraPreview.stop();

Avec toBack: true, l’aperçu se situe derrière le WebView, donc la page et tous les éléments parents doivent avoir un fond transparent pendant que la caméra est active. Un schéma courant consiste à utiliser un camera-active classe sur body qui définit background: transparent et masque le reste de l'app. Ajoutez NSCameraUsageDescription sur iOS, et sur Android le plugin demande la CAMERA permission runtime via requestPermissions(). Appelez toujours stop() lors de la fermeture de l'écran pour libérer la caméra.

Affichez, compressez et convertissez

Utiliser webPath (ou Capacitor.convertFileSrc(path) pour les chemins issus d'autres plugins) afin d'afficher des images. Évitez le base64 dans <img> tags pour les photos plein format : un JPEG de 4 Mo devient une chaîne de 5,3 Mo en mémoire JS et traverse le bridge.

Avant l'upload, réduisez la taille de l'image :

import { FileCompressor } from '@capgo/capacitor-file-compressor';

const { path } = await FileCompressor.compressImage({
  path: photo.uri!,
  width: 1600,          // keeps aspect ratio
  quality: 0.75,
  mimeType: 'image/jpeg',
});

Le compresseur accepte les fichiers HEIC des iPhones : le même appel convertit donc le HEIC en JPEG pour les backends qui ne savent pas le lire. Il écrit le résultat dans un répertoire temporaire, déplacez-l’avec @capacitor/filesystem si vous devez le conserver.

EXIF metadata and location privacy

Avec includeMetadata: true, metadata.exif contient le bloc EXIF de la photo. Les photos de la galerie peuvent inclure des coordonnées GPS. Deux règles pour éviter les ennuis :

  1. Supprimez la localisation avant d'uploader des photos qui deviennent publiques (photos de profil, annonces de marketplace). @capgo/capacitor-file-compressor supprime les EXIF sur toutes les plateformes lors de la compression.
  2. Si vous avez besoin de la localisation (rapports de terrain, inspections), lisez-la depuis les EXIF avant de compresser et envoyez-la dans un champ séparé. Déclarez-l’ensuite dans l’étiquette de confidentialité de l’App Store et le formulaire de sécurité des données de Play. Consultez notre guide du manifeste de confidentialité pour la partie iOS.

saveToGallery: true sur takePhoto ou editURIPhoto enregistre l'image dans la pellicule et définit saved sur le résultat. Pour que la photo reste privée dans votre application, copiez-la dans le stockage de l'app :

import { Filesystem, Directory } from '@capacitor/filesystem';

await Filesystem.copy({
  from: photo.uri!,
  to: `photos/${Date.now()}.jpg`,
  toDirectory: Directory.Data,
});

Les fichiers du répertoire cache peuvent être supprimés par l'OS à tout moment, n'y stockez donc pas de références persistantes.

Envoyer la photo

Pour les petites photos, fetch par FormData est suffisant :

const blob = await fetch(photo.webPath!).then((r) => r.blob());
const form = new FormData();
form.append('file', blob, 'photo.jpg');
await fetch('https://api.example.com/photos', { method: 'POST', body: form });

Pour les fichiers volumineux ou les réseaux instables, utilisez un uploader natif qui continue même quand l'app passe en arrière-plan :

import { Uploader } from '@capgo/capacitor-uploader';

const { id } = await Uploader.startUpload({
  filePath: path,
  serverUrl: 'https://api.example.com/photos',
  method: 'POST',
  mimeType: 'image/jpeg',
});

await Uploader.addListener('events', (event) => {
  console.log(event.name, event.payload);
});

Exemple : flux de photo de profil

  1. Afficher un action sheet avec « Prendre une photo » et « Choisir dans la bibliothèque ».
  2. Appelez takePhoto ou chooseFromGallery par editable: 'in-app', targetWidth: 1024, targetHeight: 1024.
  3. Recadrez en carré dans un canvas si vous avez besoin d'un ratio 1:1 garanti.
  4. Compressez avec quality: 0.8 et les données EXIF supprimées.
  5. Faites l'upload, puis affichez l'URL du serveur plutôt que le fichier local, pour que l'avatar persiste après une réinstallation.

Troubleshooting

L'application plante sur iOS à l'ouverture de la caméra: une clé de description d'utilisation est manquante dans Info.plist.

La photo est tournée sur Android: définissez correctOrientation: true (par défaut) et évitez de lire les pixels bruts sans appliquer l'orientation EXIF.

L'application redémarre après la prise d'une photo sur les appareils Android à faible mémoire: Android peut tuer votre activité pendant que la caméra est ouverte. Écoutez appRestoredResult depuis @capacitor/app pour récupérer le résultat après le redémarrage.

webPath ne charge pas: vous avez sauvegardé le webPath et l'avez réutilisé après un redémarrage. Persistez le uri ou un chemin Filesystem et convertissez-l’à nouveau avec Capacitor.convertFileSrc.

L'aperçu de la caméra affiche un écran noir: un élément au-dessus de la WebView a encore une couleur d'arrière-plan, ou start() a été appelé avant que la permission ne soit accordée.

Play Console rejette le build pour les permissions photo: supprimer READ_MEDIA_IMAGES et READ_MEDIA_VIDEO sauf si vous avez déclaré que votre application a besoin d’un accès étendu.

Déployer des correctifs sans passer par les stores

Les tailles d’image, niveaux de compression, ratios de recadrage et endpoints d’upload sont tous dans votre web code. Avec les live updates Capgo vous pouvez les modifier pour tous les utilisateurs sans attendre de validation, tandis que les modifications natives comme de nouveaux plugins passent par un build classique. Si vous avez besoin de builds iOS sans Mac, Build Capgo gère la signature et les uploads avec Xcode 26, exigé par App Store Connect depuis avril 2026.

Live updates pour les apps Capacitor

Lorsqu'un bug de la couche web est en ligne, déployez le correctif via Capgo au lieu d'attendre des jours l'approbation des stores. Les utilisateurs reçoivent la mise à jour en arrière-plan, tandis que les changements natifs suivent le circuit de validation habituel.

Les derniers articles du blog

Capgo vous offre les meilleures informations pour créer une application mobile vraiment professionnelle.