如何在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
相关产品推荐
相关产品推荐

