HarmonyOS APP<<古今职鉴定>>开源教程第22篇:图片处理与资源管理
本篇学习图片处理能力和资源管理,实现祝福卡片图片处理
图:图片处理与资源管理 的关键流程与实现要点。
学习目标
完成本篇后,你将能够:
- ✅ 掌握图片资源类型
- ✅ 配置图片组件属性
- ✅ 使用图片处理能力
- ✅ 管理应用资源文件
预计学习时间
约 90 分钟
实战一:图片资源类型
第一步:本地图片资源
// 使用 $r 引用 media 目录下的图片 Image($r('app.media.icon')) .width(100) .height(100) // 使用 $rawfile 引用 rawfile 目录下的图片 Image($rawfile('images/photo.png')) .width(100) .height(100)第二步:网络图片
Image('https://example.com/image.png') .width(100) .height(100)第三步:Base64 图片
const base64Image = 'data:image/png;base64,iVBORw0KGgo...'; Image(base64Image) .width(100) .height(100)第四步:PixelMap 图片
import { image } from '@kit.ImageKit'; @State pixelMap: image.PixelMap | null = null; // 使用 PixelMap if (this.pixelMap) { Image(this.pixelMap) .width(100) .height(100) }实战二:图片组件配置
第一步:填充模式
Image($r('app.media.photo')) .width(200) .height(150) .objectFit(ImageFit.Cover) // 填充模式| 模式 | 说明 |
|---|---|
| Contain | 保持比例,完整显示 |
| Cover | 保持比例,填满容器 |
| Fill | 拉伸填满,可能变形 |
| None | 原始尺寸 |
| ScaleDown | 缩小或原始尺寸 |
案例效果:
┌─────────────────────────────────────────┐ │ 填充模式对比 │ │ │ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │ 福 │ │██████│ │██████│ │ │ │ │ │██福██│ │█福███│ │ │ │ 完整 │ │██████│ │██████│ │ │ └──────┘ └──────┘ └──────┘ │ │ Contain Cover Fill │ │ 保持比例 填满裁切 拉伸填满 │ └─────────────────────────────────────────┘第二步:图片插值
Image($r('app.media.photo')) .interpolation(ImageInterpolation.High) // 高质量插值第三步:渲染模式
// 原始模式 Image($r('app.media.icon')) .renderMode(ImageRenderMode.Original) // 模板模式(可着色) Image($r('app.media.icon')) .renderMode(ImageRenderMode.Template) .fillColor('#c41e3a')第四步:图片圆角和边框
Image($r('app.media.avatar')) .width(80) .height(80) .borderRadius(40) // 圆形 .border({ width: 2, color: '#c41e3a' })实战三:图片处理能力
第一步:创建 PixelMap
import { image } from '@kit.ImageKit'; async function createPixelMap(width: number, height: number): Promise<image.PixelMap> { const options: image.InitializationOptions = { size: { width, height }, pixelFormat: image.PixelMapFormat.RGBA_8888, alphaType: image.AlphaType.IMAGE_ALPHA_TYPE_OPAQUE }; return await image.createPixelMap(options); }第二步:从资源创建 PixelMap
async function loadImageFromResource(resourceName: string): Promise<image.PixelMap | null> { try { const context = getContext(this); const resourceManager = context.resourceManager; // 读取资源 const imageData = await resourceManager.getMediaContent($r(`app.media.${resourceName}`)); // 创建 ImageSource const imageSource = image.createImageSource(imageData.buffer); // 解码为 PixelMap const pixelMap = await imageSource.createPixelMap(); return pixelMap; } catch (error) { console.error('加载图片失败:', error); return null; } }第三步:图片裁剪
async function cropImage( pixelMap: image.PixelMap, x: number, y: number, width: number, height: number ): Promise<void> { await pixelMap.crop({ x, y, size: { width, height } }); }第四步:图片缩放
async function scaleImage( pixelMap: image.PixelMap, scaleX: number, scaleY: number ): Promise<void> { await pixelMap.scale(scaleX, scaleY); }实战四:资源管理器
第一步:获取资源管理器
import { resourceManager } from '@kit.LocalizationKit'; const context = getContext(this); const resManager = context.resourceManager;第二步:读取 rawfile 资源
async function readRawFile(fileName: string): Promise<Uint8Array> { const context = getContext(this); const resManager = context.resourceManager; const data = await resManager.getRawFileContent(fileName); return data; }第三步:读取媒体资源
async function readMediaResource(resourceId: Resource): Promise<Uint8Array> { const context = getContext(this); const resManager = context.resourceManager; const data = await resManager.getMediaContent(resourceId); return data; }第四步:写入临时文件
import { fileIo } from '@kit.CoreFileKit'; async function writeToTempFile(data: Uint8Array, fileName: string): Promise<string> { const context = getContext(this); const tempDir = context.tempDir; const filePath = `${tempDir}/${fileName}`; const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY); fileIo.writeSync(file.fd, data.buffer); fileIo.closeSync(file.fd); return filePath; }实战五:祝福卡片图片处理
第一步:创建图片处理页面
import { image } from '@kit.ImageKit'; import { fileIo } from '@kit.CoreFileKit'; interface BlessingCard { name: string; title: string; resource: Resource; } @Entry @Component struct Lesson22Page { @State currentCard: BlessingCard | null = null; @State cardPixelMap: image.PixelMap | null = null; @State isLoading: boolean = false; private blessingCards: BlessingCard[] = [ { name: 'fortune', title: '福', resource: $r('app.media.blessing_fortune') }, { name: 'wealth', title: '财', resource: $r('app.media.blessing_gold_ingot') }, { name: 'happiness', title: '喜', resource: $r('app.media.blessing_happy_newyear') }, { name: 'longevity', title: '寿', resource: $r('app.media.blessing_good_luck') }, { name: 'prosperity', title: '禄', resource: $r('app.media.blessing_five_fortune') } ]; aboutToAppear() { this.selectRandomCard(); } selectRandomCard() { const index = Math.floor(Math.random() * this.blessingCards.length); this.currentCard = this.blessingCards[index]; } async loadCardImage() { if (!this.currentCard) return; this.isLoading = true; try { const context = getContext(this); const resManager = context.resourceManager; // 读取图片资源 const imageData = await resManager.getMediaContent(this.currentCard.resource); // 创建 ImageSource const imageSource = image.createImageSource(imageData.buffer); // 解码为 PixelMap this.cardPixelMap = await imageSource.createPixelMap(); } catch (error) { console.error('加载图片失败:', error); } finally { this.isLoading = false; } } async saveToTempFile(): Promise<string | null> { if (!this.currentCard) return null; try { const context = getContext(this); const resManager = context.resourceManager; // 读取图片资源 const imageData = await resManager.getMediaContent(this.currentCard.resource); // 写入临时文件 const tempDir = context.tempDir; const filePath = `${tempDir}/${this.currentCard.name}.png`; const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY); fileIo.writeSync(file.fd, imageData.buffer); fileIo.closeSync(file.fd); return filePath; } catch (error) { console.error('保存文件失败:', error); return null; } } build() { Column() { // 头部 Row() { Text('图片处理') .fontSize(18) .fontWeight(FontWeight.Bold) .fontColor('#1e293b') } .width('100%') .height(56) .padding({ left: 16, right: 16 }) .backgroundColor(Color.White) Scroll() { Column({ space: 20 }) { // 当前卡片 Column({ space: 12 }) { Text('当前祝福卡片') .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#1e293b') if (this.currentCard) { Column({ space: 8 }) { Image(this.currentCard.resource) .width(200) .height(200) .objectFit(ImageFit.Contain) .borderRadius(12) Text(this.currentCard.title) .fontSize(24) .fontWeight(FontWeight.Bold) .fontColor('#c41e3a') } } Button('换一张') .onClick(() => { this.selectRandomCard(); }) .backgroundColor('#c41e3a') } .width('100%') .padding(16) .backgroundColor(Color.White) .borderRadius(12) .alignItems(HorizontalAlign.Center) // 图片操作 Column({ space: 12 }) { Text('图片操作') .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#1e293b') Row({ space: 12 }) { Button('加载 PixelMap') .layoutWeight(1) .onClick(async () => { await this.loadCardImage(); }) Button('保存到临时目录') .layoutWeight(1) .onClick(async () => { const path = await this.saveToTempFile(); if (path) { AlertDialog.show({ title: '保存成功', message: `文件路径: ${path}`, primaryButton: { value: '确定', action: () => {} } }); } }) } .width('100%') if (this.isLoading) { LoadingProgress() .width(32) .height(32) } if (this.cardPixelMap) { Text('PixelMap 已加载') .fontSize(14) .fontColor('#22c55e') } } .width('100%') .padding(16) .backgroundColor(Color.White) .borderRadius(12) .alignItems(HorizontalAlign.Start) // 图片展示模式 Column({ space: 12 }) { Text('填充模式对比') .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#1e293b') Row({ space: 8 }) { Column({ space: 4 }) { Image($r('app.media.blessing_fortune')) .width(80) .height(60) .objectFit(ImageFit.Contain) .backgroundColor('#f0f0f0') .borderRadius(4) Text('Contain') .fontSize(10) .fontColor('#64748b') } Column({ space: 4 }) { Image($r('app.media.blessing_fortune')) .width(80) .height(60) .objectFit(ImageFit.Cover) .backgroundColor('#f0f0f0') .borderRadius(4) Text('Cover') .fontSize(10) .fontColor('#64748b') } Column({ space: 4 }) { Image($r('app.media.blessing_fortune')) .width(80) .height(60) .objectFit(ImageFit.Fill) .backgroundColor('#f0f0f0') .borderRadius(4) Text('Fill') .fontSize(10) .fontColor('#64748b') } } } .width('100%') .padding(16) .backgroundColor(Color.White) .borderRadius(12) .alignItems(HorizontalAlign.Start) } .padding(16) } .layoutWeight(1) } .width('100%') .height('100%') .backgroundColor('#f8f6f5') } } @Builder export function Lesson22PageBuilder() { Lesson22Page() }案例效果:页面运行后展示如下界面:
┌─────────────────────────────────────┐ │ 图片处理 │ ├─────────────────────────────────────┤ │ │ │ ┌─────────────────────────────┐ │ │ │ 当前祝福卡片 │ │ │ │ │ │ │ │ ┌──────────┐ │ │ │ │ │ │ │ │ │ │ │ 🧧福 │ │ │ │ │ │ │ │ │ │ │ └──────────┘ │ │ │ │ 福 │ │ │ │ │ │ │ │ [ 换一张 ] │ │ │ └─────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ 图片操作 │ │ │ │ │ │ │ │ [加载PixelMap] [保存到临时] │ │ │ │ │ │ │ │ ✅ PixelMap 已加载 │ │ │ └─────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ 填充模式对比 │ │ │ │ │ │ │ │ ┌────┐ ┌────┐ ┌────┐ │ │ │ │ │ 福 │ │█福█│ │█福█│ │ │ │ │ └────┘ └────┘ └────┘ │ │ │ │ Contain Cover Fill │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────┘效果说明: - 顶部展示随机抽取的祝福卡片图片和标题 - 点击「换一张」随机切换不同祝福卡片 - 「加载 PixelMap」按钮加载后显示绿色 ✅ 提示 - 「保存到临时目录」成功后弹出路径弹窗 - 底部展示 Contain/Cover/Fill 三种填充模式的对比效果
第二步:运行验证
hvigorw assembleHap --no-daemon本课小结
核心知识点
| 知识点 | 说明 |
|---|---|
| $r() | 引用 media 资源 |
| $rawfile() | 引用 rawfile 资源 |
| objectFit | 图片填充模式 |
| PixelMap | 图片像素数据 |
| resourceManager | 资源管理器 |
图片处理流程
- 获取资源管理器
- 读取图片数据
- 创建 ImageSource
- 解码为 PixelMap
- 进行图片处理
- 保存或显示
课后练习
练习1:实现图片滤镜
为祝福卡片添加灰度、模糊等滤镜效果。
练习2:实现图片合成
将祝福文字合成到卡片图片上。
下一课预告
第23课我们将进入案例实战篇,完整开发职官词典模块,包括:
- 需求分析与设计
- 数据层实现
- 列表页与详情页
- 收藏功能
项目开源地址
https://gitcode.com/daleishen/gujinzhijian
