Salta al contenuto principale

Come scattare e modificare foto in un'app Capacitor

Scatta e modifica foto in un'app Capacitor con Camera 8 API: cattura, selezione dalla galleria, crop in-app, live preview, compressione, EXIF e upload.

Scattare e modificare una foto con la fotocamera all'interno di un'app Capacitor

Per scattare e modificare foto in un'app Capacitor, usa @capacitor/camera 8.1 o versioni successive: Camera.takePhoto() apre la fotocamera, editable: 'in-app' aggiunge un passaggio di ritaglio e rotazione, e chooseFromGallery() seleziona le foto esistenti tramite il picker di sistema. Per una schermata fotocamera personalizzata con i tuoi controlli, usa @capgo/camera-preview. Questa guida copre acquisizione, editing, permessi, compressione, metadati, salvataggio e upload, con code per Capacitor 8.

Scegli lo strumento giusto

Ti servono Plugin Perché
Scatta una foto con l'interfaccia fotocamera di sistema @capacitor/camera takePhoto Nessuna UI personalizzata da mantenere, funziona ovunque
Consenti all'utente di ritagliare o ruotare @capacitor/camera editable, editPhoto, editURIPhoto Editor in-app integrato su iOS e Android
Seleziona una o più foto o video @capacitor/camera chooseFromGallery Usa Android Photo Picker e PHPicker, senza permessi ampi
Schermata fotocamera personalizzata (overlay, cornice documento, guida volto) @capgo/camera-preview Anteprima nativa gestibile via HTML e JS
Sfoglia l'intera libreria nella tua griglia @capgo/capacitor-photo-library Accesso paginato alla libreria con miniature
Riduci, ridimensiona o converti HEIC @capgo/capacitor-file-compressor Encoder nativo, rimuove i metadati EXIF
Upload affidabile di foto pesanti @capgo/capacitor-uploader Upload nativo in background con progresso

Installa e configura il plugin Camera

bun add @capacitor/camera
bunx cap sync

Assicurati di avere la versione 8.1 o superiore. I metodi in questa guida (takePhoto, chooseFromGallery, editPhoto, editURIPhoto) sono stati aggiunti nella 8.1, e CameraErrorCode nella 8.2. I vecchi getPhoto e pickImages sono deprecati.

iOS

Aggiungi queste chiavi a ios/App/App/Info.plist. Apple rifiuta i build con stringhe vaghe, quindi spiega il motivo reale:

<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 cattura e la selezione dalla galleria non richiedono permessi. Il plugin avvia l'attività della fotocamera di sistema e utilizza Android Photo Picker sui dispositivi Android 11+ che lo supportano, con fallback su ACTION_OPEN_DOCUMENT altrimenti. Per ottenere il picker backportato su dispositivi più vecchi con Google Play services, aggiungi questo dentro <application> nella AndroidManifest.xml. Lo snippet usa lo tools namespace, quindi aggiungi anche xmlns:tools="http://schemas.android.com/tools" all'elemento radice <manifest> se non è già presente (un nuovo progetto Capacitor dichiara solo 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>

Solo le saveToGallery: true richiede permessi di archiviazione, e solo su vecchie versioni di Android:

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

Evita di aggiungere READ_MEDIA_IMAGES a meno che lo scopo principale della tua app non sia una galleria o un editor. La policy di Google Play sui permessi per foto e video impone alle app che hanno bisogno di foto solo occasionalmente di usare il picker, e le build che dichiarano il permesso senza giustificazione vengono rifiutate.

Scatta una foto

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;
  }
}

Quello che ottieni è un MediaResult:

  • uri: URI file nativo dell'immagine completa (non su web). Passalo a Filesystem, a un uploader o a editURIPhoto.
  • webPath: un URL che la WebView può caricare. Usalo per le anteprime invece di base64.
  • thumbnail: una miniatura base64 su nativo, l'immagine completa su web.
  • metadata: dimensione, formato, risoluzione, data di creazione ed EXIF quando includeMetadata è true.
  • saved: se saveToGallery ha avuto successo.

targetWidth e targetHeight devono essere impostati insieme. Sono il modo più economico per evitare di fare l'upload di foto da 12 MP quando serve solo un'immagine da 2048 pixel.

Modifica la foto: ritaglia e ruota

Modifica subito dopo lo scatto

Imposta editable e l'utente si ritrova in un editor prima che la promise si risolva:

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

'external' passa l'immagine a un altro editor installato su Android (ad esempio Google Photos) e, in alternativa, usa l'editor in-app. iOS non ha editing esterno e usa sempre 'in-app'.

Modifica una foto esistente

Per modificare una foto selezionata o una salvata in precedenza, passa il suo URI:

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

Se hai solo base64 (ad esempio da un canvas o da una risposta del server), usa editPhoto. L'input deve essere base64 grezzo senza data: prefisso:

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

Nessuno dei due metodi è disponibile sul web. Se hai bisogno di filtri, sticker, testo o disegno libero, l'editor integrato non basta. Renderizza l'immagine in un <canvas> e applica le modifiche lì, oppure usa un cropper JavaScript come Cropper.js per rapporti di aspetto fissi come gli avatar. Un export canvas (canvas.toBlob(cb, 'image/jpeg', 0.85)) ti fornisce un Blob che puoi uploadare direttamente.

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);

Per una singola foto, allowMultipleSelection: false più editable: 'in-app' ti dà selezione e ritaglio in un'unica chiamata. Imposta mediaType: MediaTypeSelection.All per accettare anche i video.

Su iOS, gli utenti possono concedere un accesso limitato. pickLimitedLibraryPhotos() permette loro di modificare la selezione e getLimitedLibraryPhotos() restituisce ciò che vedi. Con il picker di sistema raramente ne hai bisogno, perché gli elementi selezionati sono sempre leggibili.

