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: scatto, selezione dalla galleria, ritaglio in-app, anteprima live, compressione, EXIF e upload.

Come scattare e modificare foto in 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 foto esistenti tramite il picker di sistema. Per una schermata della fotocamera personalizzata con i tuoi controlli, usa @capgo/camera-preview. Questa guida copre cattura, editing, permessi, compressione, metadati, salvataggio e upload, con code per Capacitor 8.

Scegli lo strumento giusto

Necessità Plugin Perché
Scatta una foto con la UI della fotocamera di sistema @capacitor/camera takePhoto Nessuna UI custom da mantenere, funziona ovunque
Permetti all'utente di ritagliare o ruotare @capacitor/camera editable, editPhoto, editURIPhoto Editor integrato nell'app su iOS e Android
Seleziona una o più foto o video @capacitor/camera chooseFromGallery Usa Android Photo Picker e PHPicker, senza permessi ampi
Schermata camera 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 alla libreria paginato con thumbnail
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 state aggiunte in 8.1, e CameraErrorCode in 8.2. Le versioni precedenti getPhoto e pickImages sono deprecate.

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

Scattare foto o scegliere dalla galleria non richiede permessi. Il plugin avvia l'attività della fotocamera di sistema e usa l'Android Photo Picker sui dispositivi Android 11+ compatibili, altrimenti usa ACTION_OPEN_DOCUMENT altrove. Per avere il picker backportato sui dispositivi più vecchi con Google Play services, aggiungi questo dentro <application> in AndroidManifest.xml. Lo snippet usa il tools namespace, quindi aggiungi anche xmlns:tools="http://schemas.android.com/tools" alla root <manifest> element se non è ancora 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 saveToGallery: true richiede permessi di storage, e solo sulle 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 richiede che le app con necessità occasionali usino il picker; i build che dichiarano il permesso senza giustificazione vengono rifiutati.

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 ricevi è un MediaResult:

  • uri: URI del file nativo dell'immagine completa (non su web). Passalo a Filesystem, un uploader o editURIPhoto.
  • webPath: un URL che la WebView può caricare. Usalo per le anteprime invece del base64.
  • thumbnail: una thumbnail in 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 finisce 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 torna all'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 il base64 (ad esempio da un canvas o da una risposta del server), usa editPhoto. L'input deve essere base64 raw senza un data: prefisso:

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

Nessuno dei due metodi è disponibile sul web. Se ti servono 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 aspect ratio fissi come gli avatar. Un export del canvas ("canvas.toBlob(cb, 'image/jpeg', 0.85)") ti restituisce un Blob che puoi caricare 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' gives you pick-then-crop in one call. Set mediaType: MediaTypeSelection.All per accettare anche i video.

Su iOS, gli utenti possono concedere un accesso limitato. pickLimitedLibraryPhotos() permette di modificare la selezione e getLimitedLibraryPhotos() restituisce ciò che è visibile. Con il picker di sistema raramente servono entrambi, poiché gli elementi scelti sono sempre leggibili.

Se la tua app mostra la libreria dell'utente nella propria UI (es. un'app di stampa foto o 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
}

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

Creare una schermata della fotocamera personalizzata

L'interfaccia della fotocamera di sistema non può mostrare un frame per documenti, un contorno o il tuo brand. @capgo/camera-preview renderizza il feed della fotocamera nativa e ti permette di disegnare la 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, l'anteprima si trova 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 class on body che imposta background: transparent e nasconde il resto dell'app. Aggiungi NSCameraUsageDescription su iOS, mentre su Android il plugin richiede il CAMERA permesso di 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 dimensione intera: un JPEG da 4 MB diventa una stringa da 5.3 MB nella memoria JS e attraversa il bridge.

Prima dell'upload, ridimensiona 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 dagli iPhone, quindi la stessa chiamata converte HEIC in JPEG per i backend che non lo supportano. Il risultato viene salvato in una directory temporanea, quindi spostalo con @capacitor/filesystem se hai bisogno di conservarlo.

Metadati EXIF e privacy della posizione

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

  1. Rimuovi la posizione prima di caricare foto pubbliche (foto profilo, annunci marketplace). @capgo/capacitor-file-compressor rimuove gli EXIF su tutte le piattaforme durante la compressione.
  2. Se ti serve la posizione (report sul campo, ispezioni), leggila dagli EXIF prima della compressione e inviala come campo separato. Poi dichiaralo nell'etichetta privacy dell'App Store e nel modulo Play data safety. Consulta la nostra guida al privacy manifest per la parte iOS.

saveToGallery: true su takePhoto o editURIPhoto salva l'immagine nella galleria e imposta saved sul risultato. Per mantenere una foto privata all'interno della 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.

Carica la foto

Per 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 connessioni instabili, usa un uploader nativo che continui l'operazione 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. Esegui takePhoto o chooseFromGallery con editable: 'in-app', targetWidth: 1024, targetHeight: 1024.
  3. Ritaglia in un canvas quadrato se ti serve un rapporto 1:1 garantito.
  4. Comprimi con quality: 0.8 e i dati EXIF rimossi.
  5. Fai l'upload, poi mostra l'URL del server invece del file locale, così l'avatar sopravvive alle reinstallazioni.

Risoluzione problemi

L'app crasha su iOS quando si apre la fotocamera: manca una chiave usage description in Info.plist.

La foto risulta ruotata su Android: imposta correctOrientation: true (il default) 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 immagini, compressione, crop e endpoint di upload sono gestiti nel tuo web code. Con Live Updates di Capgo puoi modificarli per ogni utente senza attendere la revisione, mentre i cambiamenti nativi come i nuovi plugin richiedono una build regolare. Se ti servono build iOS senza un Mac, Build con Capgo gestisce la firma e l'upload con Xcode 26, richiesto da App Store Connect da aprile 2026.

Live update per le app Capacitor

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

Le ultime dal nostro blog

Capgo ti offre i migliori insight per creare un'app mobile davvero professionale.