Harmony学习之图片处理与相机调用
Harmony学习之图片处理与相机调用
一、场景引入
小明正在开发一个社交应用,需要实现用户上传头像、拍照分享等功能。他发现图片处理和相机调用是移动应用开发中的核心能力,但面对HarmonyOS的多媒体API,他感到有些困惑。本篇文章将带领小明系统学习HarmonyOS 5 API 12+中的图片处理和相机调用技术。
二、核心概念
2.1 图片处理相关API
HarmonyOS提供了丰富的图片处理能力,主要包含以下模块:
- @ohos.multimedia.image:核心图片处理模块,支持图片编解码、裁剪、缩放等操作
- @kit.MediaLibraryKit:媒体库管理,用于访问相册和保存图片
- @ohos.file.picker:文件选择器,用于从相册选择图片
- @ohos.file.fs:文件系统操作,用于读写图片文件
2.2 相机调用相关API
相机开发主要依赖以下模块:
- @ohos.media.camera:相机核心API,提供相机设备管理和拍照功能
- @ohos.multimedia.mediaLibrary:媒体库访问,用于保存拍摄的照片
- @ohos.abilityAccessCtrl:权限管理,申请相机和存储权限
三、关键实现
3.1 权限配置
在开发图片处理和相机功能前,需要在module.json5中配置必要的权限:
// module.json5
{
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "用于拍照功能",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "用于保存图片到相册",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "用于访问相册图片",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
3.2 从相册选择图片
使用PhotoViewPicker可以方便地从相册选择图片:
// entry/src/main/ets/pages/ImagePickerPage.ets
import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import image from '@ohos.multimedia.image';
import { promptAction } from '@kit.ArkUI';
@Entry
@Component
struct ImagePickerPage {
@State selectedImage: string = '';
@State pixelMap: image.PixelMap | null = null;
// 从相册选择图片
async selectImageFromAlbum() {
try {
const photoSelectOptions = new picker.PhotoSelectOptions();
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
photoSelectOptions.maxSelectNumber = 1;
const photoPicker = new picker.PhotoViewPicker();
const photoSelectResult = await photoPicker.select(photoSelectOptions);
if (photoSelectResult.photoUris.length > 0) {
this.selectedImage = photoSelectResult.photoUris[0];
await this.loadImageToPixelMap(this.selectedImage);
}
} catch (error) {
promptAction.showToast({ message: '选择图片失败' });
}
}
// 将URI转换为PixelMap用于显示
async loadImageToPixelMap(uri: string) {
try {
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(file.fd);
this.pixelMap = await imageSource.createPixelMap();
fs.closeSync(file);
} catch (error) {
promptAction.showToast({ message: '图片加载失败' });
}
}
build() {
Column() {
Button('选择图片')
.onClick(() => {
this.selectImageFromAlbum();
})
.margin(20)
if (this.pixelMap) {
Image(this.pixelMap)
.width(300)
.height(300)
.objectFit(ImageFit.Contain)
.borderRadius(10)
}
}
.width('100%')
.height('100%')
}
}
3.3 调用相机拍照
实现相机拍照功能需要申请权限并配置相机参数:
// entry/src/main/ets/pages/CameraPage.ets
import camera from '@ohos.media.camera';
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
@Entry
@Component
struct CameraPage {
@State capturedImage: string = '';
private cameraKit: camera.CameraKit | null = null;
private cameraDevice: camera.Camera | null = null;
// 申请相机权限
async requestCameraPermission() {
const atManager = abilityAccessCtrl.createAtManager();
const context = getContext(this);
try {
const result = await atManager.requestPermissionsFromUser(context, [
'ohos.permission.CAMERA',
'ohos.permission.WRITE_MEDIA'
]);
if (result.authResults[0] === 0) {
await this.openCamera();
} else {
promptAction.showToast({ message: '相机权限申请失败' });
}
} catch (error) {
promptAction.showToast({ message: '权限申请异常' });
}
}
// 打开相机
async openCamera() {
try {
const context = getContext(this);
this.cameraKit = camera.getCameraKit(context);
const cameraIds = this.cameraKit.getCameraIds();
if (cameraIds.length === 0) {
promptAction.showToast({ message: '未找到可用相机' });
return;
}
// 选择后置摄像头
const backCameraId = cameraIds.find(id => {
const cameraInfo = this.cameraKit!.getCameraInfo(id);
return cameraInfo.getFacingType() === camera.CameraFacing.FACING_BACK;
});
if (!backCameraId) {
promptAction.showToast({ message: '未找到后置摄像头' });
return;
}
this.cameraDevice = await this.cameraKit.createCamera(backCameraId);
promptAction.showToast({ message: '相机准备就绪' });
} catch (error) {
promptAction.showToast({ message: '相机打开失败' });
}
}
// 拍照
async takePhoto() {
if (!this.cameraDevice) {
promptAction.showToast({ message: '请先打开相机' });
return;
}
try {
const photoOutput = await this.cameraDevice.createPhotoOutput();
await this.cameraDevice.startPreview();
const photo = await photoOutput.capture();
this.capturedImage = await this.savePhotoToGallery(photo);
promptAction.showToast({ message: '拍照成功' });
} catch (error) {
promptAction.showToast({ message: '拍照失败' });
}
}
// 保存照片到相册
async savePhotoToGallery(photo: camera.Photo): Promise<string> {
const context = getContext(this);
const mediaLib = mediaLibrary.getMediaLibrary(context);
const photoAsset = await mediaLib.createAsset(
mediaLibrary.MediaType.IMAGE,
'IMG_' + Date.now() + '.jpg'
);
const file = await mediaLib.openAsset(photoAsset, mediaLibrary.OpenMode.WRITE_ONLY);
await file.write(photo.getData());
await file.close();
return photoAsset.uri;
}
build() {
Column() {
Button('打开相机')
.onClick(() => {
this.requestCameraPermission();
})
.margin(10)
Button('拍照')
.onClick(() => {
this.takePhoto();
})
.margin(10)
if (this.capturedImage) {
Image(this.capturedImage)
.width(300)
.height(300)
.objectFit(ImageFit.Contain)
.borderRadius(10)
}
}
.width('100%')
.height('100%')
}
}
3.4 图片压缩处理
在实际应用中,经常需要对图片进行压缩处理:
// entry/src/main/ets/utils/ImageUtils.ets
import image from '@ohos.multimedia.image';
import fs from '@ohos.file.fs';
export class ImageUtils {
// 压缩图片
static async compressImage(
uri: string,
maxWidth: number = 800,
quality: number = 80
): Promise<string | null> {
try {
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(file.fd);
// 获取图片原始尺寸
const imageInfo = await imageSource.getImageInfo();
const { width, height } = imageInfo.size;
// 计算缩放比例
const scale = Math.min(maxWidth / width, 1);
const targetWidth = Math.floor(width * scale);
const targetHeight = Math.floor(height * scale);
// 创建PixelMap并压缩
const decodeOptions = {
desiredSize: { width: targetWidth, height: targetHeight }
};
const pixelMap = await imageSource.createPixelMap(decodeOptions);
const imagePacker = image.createImagePacker();
// 设置压缩参数
const packOptions = {
format: 'image/jpeg',
quality: quality
};
const imageData = await imagePacker.packing(pixelMap, packOptions);
// 保存压缩后的图片
const compressedPath = uri.replace('.jpg', '_compressed.jpg');
const compressedFile = fs.openSync(
compressedPath,
fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY
);
await fs.write(compressedFile.fd, imageData);
fs.closeSync(compressedFile.fd);
// 释放资源
pixelMap.release();
imagePacker.release();
fs.closeSync(file);
return compressedPath;
} catch (error) {
console.error('图片压缩失败:', error);
return null;
}
}
}
3.5 图片裁剪功能
实现图片裁剪功能:
// entry/src/main/ets/utils/ImageUtils.ets
import image from '@ohos.multimedia.image';
import fs from '@ohos.file.fs';
export class ImageUtils {
// 裁剪图片
static async cropImage(
uri: string,
x: number,
y: number,
width: number,
height: number
): Promise<string | null> {
try {
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(file.fd);
const pixelMap = await imageSource.createPixelMap();
// 执行裁剪
const croppedPixelMap = await pixelMap.crop({
x: x,
y: y,
width: width,
height: height
});
// 保存裁剪后的图片
const imagePacker = image.createImagePacker();
const imageData = await imagePacker.packing(croppedPixelMap, {
format: 'image/jpeg',
quality: 90
});
const croppedPath = uri.replace('.jpg', '_cropped.jpg');
const croppedFile = fs.openSync(
croppedPath,
fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY
);
await fs.write(croppedFile.fd, imageData);
fs.closeSync(croppedFile.fd);
// 释放资源
croppedPixelMap.release();
pixelMap.release();
imagePacker.release();
fs.closeSync(file);
return croppedPath;
} catch (error) {
console.error('图片裁剪失败:', error);
return null;
}
}
}
四、实战案例
4.1 头像上传功能
结合前面学到的知识,实现一个完整的头像上传功能:
// entry/src/main/ets/pages/ProfilePage.ets
import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import image from '@ohos.multimedia.image';
import { ImageUtils } from '../utils/ImageUtils';
import { promptAction } from '@kit.ArkUI';
@Entry
@Component
struct ProfilePage {
@State avatarUri: string = '';
@State pixelMap: image.PixelMap | null = null;
// 选择头像
async selectAvatar() {
try {
const photoSelectOptions = new picker.PhotoSelectOptions();
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
photoSelectOptions.maxSelectNumber = 1;
const photoPicker = new picker.PhotoViewPicker();
const photoSelectResult = await photoPicker.select(photoSelectOptions);
if (photoSelectResult.photoUris.length > 0) {
const originalUri = photoSelectResult.photoUris[0];
// 压缩图片
const compressedUri = await ImageUtils.compressImage(originalUri, 300, 80);
if (compressedUri) {
this.avatarUri = compressedUri;
await this.loadImageToPixelMap(this.avatarUri);
promptAction.showToast({ message: '头像设置成功' });
}
}
} catch (error) {
promptAction.showToast({ message: '选择头像失败' });
}
}
// 加载图片到PixelMap
async loadImageToPixelMap(uri: string) {
try {
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(file.fd);
this.pixelMap = await imageSource.createPixelMap();
fs.closeSync(file);
} catch (error) {
promptAction.showToast({ message: '图片加载失败' });
}
}
build() {
Column() {
// 头像显示区域
Stack() {
if (this.pixelMap) {
Image(this.pixelMap)
.width(120)
.height(120)
.borderRadius(60)
.objectFit(ImageFit.Cover)
} else {
Image($r('app.media.default_avatar'))
.width(120)
.height(120)
.borderRadius(60)
}
// 上传按钮
Button('+')
.width(40)
.height(40)
.borderRadius(20)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.fontSize(20)
.position({ x: '80%', y: '80%' })
.onClick(() => {
this.selectAvatar();
})
}
.width(120)
.height(120)
.margin(30)
Text('点击上传头像')
.fontSize(16)
.fontColor(Color.Gray)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
五、最佳实践
5.1 权限管理最佳实践
- 按需申请:不要一次性申请所有权限,在需要使用时再申请
- 权限拒绝处理:处理用户拒绝权限的情况,提供友好的提示和引导
- 权限状态检查:在调用敏感API前检查权限状态
// 检查相机权限
async checkCameraPermission(): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
const context = getContext(this);
try {
const permissions = await atManager.checkAccessToken(
context,
['ohos.permission.CAMERA']
);
return permissions[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
} catch (error) {
return false;
}
}
5.2 内存管理最佳实践
- 及时释放资源:使用完PixelMap、ImageSource等资源后及时调用release()方法
- 避免内存泄漏:不要在循环中创建大量图片对象
- 大图处理:对大尺寸图片进行压缩后再处理
5.3 性能优化
- 图片懒加载:使用LazyForEach加载列表中的图片
- 图片缓存:对网络图片进行本地缓存,避免重复下载
- 渐进式加载:先加载低质量图片,再加载高质量图片
六、总结与行动建议
通过本篇文章的学习,小明掌握了HarmonyOS中图片处理和相机调用的核心技能。建议在实际开发中:
- 遵循权限管理规范,确保应用合规性
- 注意资源释放,避免内存泄漏问题
- 优化图片处理流程,提升用户体验
- 参考官方文档,了解最新API变化
在实际项目中,可以结合业务需求,灵活运用这些技术,实现更丰富的图片处理功能。

浙公网安备 33010602011771号