Se la tua app mostra la libreria dell'utente all'interno della tua UI (un'app di stampa foto, un'app di backup), usa @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
}

Lo stesso plugin pickMedia() apre il picker di sistema e copia la selezione nella cache, se vuoi un plugin unico per entrambi i flussi. Ricorda la policy di Google Play sopra prima di richiedere l'accesso completo alla libreria su Android.

Creare una schermata fotocamera personalizzata

L'UI della fotocamera di sistema non può mostrare un riquadro per carte d'identità, un contorno per documenti o il tuo brand. @capgo/camera-preview rende il feed nativo della fotocamera e ti permette di disegnare l'UI in 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();

Con toBack: true, la preview è dietro la WebView, quindi la pagina e ogni elemento genitore devono avere uno sfondo trasparente mentre la fotocamera è attiva. Un pattern comune è un camera-active classe su body che imposta background: transparent e nasconde il resto dell'app. Aggiungi NSCameraUsageDescription su iOS, mentre su Android il plugin richiede il CAMERA permesso runtime tramite requestPermissions(). Chiama sempre stop() quando la schermata si chiude per rilasciare la fotocamera.

Visualizza, comprimi e converti

Usa webPath (o Capacitor.convertFileSrc(path) per i percorsi di altri plugin) per mostrare le immagini. Evita il base64 in <img> tag per foto a piena dimensione: un JPEG da 4 MB diventa una stringa da 5,3 MB nella memoria JS e attraversa il bridge.

Prima di caricare, riduci l'immagine:

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',
});

Il compressore accetta input HEIC da iPhone, quindi la stessa chiamata converte HEIC in JPEG per i backend che non leggono HEIC. Scrive il risultato in una directory temporanea, quindi spostalo con @capacitor/filesystem se ti serve conservarla.

Metadati EXIF e privacy della posizione

Con includeMetadata: true, metadata.exif contiene il blocco EXIF della foto. Le foto dalla galleria possono includere coordinate GPS. Due regole per evitare problemi:

  1. Rimuovi la posizione prima di caricare foto che diventano pubbliche (foto profilo, annunci di marketplace). @capgo/capacitor-file-compressor rimuove i metadati EXIF su tutte le piattaforme durante la compressione.
  2. Se ti serve la posizione (rapporti di campo, ispezioni), leggila dai metadati EXIF prima di comprimere e inviala come campo separato. Poi dichiarala nell'etichetta privacy dell'App Store e nel modulo Data safety di Play. Consulta la nostra guida al privacy manifest per la parte iOS.

saveToGallery: true su takePhoto oppure editURIPhoto scrive l'immagine nella galleria e imposta saved sul risultato. Per tenere una foto privata nella tua app, copiala nello storage dell'app:

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

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

I file nella directory cache possono essere rimossi dal sistema operativo in qualsiasi momento, quindi non conservare riferimenti a lungo termine a questi file.

Fai l'upload della foto

Per le foto piccole, fetch con FormData è sufficiente:

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 });

Per file grandi o reti instabili, usa un uploader nativo che continua anche quando l'app è in background:

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);
});

Esempio: flusso foto profilo

  1. Mostra un action sheet con “Scatta foto” e “Scegli dalla libreria”.
  2. Chiama takePhoto oppure chooseFromGallery con editable: 'in-app', targetWidth: 1024, targetHeight: 1024.
  3. Ritaglia in un quadrato su un canvas se hai bisogno di un rapporto 1:1 garantito.
  4. Comprimi con quality: 0.8 e EXIF rimosso.
  5. Carica, poi mostra l'URL del server, non il file locale, in modo che l'avatar sopravviva ai reinstall.

Risoluzione problemi

L'app va in crash su iOS quando si apre la fotocamera: manca una chiave di descrizione utilizzo in Info.plist.

La foto è ruotata su Android: imposta correctOrientation: true (il predefinito) ed evita di leggere i pixel grezzi senza applicare l'orientamento EXIF.

L'app si riavvia dopo aver scattato una foto su dispositivi Android con poca memoria: Android potrebbe terminare la tua activity mentre la fotocamera è aperta. Ascolta appRestoredResult da @capacitor/app per recuperare il risultato dopo il riavvio.

webPath non carica: hai salvato il webPath e lo hai riutilizzato dopo un riavvio. Mantieni il uri o un percorso Filesystem e convertilo di nuovo con Capacitor.convertFileSrc.

L'anteprima della fotocamera mostra uno schermo nero: un elemento sopra la WebView ha ancora un colore di sfondo, oppure start() è stata chiamata prima che il permesso venisse concesso.

Play Console rifiuta la build per i permessi foto: rimuovi READ_MEDIA_IMAGES e READ_MEDIA_VIDEO a meno che tu non abbia dichiarato che la tua app necessita di un accesso esteso.

Rilascia fix senza passare dallo store

Dimensioni delle immagini, livelli di compressione, rapporti di ritaglio ed endpoint di upload risiedono nel tuo code web. Con Live updates di Capgo puoi modificarli per ogni utente senza aspettare la revisione, mentre le modifiche native come i nuovi plugin passano da una build normale. Se ti servono build iOS senza un Mac, Build Capgo gestisce firma e upload con Xcode 26, che App Store Connect richiede da aprile 2026.

Live updates per le app Capacitor

Quando un bug nel web-layer è live, rilascia la fix tramite Capgo invece di aspettare giorni l'approvazione degli store. Gli utenti ricevono l'update in background, mentre le modifiche native restano nel normale processo di review.

Ultimi articoli dal blog

Capgo ti offre gli insight migliori che ti servono per creare un'app mobile davvero professionale.