依托HarmonyOS 6.1最新特性实现图片编辑APP(六):图片元数据Exif/XMP处理
·
依托HarmonyOS 6.1最新特性实现图片编辑APP(六):图片元数据Exif/XMP处理
前言
在完成图片的基础编辑和滤镜处理后,ImageEditor Pro APP还需要一个重要的功能——图片元数据管理。每张照片都携带着大量的元数据信息,包括拍摄时间、设备型号、GPS位置、光圈快门等Exif信息,以及版权、作者、编辑历史等XMP信息。Image Kit 提供了完整的元数据读取和编辑能力。
图片元数据(Image Metadata)是描述图像属性、拍摄信息、格式信息或处理辅助信息的数据集合,可供读取、修改或参与后续图像处理。Exif(可交换图像文件格式)和XMP(可扩展元数据平台)是两种最重要的元数据标准。
一、图片元数据概念
1.1 Exif元数据
Exif(Exchangeable image file format)是记录照片属性和拍摄信息的元数据格式:
| Exif标签 | 说明 | 示例值 |
|---|---|---|
| Make | 相机制造商 | “HUAWEI” |
| Model | 相机型号 | “P60 Pro” |
| DateTime | 拍摄时间 | “2026:06:15 14:30:00” |
| GPSLatitude | GPS纬度 | “39.9042 N” |
| GPSLongitude | GPS经度 | “116.4074 E” |
| FNumber | 光圈值 | “f/1.8” |
| ExposureTime | 曝光时间 | “1/1000” |
| ISOSpeedRatings | ISO感光度 | “100” |
| FocalLength | 焦距 | “5.4mm” |
| Orientation | 图片方向 | 1(正常)/6(旋转90°) |
1.2 XMP元数据
XMP(Extensible Metadata Platform)是一种可扩展的元数据格式:
| XMP字段 | 说明 | 用途 |
|---|---|---|
| dc:creator | 作者 | 版权信息 |
| dc:rights | 版权声明 | 法律保护 |
| dc:description | 图片描述 | 内容说明 |
| xmp:Rating | 评分 | 图片评级 |
| xmp:Label | 标签 | 分类标识 |
| photoshop:DateCreated | 创建日期 | 时间信息 |
二、Exif信息读取
2.1 基础Exif读取
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
// Exif信息读取工具
class ExifReader {
private imageSource: image.ImageSource;
constructor(imageSource: image.ImageSource) {
this.imageSource = imageSource;
}
// 获取所有Exif标签
async getAllExifTags(): Promise<Record<string, string> | undefined> {
try {
const tags = await this.imageSource.getImageProperty();
console.info(`Retrieved ${Object.keys(tags).length} Exif tags.`);
return tags;
} catch (error) {
console.error(`Failed to get Exif tags: ${error}`);
return undefined;
}
}
// 获取指定Exif标签
async getExifTag(tagName: string): Promise<string | undefined> {
try {
const value = await this.imageSource.getImagePropertyString(tagName);
console.info(`Exif tag '${tagName}': ${value}`);
return value;
} catch (error) {
console.error(`Failed to get Exif tag '${tagName}': ${error}`);
return undefined;
}
}
// 获取拍摄信息摘要
async getCameraInfo(): Promise<CameraInfo> {
const info: CameraInfo = {
make: await this.getExifTag('Make') || 'Unknown',
model: await this.getExifTag('Model') || 'Unknown',
dateTime: await this.getExifTag('DateTime') || 'Unknown',
orientation: await this.getExifTag('Orientation') || '1',
fNumber: await this.getExifTag('FNumber') || 'Unknown',
exposureTime: await this.getExifTag('ExposureTime') || 'Unknown',
iso: await this.getExifTag('ISOSpeedRatings') || 'Unknown',
focalLength: await this.getExifTag('FocalLength') || 'Unknown'
};
return info;
}
// 获取GPS信息
async getGPSInfo(): Promise<GPSInfo> {
const gpsInfo: GPSInfo = {
latitude: await this.getExifTag('GPSLatitude') || 'Unknown',
latitudeRef: await this.getExifTag('GPSLatitudeRef') || 'N',
longitude: await this.getExifTag('GPSLongitude') || 'Unknown',
longitudeRef: await this.getExifTag('GPSLongitudeRef') || 'E',
altitude: await this.getExifTag('GPSAltitude') || 'Unknown'
};
return gpsInfo;
}
}
interface CameraInfo {
make: string;
model: string;
dateTime: string;
orientation: string;
fNumber: string;
exposureTime: string;
iso: string;
focalLength: string;
}
interface GPSInfo {
latitude: string;
latitudeRef: string;
longitude: string;
longitudeRef: string;
altitude: string;
}
2.2 图片旋转角度获取
// 旋转角度获取工具
class ImageRotationUtil {
// 从Exif方向获取旋转角度
static async getRotationFromExif(imageSource: image.ImageSource): Promise<number> {
try {
const orientation = await imageSource.getImagePropertyString('Orientation');
console.info(`Exif orientation: ${orientation}`);
switch (orientation) {
case '1': return 0; // 正常
case '3': return 180; // 旋转180度
case '6': return 90; // 顺时针旋转90度
case '8': return 270; // 顺时针旋转270度
default: return 0;
}
} catch (error) {
console.error(`Failed to get rotation from Exif: ${error}`);
return 0;
}
}
// 自动校正图片方向
static async autoOrient(pixelMap: image.PixelMap, imageSource: image.ImageSource): Promise<void> {
const rotation = await ImageRotationUtil.getRotationFromExif(imageSource);
if (rotation > 0) {
await pixelMap.rotate(rotation);
console.info(`Auto-oriented by ${rotation} degrees.`);
}
}
}
三、Exif信息编辑
3.1 修改Exif标签
// Exif信息编辑工具
class ExifEditor {
private imageSource: image.ImageSource;
constructor(imageSource: image.ImageSource) {
this.imageSource = imageSource;
}
// 修改单个Exif标签
async modifyExifTag(tagName: string, value: string): Promise<boolean> {
try {
await this.imageSource.modifyImageProperty(tagName, value);
console.info(`Exif tag '${tagName}' modified to: ${value}`);
return true;
} catch (error) {
console.error(`Failed to modify Exif tag '${tagName}': ${error}`);
return false;
}
}
// 批量修改Exif标签
async modifyExifTags(tags: Record<string, string>): Promise<boolean> {
try {
for (const [tagName, value] of Object.entries(tags)) {
await this.imageSource.modifyImageProperty(tagName, value);
}
console.info(`Modified ${Object.keys(tags).length} Exif tags.`);
return true;
} catch (error) {
console.error(`Failed to modify Exif tags: ${error}`);
return false;
}
}
// 设置拍摄时间
async setDateTime(dateTime: string): Promise<boolean> {
return this.modifyExifTag('DateTime', dateTime);
}
// 设置图片方向
async setOrientation(orientation: string): Promise<boolean> {
return this.modifyExifTag('Orientation', orientation);
}
// 清除GPS信息(隐私保护)
async clearGPSInfo(): Promise<boolean> {
try {
const gpsTags = ['GPSLatitude', 'GPSLatitudeRef', 'GPSLongitude',
'GPSLongitudeRef', 'GPSAltitude', 'GPSAltitudeRef'];
for (const tag of gpsTags) {
await this.modifyExifTag(tag, '');
}
console.info('GPS information cleared for privacy.');
return true;
} catch (error) {
console.error(`Failed to clear GPS info: ${error}`);
return false;
}
}
}
3.2 专有元数据获取
// 专有元数据获取工具
class SpecificMetadataReader {
// 获取HEIF/HEIC专有元数据
static async getHEIFMetadata(imageSource: image.ImageSource): Promise<Record<string, string>> {
try {
const metadata: Record<string, string> = {};
// 获取HEIF特有的属性
const heifType = await imageSource.getImagePropertyString('HEIFType');
if (heifType) metadata['HEIFType'] = heifType;
const depthMap = await imageSource.getImagePropertyString('DepthMap');
if (depthMap) metadata['DepthMap'] = depthMap;
return metadata;
} catch (error) {
console.error(`Failed to get HEIF metadata: ${error}`);
return {};
}
}
// 获取DNG专有元数据
static async getDNGMetadata(imageSource: image.ImageSource): Promise<Record<string, string>> {
try {
const metadata: Record<string, string> = {};
const dngVersion = await imageSource.getImagePropertyString('DNGVersion');
if (dngVersion) metadata['DNGVersion'] = dngVersion;
const rawData = await imageSource.getImagePropertyString('RawData');
if (rawData) metadata['RawData'] = rawData;
return metadata;
} catch (error) {
console.error(`Failed to get DNG metadata: ${error}`);
return {};
}
}
}
四、XMP元数据处理
4.1 XMP数据读取
// XMP元数据读取工具
class XMPReader {
private imageSource: image.ImageSource;
constructor(imageSource: image.ImageSource) {
this.imageSource = imageSource;
}
// 获取完整的XMP数据
async getXMPData(): Promise<string | undefined> {
try {
const xmpData = await this.imageSource.getImagePropertyString('XMP');
console.info(`XMP data length: ${xmpData?.length || 0}`);
return xmpData;
} catch (error) {
console.error(`Failed to get XMP data: ${error}`);
return undefined;
}
}
// 解析XMP数据中的关键字段
async parseXMPFields(): Promise<XMPFields> {
const xmpData = await this.getXMPData();
const fields: XMPFields = {
creator: '',
rights: '',
description: '',
rating: '',
label: ''
};
if (xmpData) {
// 从XMP XML中提取字段
// 实际项目中需要使用XML解析器
fields.creator = this.extractXMPField(xmpData, 'dc:creator');
fields.rights = this.extractXMPField(xmpData, 'dc:rights');
fields.description = this.extractXMPField(xmpData, 'dc:description');
fields.rating = this.extractXMPField(xmpData, 'xmp:Rating');
fields.label = this.extractXMPField(xmpData, 'xmp:Label');
}
return fields;
}
// 简单的XMP字段提取
private extractXMPField(xmp: string, fieldName: string): string {
const regex = new RegExp(`${fieldName}\\s*=\\s*"([^"]*)"`, 'i');
const match = xmp.match(regex);
return match ? match[1] : '';
}
}
interface XMPFields {
creator: string;
rights: string;
description: string;
rating: string;
label: string;
}
4.2 XMP数据编辑
// XMP元数据编辑工具
class XMPEditor {
private imageSource: image.ImageSource;
constructor(imageSource: image.ImageSource) {
this.imageSource = imageSource;
}
// 设置XMP元数据
async setXMPData(xmpData: string): Promise<boolean> {
try {
await this.imageSource.modifyImageProperty('XMP', xmpData);
console.info('XMP data updated successfully.');
return true;
} catch (error) {
console.error(`Failed to set XMP data: ${error}`);
return false;
}
}
// 构建XMP数据XML
static buildXMPXML(fields: XMPFields): string {
return `<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xmp="http://ns.adobe.com/xap/1.0/">
<dc:creator>${fields.creator}</dc:creator>
<dc:rights>${fields.rights}</dc:rights>
<dc:description>${fields.description}</dc:description>
<xmp:Rating>${fields.rating}</xmp:Rating>
<xmp:Label>${fields.label}</xmp:Label>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>`;
}
// 设置作者版权信息
async setCopyright(author: string, rights: string): Promise<boolean> {
const xmp = XMPEditor.buildXMPXML({
creator: author,
rights: rights,
description: '',
rating: '',
label: ''
});
return this.setXMPData(xmp);
}
}
五、ImageEditor Pro元数据管理页面
5.1 元数据查看页面
// 元数据信息类型
interface MetadataInfo {
camera: CameraInfo;
gps: GPSInfo;
xmp: XMPFields;
imageFormat: string;
imageSize: string;
fileSize: string;
}
// 元数据查看页面
@Entry
@Component
struct MetadataPage {
@State metadata: MetadataInfo | undefined = undefined;
@State isLoading: boolean = false;
@State activeTab: string = 'camera';
private imageSource: image.ImageSource | undefined;
// 加载元数据
async loadMetadata(filePath: string): Promise<void> {
this.isLoading = true;
try {
this.imageSource = image.createImageSource(filePath);
const exifReader = new ExifReader(this.imageSource);
const xmpReader = new XMPReader(this.imageSource);
const imageInfo = await this.imageSource.getImageInfo();
this.metadata = {
camera: await exifReader.getCameraInfo(),
gps: await exifReader.getGPSInfo(),
xmp: await xmpReader.parseXMPFields(),
imageFormat: imageInfo.encodedFormat || 'Unknown',
imageSize: `${imageInfo.size.width} x ${imageInfo.size.height}`,
fileSize: 'Unknown'
};
} catch (error) {
console.error(`Failed to load metadata: ${error}`);
} finally {
this.isLoading = false;
}
}
build() {
Column() {
Text('图片元数据')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.padding(20)
if (this.isLoading) {
LoadingProgress().width(50).height(50)
}
if (this.metadata) {
// 标签切换
Tabs({ index: 0 }) {
TabContent() {
this.buildCameraInfo()
}.tabBar('拍摄信息')
TabContent() {
this.buildGPSInfo()
}.tabBar('GPS信息')
TabContent() {
this.buildXMPInfo()
}.tabBar('XMP信息')
TabContent() {
this.buildImageInfo()
}.tabBar('图片信息')
}
}
}
.width('100%')
.height('100%')
}
@Builder
buildCameraInfo() {
List() {
ListItem() { this.buildInfoRow('制造商', this.metadata?.camera.make || '') }
ListItem() { this.buildInfoRow('型号', this.metadata?.camera.model || '') }
ListItem() { this.buildInfoRow('拍摄时间', this.metadata?.camera.dateTime || '') }
ListItem() { this.buildInfoRow('光圈', this.metadata?.camera.fNumber || '') }
ListItem() { this.buildInfoRow('曝光时间', this.metadata?.camera.exposureTime || '') }
ListItem() { this.buildInfoRow('ISO', this.metadata?.camera.iso || '') }
ListItem() { this.buildInfoRow('焦距', this.metadata?.camera.focalLength || '') }
}
}
@Builder
buildGPSInfo() {
List() {
ListItem() { this.buildInfoRow('纬度', `${this.metadata?.gps.latitude || ''} ${this.metadata?.gps.latitudeRef || ''}`) }
ListItem() { this.buildInfoRow('经度', `${this.metadata?.gps.longitude || ''} ${this.metadata?.gps.longitudeRef || ''}`) }
ListItem() { this.buildInfoRow('海拔', this.metadata?.gps.altitude || '') }
}
}
@Builder
buildXMPInfo() {
List() {
ListItem() { this.buildInfoRow('作者', this.metadata?.xmp.creator || '') }
ListItem() { this.buildInfoRow('版权', this.metadata?.xmp.rights || '') }
ListItem() { this.buildInfoRow('描述', this.metadata?.xmp.description || '') }
ListItem() { this.buildInfoRow('评分', this.metadata?.xmp.rating || '') }
}
}
@Builder
buildImageInfo() {
List() {
ListItem() { this.buildInfoRow('格式', this.metadata?.imageFormat || '') }
ListItem() { this.buildInfoRow('尺寸', this.metadata?.imageSize || '') }
ListItem() { this.buildInfoRow('文件大小', this.metadata?.fileSize || '') }
}
}
@Builder
buildInfoRow(label: string, value: string) {
Row() {
Text(label)
.fontSize(14)
.fontColor('#999999')
.width('30%')
Text(value || '未知')
.fontSize(14)
.fontColor('#333333')
.width('70%')
.textAlign(TextAlign.End)
}
.width('100%')
.padding({ left: 20, right: 20, top: 12, bottom: 12 })
}
}
5.2 元数据编辑功能
// 元数据编辑工具
class MetadataEditor {
// 隐私保护:清除敏感元数据
static async sanitizeMetadata(imageSource: image.ImageSource): Promise<boolean> {
try {
const editor = new ExifEditor(imageSource);
// 清除GPS位置信息
await editor.clearGPSInfo();
// 清除相机序列号
await editor.modifyExifTag('BodySerialNumber', '');
await editor.modifyExifTag('LensSerialNumber', '');
console.info('Metadata sanitized for privacy.');
return true;
} catch (error) {
console.error(`Metadata sanitization failed: ${error}`);
return false;
}
}
// 添加版权水印信息
static async addCopyrightWatermark(
imageSource: image.ImageSource,
author: string,
year: string
): Promise<boolean> {
try {
const xmpEditor = new XMPEditor(imageSource);
const copyright = `Copyright ${year} ${author}. All rights reserved.`;
return await xmpEditor.setCopyright(author, copyright);
} catch (error) {
console.error(`Copyright watermark failed: ${error}`);
return false;
}
}
}
六、元数据应用场景
6.1 常见应用场景
| 场景 | 使用元数据 | 实现方式 |
|---|---|---|
| 图片自动旋转 | Exif Orientation | 读取方向标签并自动旋转 |
| 照片时间线 | Exif DateTime | 按拍摄时间排序照片 |
| 地图展示 | Exif GPS | 在MapView上标记拍摄位置 |
| 隐私保护 | Exif GPS/SN | 分享前清除敏感信息 |
| 版权保护 | XMP dc:rights | 嵌入作者和版权声明 |
| 图片评级 | XMP xmp:Rating | 按评分筛选图片 |
6.2 元数据保留策略
// 元数据保留策略
class MetadataRetentionPolicy {
// 分享时的元数据保留策略
static readonly SHARE_POLICY = {
keep: ['Orientation', 'DateTime', 'Make', 'Model'],
remove: ['GPSLatitude', 'GPSLongitude', 'GPSAltitude',
'BodySerialNumber', 'LensSerialNumber',
'CameraOwnerName']
};
// 存档时的元数据保留策略
static readonly ARCHIVE_POLICY = {
keep: [], // 空数组表示保留所有
remove: []
};
// 应用策略
static async applyPolicy(
imageSource: image.ImageSource,
policy: { keep: string[]; remove: string[] }
): Promise<void> {
const editor = new ExifEditor(imageSource);
for (const tag of policy.remove) {
await editor.modifyExifTag(tag, '');
}
console.info(`Metadata retention policy applied.`);
}
}
总结
本文详细介绍了 HarmonyOS 6.1 Image Kit 的图片元数据处理能力,包括Exif信息(拍摄参数、GPS位置、方向信息)的读取和编辑,以及XMP元数据(作者、版权、描述)的处理。通过元数据管理,ImageEditor Pro 可以实现图片自动旋转、隐私保护、版权管理等高级功能。
下一篇文章,我们将探索 Image Kit 的高级特性——图片超分辨率与AI增强,使用VideoProcessingEngine提升图片画质。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
更多推荐




所有评论(0)