跳过主要内容

在Capacitor应用中如何拍摄和编辑照片

在 Capacitor 应用中拍摄和编辑照片,使用摄像头 8 API: 拍摄、图库选择、内置裁剪、实时预览、压缩、EXIF 和上传。

文章来源

马丁·多纳迪尤

作者

瓦莱里亚

审阅者

乔丹

编辑

在 Capacitor 应用中拍摄和编辑照片

要在 Capacitor 应用中拍摄和编辑照片,请使用 @capacitor/camera 8.1 或更高版本: Camera.takePhoto() 打开摄像头, editable: 'in-app' 添加裁剪和旋转步骤,并 chooseFromGallery() 通过系统选择器选择现有的照片。要使用自定义相机屏幕和自己的控件,请 @capgo/camera-preview。 本指南涵盖了捕获、编辑、权限、压缩、元数据、保存和上传,包括code为Capacitor 8。

选择合适的工具

需要 插件 为什么
使用系统相机 UI 拍照 @capacitor/camera takePhoto 无需维护自定义 UI,适用于所有平台
让用户裁剪或旋转 @capacitor/camera editable, editPhoto, editURIPhoto iOS 和 Android 上的内置编辑器
选择一个或多个照片或视频 @capacitor/camera chooseFromGallery 使用 Android Photo Picker 和 PHPicker,未获取广泛权限
自定义相机屏幕(覆盖层,身份证框,面部指引) @capgo/camera-preview 您可以从 HTML 和 JS 控制原生预览
在自己的网格中浏览整个图库 @capgo/capacitor-photo-library 带有缩略图的分页图库访问
压缩、重置或转换 HEIC @capgo/capacitor-file-compressor 原生编码器,去除 EXIF
可靠地上传大照片 @capgo/capacitor-uploader 原生背景上传,显示进度

安装并配置相机插件

bun add @capacitor/camera
bunx cap sync

确保您有 8.1 或更高版本。 本指南中的方法(takePhoto, chooseFromGallery, editPhoto, editURIPhoto)在 8.1 中添加了 CameraErrorCode 在 8.2 中。较旧的 getPhoto 和 pickImages 已废弃

iOS

添加这些键 ios/App/App/Info.plist. Apple 拒绝了含有模糊字符串的构建,因此解释真实原因:

<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

捕获和图库选择不需要权限。该插件启动系统摄像头活动,并在 Android 11+ 设备上使用 Android Photo Picker(支持该功能的设备),在此设备上回退到 ACTION_OPEN_DOCUMENT 在 <application> 。该片段使用 AndroidManifest.xml命名空间,因此也添加 tools __CAPGO_KEEP_0__ xmlns:tools="http://schemas.android.com/tools" 到根目录 <manifest> 如果它还没有存在(一个新的Capacitor项目只声明 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>

仅仅 saveToGallery: true 需要存储权限,并且仅在旧版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" />

避免添加 READ_MEDIA_IMAGES 除非你的应用的核心目的就是一个图库或编辑器。Google Play的照片和视频权限政策要求偶尔需要照片的应用使用选择器,而不是直接声明权限,否则会被拒绝。

拍摄照片

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

你得到的就是一个 MediaResult:

  • uri:原生文件URI的全尺寸图片(不在web上)。将其传递给Filesystem,一个上传器,或者 editURIPhoto.
  • webPath:一个WebView可以加载的URL。使用它来预览而不是base64。
  • thumbnail:原生中的base64缩略图,web中的全尺寸图片。
  • metadata:尺寸,格式,分辨率,创建日期和EXIF信息 includeMetadata 是 true.
  • saved: 是否 saveToGallery 成功了.

targetWidth 和 targetHeight 必须同时设置。它们是避免上传 12 MP 照片(当您只需要 2048 像素图像时)最便宜的方法。

编辑照片:裁剪和旋转

编辑拍摄后

设置 editable 并且用户在承诺解决之前落地在一个编辑器中:

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

'external' 将图像传递给安卓上安装的另一个编辑器(例如 Google Photos),并在 iOS 上使用内置编辑器(因为 iOS 没有外部编辑器)。 'in-app'.

编辑现有照片

要编辑选中的照片或您之前存储的照片,请传递其 URI:

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

如果您只有一串 base64 (例如来自 canvas 或服务器响应), 使用 editPhoto。输入必须是原始 base64 没有 data: 前缀:

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

在 web 上, 两种方法都不可用。如果您需要滤镜,贴纸,文本或自由形式绘画,内置编辑器是不够的。将图像渲染在 <canvas> 并在那里应用编辑,或使用 JavaScript 剪切器,如 Cropper.js,以固定比例比如头像。一个 canvas 导出 (canvas.toBlob(cb, 'image/jpeg', 0.85) ) 给您一个可以直接上传的 Blob。

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

对于单张照片, allowMultipleSelection: false plus editable: 'in-app' 以接受视频。 mediaType: MediaTypeSelection.All 在 iOS 上,用户可以授予有限的访问权限。

__CAPGO_KEEP_0__ pickLimitedLibraryPhotos() 让用户改变选择并 getLimitedLibraryPhotos() 返回用户可以看到的内容。使用系统选择器时,您很少需要它们,因为选择的项目始终可读。

如果您的应用在自己的 UI 中显示用户的图库(照片打印应用,备份应用),请使用 @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
}

