You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Expo+React Native中创建内存图像并合成多图后保存

在Expo中合成图像(黑背景+两张图片)并保存

所需依赖

先安装必要的Expo包:

npx expo install expo-canvas expo-file-system expo-image-picker expo-media-library

实现思路

借助expo-canvas创建内存画布,先绘制黑色背景,再加载两张目标图片并绘制到画布指定位置,最后将画布内容导出为图片,完成显示和本地保存。

完整代码示例

import React, { useRef, useState } from 'react';
import { View, Button, Image, StyleSheet, Alert } from 'react-native';
import { Canvas, Image as CanvasImage } from 'expo-canvas';
import * as ImagePicker from 'expo-image-picker';
import * as FileSystem from 'expo-file-system';
import * as MediaLibrary from 'expo-media-library';

export default function ImageComposer() {
  const canvasRef = useRef(null);
  const [selectedImages, setSelectedImages] = useState([]);
  const [composedImageUri, setComposedImageUri] = useState(null);

  // 选择两张图片
  const pickImages = async () => {
    const [pickerStatus, mediaStatus] = await Promise.all([
      ImagePicker.requestMediaLibraryPermissionsAsync(),
      MediaLibrary.requestPermissionsAsync()
    ]);
    
    if (pickerStatus.status !== 'granted' || mediaStatus.status !== 'granted') {
      Alert.alert('权限不足', '需要访问相册和保存图片的权限');
      return;
    }

    const selected = [];
    for (let i = 0; i < 2; i++) {
      const result = await ImagePicker.launchImageLibraryAsync({
        allowsEditing: false,
        quality: 1,
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
      });
      if (!result.canceled) {
        selected.push(result.assets[0].uri);
      } else {
        break;
      }
    }

    selected.length === 2 ? setSelectedImages(selected) : Alert.alert('提示', '请选择两张图片');
  };

  // 合成图像
  const composeImages = async () => {
    if (selectedImages.length !== 2 || !canvasRef.current) return;
    
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    // 自定义画布尺寸,可根据需求调整
    const canvasWidth = 800;
    const canvasHeight = 600;
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    // 绘制黑色背景
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);

    // 加载并绘制第一张图片(示例:左上角区域)
    const img1 = new CanvasImage(canvas);
    img1.src = selectedImages[0];
    await new Promise(resolve => img1.onload = resolve);
    // 参数:图片对象、起始x、起始y、绘制宽度、绘制高度
    ctx.drawImage(img1, 0, 0, canvasWidth / 2, canvasHeight / 2);

    // 加载并绘制第二张图片(示例:右上角区域)
    const img2 = new CanvasImage(canvas);
    img2.src = selectedImages[1];
    await new Promise(resolve => img2.onload = resolve);
    ctx.drawImage(img2, canvasWidth / 2, 0, canvasWidth / 2, canvasHeight / 2);

    // 导出画布为图片URI
    const uri = await canvas.toDataURLAsync('image/jpeg', 0.9);
    setComposedImageUri(uri);
  };

  // 保存合成后的图片到相册
  const saveImage = async () => {
    if (!composedImageUri) return;
    try {
      // 处理Base64格式,提取数据部分
      const base64Data = composedImageUri.split(',')[1];
      const filePath = `${FileSystem.documentDirectory}composed_${Date.now()}.jpg`;
      
      await FileSystem.writeAsStringAsync(filePath, base64Data, {
        encoding: FileSystem.EncodingType.Base64,
      });
      await MediaLibrary.saveToLibraryAsync(filePath);
      Alert.alert('成功', '图片已保存到相册');
    } catch (error) {
      console.error('保存失败:', error);
      Alert.alert('失败', '保存图片时出现错误');
    }
  };

  return (
    <View style={styles.container}>
      <Button title="选择两张图片" onPress={pickImages} />
      
      {selectedImages.length === 2 && (
        <Button title="合成图像" onPress={composeImages} style={styles.btnMargin} />
      )}

      {composedImageUri && (
        <View style={styles.imageWrap}>
          <Image source={{ uri: composedImageUri }} style={styles.previewImg} />
          <Button title="保存图片" onPress={saveImage} style={styles.btnMargin} />
        </View>
      )}

      {/* 隐藏画布,仅用于后台合成 */}
      <Canvas ref={canvasRef} style={styles.hiddenCanvas} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  btnMargin: {
    marginVertical: 15,
    width: '80%',
  },
  imageWrap: {
    marginTop: 20,
    alignItems: 'center',
  },
  previewImg: {
    width: 400,
    height: 300,
    resizeMode: 'contain',
    borderRadius: 8,
  },
  hiddenCanvas: {
    position: 'absolute',
    width: 0,
    height: 0,
  },
});

关键细节说明

  • 画布绘制:通过fillRect填充黑色背景,drawImage控制图片的位置和缩放尺寸,可根据需求调整坐标和宽高参数。
  • 图片加载:必须等待CanvasImage的onload事件触发后再绘制,否则会因图片未加载完成导致空白。
  • 权限处理:同时申请相册访问和媒体库保存权限,确保功能正常运行。
  • 保存逻辑:将画布导出的Base64格式数据转为本地文件,再通过expo-media-library保存到系统相册。

内容的提问来源于stack exchange,提问作者devnull69

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 16:34:36