该插件的 pickMedia() 打开系统选择器并将选择复制到缓存中,如果您想为两种流程使用一个插件,请记住 Google Play 政策上述内容之前请求 Android 全部图库访问权。

构建自定义相机屏幕

系统相机 UI 无法显示身份证框架,文档轮廓或您的品牌。 @capgo/camera-preview 渲染原生相机 feed 并让您在 HTML 中绘制 UI:

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

使用 toBack: true,预览位于 WebView 后面,因此页面和每个父元素必须在相机激活时具有透明背景。常见的模式是使用 camera-active 类 body 设置它 background: transparent 并隐藏了应用程序的其余部分。添加 NSCameraUsageDescription 在 iOS 上,和在 Android 上,该插件会请求 CAMERA 通过runtime权限 requestPermissions(). 总是调用 stop() 当屏幕关闭时释放相机。

显示、压缩和转换

Use webPath (or Capacitor.convertFileSrc(path) 为了从其他插件获取路径(包括来自其他插件的路径)来显示图片。避免使用base64。 <img> for paths from other plugins) to show images. Avoid base64 in

在上传之前,压缩图片:

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

HEIC格式的图片可以直接传入压缩器,iPhone的HEIC格式可以直接传入压缩器,压缩器会将其转换为JPEG格式,适合后端无法读取HEIC格式的场景。压缩结果会写入临时目录,需要移动它时请注意。 @capacitor/filesystem 如果需要保留它,请移动它。

EXIF元数据和位置隐私

With includeMetadata: true, metadata.exif 在上传照片之前,务必去除GPS坐标(个人资料图片、市场上架商品)。

  1. 在所有平台上,压缩过程中都会移除EXIF信息。 @capgo/capacitor-file-compressor 压缩时移除所有平台的EXIF数据。
  2. iOS隐私清单指南 保存到相册或应用存储 在

saveToGallery: true on takePhoto 或 editURIPhoto 写入相机胶卷并设置 saved 为了让照片在您的应用中保持私密,请将其复制到应用存储中:

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

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

缓存目录中的文件可以在任何时候被操作系统删除,因此不要存储长期的对它们的引用。

上传照片

对于小照片 fetch 足够了: FormData 足够了:

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

示例:个人资料图片流程

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

显示一个操作面板,包含“拍照”和“从图库选择”。

  1. 调用
  2. Call takePhoto 或 chooseFromGallery 与 editable: 'in-app', targetWidth: 1024, targetHeight: 1024.
  3. 如果您需要保证 1:1 的比例,请在画布中裁剪成正方形。
  4. 使用 quality: 0.8 并移除 EXIF 信息。
  5. 上传后,显示服务器 URL,而不是本地文件,以便头像在重新安装时生存。

故障排除

iOS 设备上打开相机时会崩溃:缺少使用说明键在 Info.plist.

相机打开时会崩溃:缺少使用说明键: set correctOrientation: true (默认值)并避免读取未应用 EXIF 旋转的原始像素。

在低内存的Android设备上拍摄照片后,应用程序会重新启动: Android可能在摄像头打开时杀死您的活动。监听 appRestoredResult from @capacitor/app 以在重新启动后恢复结果

webPath 无法加载: 您保存的 webPath 并在重新启动后重新使用它。持久化 uri 或一个Filesystem路径并再次使用 Capacitor.convertFileSrc.

Camera预览显示一个黑屏: 一个元素仍然具有背景颜色的WebView上方,或者 start() 在权限被授予之前被调用

Play Console拒绝了对照片权限的构建: 移除 READ_MEDIA_IMAGES 和 READ_MEDIA_VIDEO 除非您已提交申报表明您的应用程序需要广泛访问。

不需要发布商店版本即可发布修复

图像尺寸、压缩级别、裁剪比例和上传端点都存储在您的 web code 中。有 Capgo 实时更新 您可以为每个用户更改它们,而无需等待审查,而原生更改,如新插件,则需要进行常规构建。如果您需要在 Mac 上没有 iOS 构建 Capgo 构建 上下文: Capgo 解决方案营销页面。角色: 短 UI 标签或导航项。见于页面 solutions/white-label.astro。保留 Capgo 产品/品牌和开发人员术语的原始形式。

Capacitor Live Updates

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

最新博客

Capgo 为您提供创建真正专业的移动应用所需的最佳见解。