鸿蒙学习实战之路-PDF转图片完全指南
最近好多朋友问我:"西兰花啊,我想把PDF页面转成图片,咋搞?有时候只想转指定区域,不想转整个页面,这能行吗?" 害,这问题我太熟了!今天我就手把手带你搞定PDF转图片的各种操作,从整个页面到指定区域,全部给你安排得明明白白~
一、PDF转图片,核心接口有哪些?
PDF Kit提供了四个核心接口来实现PDF转图片:
| 接口名 | 功能描述 |
|---|---|
| getPagePixelMap | 获取当前页的完整图片 |
| getCustomPagePixelMap | 获取指定PdfPage区域的图片内容 |
| getAreaPixelMap | 获取指定PdfPage区域的图片内容,并指定图片的宽和高 |
| getAreaPixelMapWithOptions | 获取指定PdfPage区域的图片内容,并指定图片的宽和高等参数 |
这四个接口就是咱们今天要摆弄的"相机",用好了,PDF页面任你拍~
二、准备工作,先搭好灶台
在开始PDF转图片之前,咱们需要先准备好必要的模块导入:
ts
import { pdfService } from '@kit.PDFKit';
import { image } from '@kit.ImageKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
然后,咱们需要一个工具方法,把PixelMap转换成图片文件:
ts
// 将 pixelMap 转成图片格式
async pixelMap2Buffer(pixelMap: image.PixelMap): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
// 设置打包参数
let packOpts: image.PackingOption = {
format: 'image/jpeg', // 图片格式
quality: 98 // 图片质量
};
// 创建ImagePacker实例
const imagePackerApi = image.createImagePacker();
imagePackerApi.packToData(pixelMap, packOpts)
.then((buffer: ArrayBuffer) => {
resolve(buffer);
})
.catch((err: BusinessError) => {
reject(err);
});
});
}
这个方法就像咱们的"照片冲洗机",把拍好的"底片"(PixelMap)变成可以保存的"照片"(图片文件)~
三、转换整个页面为图片
最简单的场景就是把整个PDF页面转成图片:
ts
Button('转换整个页面')
.onClick(async () => {
// 确保PDF文档加载成功
if (this.loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
// 获取要转换的页面(这里是第一页,索引0)
let page = this.pdfDocument.getPage(0);
// 调用getPagePixelMap方法获取页面图片
let pixmap: image.PixelMap = page.getPagePixelMap();
if (!pixmap) {
console.error('获取图片失败');
return;
}
// 将PixelMap转换为图片缓冲区
const imgBuffer = await this.pixelMap2Buffer(pixmap);
try {
// 保存图片到应用沙箱
const file = fs.openSync(
this.context.filesDir + `/${Date.now()}.png`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
await fs.write(file.fd, imgBuffer);
// 关闭文件
await fs.close(file.fd);
console.log('图片保存成功');
} catch (e) {
let error: BusinessError = e as BusinessError;
console.error(`保存图片失败: Code: ${error.code}, message: ${error.message}`);
}
}
})
四、转换指定区域为图片
有时候咱们只需要PDF页面的某个部分,这时候就可以用指定区域转换的接口了~
1. 使用getCustomPagePixelMap
ts
Button('转换指定区域')
.onClick(async () => {
if (this.loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
// 获取要转换的页面
let page = this.pdfDocument.getPage(0);
// 创建矩阵对象,设置要转换的区域
let matrix = new pdfService.PdfMatrix();
matrix.x = 100; // 区域左上角x坐标
matrix.y = 100; // 区域左上角y坐标
matrix.width = 500; // 区域宽度
matrix.height = 500; // 区域高度
matrix.rotate = 0; // 旋转角度
// 调用getCustomPagePixelMap方法获取指定区域图片
// 参数:矩阵,是否灰度,是否绘制批注
let pixmap: image.PixelMap = page.getCustomPagePixelMap(matrix, false, false);
if (!pixmap) {
console.error('获取图片失败');
return;
}
// 将PixelMap转换为图片缓冲区
const imgBuffer = await this.pixelMap2Buffer(pixmap);
try {
// 保存图片到应用沙箱
const file = fs.openSync(
this.context.filesDir + `/${Date.now()}.jpeg`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
await fs.write(file.fd, imgBuffer);
// 关闭文件
await fs.close(file.fd);
console.log('图片保存成功');
} catch (e) {
let error: BusinessError = e as BusinessError;
console.error(`保存图片失败: Code: ${error.code}, message: ${error.message}`);
}
}
})
2. 使用getAreaPixelMap
ts
Button('转换指定区域(自定义尺寸)')
.onClick(async () => {
if (this.loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
// 获取要转换的页面
let page = this.pdfDocument.getPage(0);
// 创建矩阵对象,设置要转换的区域
let matrix = new pdfService.PdfMatrix();
matrix.x = 100;
matrix.y = 100;
matrix.width = 500;
matrix.height = 500;
matrix.rotate = 0;
// 调用getAreaPixelMap方法获取指定区域图片
// 参数:矩阵,输出图片宽度,输出图片高度,是否灰度,是否绘制批注
let pixmap: image.PixelMap = page.getAreaPixelMap(matrix, 400, 400, true, false);
if (!pixmap) {
console.error('获取图片失败');
return;
}
// 将PixelMap转换为图片缓冲区
const imgBuffer = await this.pixelMap2Buffer(pixmap);
try {
// 保存图片到应用沙箱
const file = fs.openSync(
this.context.filesDir + `/${Date.now()}.bmp`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
await fs.write(file.fd, imgBuffer);
// 关闭文件
await fs.close(file.fd);
console.log('图片保存成功');
} catch (e) {
let error: BusinessError = e as BusinessError;
console.error(`保存图片失败: Code: ${error.code}, message: ${error.message}`);
}
}
})
3. 使用getAreaPixelMapWithOptions
这个接口最强大,可以设置更多参数:
ts
Button('转换指定区域(高级选项)')
.onClick(async () => {
if (this.loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
// 获取要转换的页面
let page = this.pdfDocument.getPage(0);
// 创建矩阵对象,设置要转换的区域
let matrix = new pdfService.PdfMatrix();
matrix.x = 100;
matrix.y = 100;
matrix.width = 500;
matrix.height = 500;
matrix.rotate = 0;
// 设置高级选项
let options = new pdfService.PixelOptions();
options.isGray = false; // 是否灰度
options.drawAnnotations = true; // 是否绘制批注
options.isTransparent = true; // 背景是否透明
// 调用getAreaPixelMapWithOptions方法获取指定区域图片
// 参数:矩阵,输出图片宽度,输出图片高度,选项
let pixmap: image.PixelMap = page.getAreaPixelMapWithOptions(matrix, 400, 400, options);
if (!pixmap) {
console.error('获取图片失败');
return;
}
// 将PixelMap转换为图片缓冲区
const imgBuffer = await this.pixelMap2Buffer(pixmap);
try {
// 保存图片到应用沙箱
const file = fs.openSync(
this.context.filesDir + `/${Date.now()}.bmp`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
await fs.write(file.fd, imgBuffer);
// 关闭文件
await fs.close(file.fd);
console.log('图片保存成功');
} catch (e) {
let error: BusinessError = e as BusinessError;
console.error(`保存图片失败: Code: ${error.code}, message: ${error.message}`);
}
}
})
🥦 西兰花警告
- 内存管理:转换大页面或高分辨率图片时,会占用大量内存,记得及时释放PixelMap资源!
- 坐标系统:PDF的坐标系统原点在左下角,和咱们平时用的屏幕坐标不太一样,别搞反了!
- 文件路径:保存图片时,要用应用沙箱路径,别直接写外部存储路径!
- 异常处理:所有操作都要包在try-catch里,避免文件不存在、格式错误等情况导致应用崩溃!
- 图片格式:不同的图片格式有不同的特点,JPEG适合照片,PNG适合有透明背景的图片,BMP质量高但文件大,根据需要选择~
🥦 西兰花小贴士
- 图片质量:调整packOpts.quality可以控制图片质量和文件大小,质量越高文件越大
- 图片尺寸:使用getAreaPixelMap或getAreaPixelMapWithOptions可以指定输出图片的尺寸
- 批量转换:如果要转换多个页面,可以用循环,一个页面一个页面地转换
- 性能优化:处理大量页面时,建议使用异步方式,避免阻塞UI线程
- 区域选择:如果不确定要转换的区域坐标,可以先转换整个页面,然后根据需要计算区域坐标
五、常见问题与解决方案
1. 转换后图片空白
问题:调用getPagePixelMap后,获取到的PixelMap是空白的
解决方案:检查PDF文档是否加载成功,页面索引是否正确:
ts
// 正确写法
if (this.loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
let pageCount = this.pdfDocument.getPageCount();
if (pageCount > 0) {
let page = this.pdfDocument.getPage(0);
// 后续操作...
}
}
2. 保存图片时提示"权限不足"
问题:保存图片到外部存储时,提示"Permission denied"
解决方案:改用应用沙箱路径:
ts
// 正确写法
const file = fs.openSync(
this.context.filesDir + `/${Date.now()}.png`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
3. 转换指定区域时位置不对
问题:设置的坐标看起来是对的,但转换出来的区域却不对
解决方案:记住PDF的坐标系统原点在左下角,不是左上角:
ts
// 调整坐标到页面上半部分
let pageHeight = page.getHeight();
matrix.y = pageHeight - 600; // 距离顶部100像素
matrix.height = 500;
六、完整代码示例
ts
import { pdfService } from '@kit.PDFKit';
import { image } from '@kit.ImageKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
@Entry
@Component
struct PdfPage {
private pdfDocument: pdfService.PdfDocument = new pdfService.PdfDocument();
private context = this.getUIContext().getHostContext() as Context;
private loadResult: pdfService.ParseResult = pdfService.ParseResult.PARSE_ERROR_FORMAT;
aboutToAppear(): void {
// 确保沙箱目录有input.pdf文档
let filePath = this.context.filesDir + '/input.pdf';
this.loadResult = this.pdfDocument.loadDocument(filePath);
}
// 将 pixelMap 转成图片格式
async pixelMap2Buffer(pixelMap: image.PixelMap): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
// 设置打包参数
let packOpts: image.PackingOption = {
format: 'image/jpeg',
quality: 98
};
// 创建ImagePacker实例
const imagePackerApi = image.createImagePacker();
imagePackerApi.packToData(pixelMap, packOpts)
.then((buffer: ArrayBuffer) => {
resolve(buffer);
})
.catch((err: BusinessError) => {
reject(err);
});
});
}
build() {
Column() {
// 转换整个页面
Button('转换整个页面')
.margin(10)
.onClick(async () => {
try {
if (this.loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
let page = this.pdfDocument.getPage(0);
let pixmap: image.PixelMap = page.getPagePixelMap();
if (!pixmap) {
console.error('获取图片失败');
return;
}
const imgBuffer = await this.pixelMap2Buffer(pixmap);
const file = fs.openSync(
this.context.filesDir + `/${Date.now()}.png`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
await fs.write(file.fd, imgBuffer);
await fs.close(file.fd);
console.log('图片保存成功');
}
} catch (e) {
let error: BusinessError = e as BusinessError;
console.error(`保存图片失败: Code: ${error.code}, message: ${error.message}`);
}
})
// 转换指定区域
Button('转换指定区域')
.margin(10)
.onClick(async () => {
try {
if (this.loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
let page = this.pdfDocument.getPage(0);
let matrix = new pdfService.PdfMatrix();
matrix.x = 100;
matrix.y = 100;
matrix.width = 500;
matrix.height = 500;
matrix.rotate = 0;
let pixmap: image.PixelMap = page.getCustomPagePixelMap(matrix, false, false);
if (!pixmap) {
console.error('获取图片失败');
return;
}
const imgBuffer = await this.pixelMap2Buffer(pixmap);
const file = fs.openSync(
this.context.filesDir + `/${Date.now()}.jpeg`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
await fs.write(file.fd, imgBuffer);
await fs.close(file.fd);
console.log('图片保存成功');
}
} catch (e) {
let error: BusinessError = e as BusinessError;
console.error(`保存图片失败: Code: ${error.code}, message: ${error.message}`);
}
})
// 转换指定区域(自定义尺寸)
Button('转换指定区域(自定义尺寸)')
.margin(10)
.onClick(async () => {
try {
if (this.loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
let page = this.pdfDocument.getPage(0);
let matrix = new pdfService.PdfMatrix();
matrix.x = 100;
matrix.y = 100;
matrix.width = 500;
matrix.height = 500;
matrix.rotate = 0;
let pixmap: image.PixelMap = page.getAreaPixelMap(matrix, 400, 400, true, false);
if (!pixmap) {
console.error('获取图片失败');
return;
}
const imgBuffer = await this.pixelMap2Buffer(pixmap);
const file = fs.openSync(
this.context.filesDir + `/${Date.now()}.bmp`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
await fs.write(file.fd, imgBuffer);
await fs.close(file.fd);
console.log('图片保存成功');
}
} catch (e) {
let error: BusinessError = e as BusinessError;
console.error(`保存图片失败: Code: ${error.code}, message: ${error.message}`);
}
})
// 转换指定区域(高级选项)
Button('转换指定区域(高级选项)')
.margin(10)
.onClick(async () => {
try {
if (this.loadResult === pdfService.ParseResult.PARSE_SUCCESS) {
let page = this.pdfDocument.getPage(0);
let matrix = new pdfService.PdfMatrix();
matrix.x = 100;
matrix.y = 100;
matrix.width = 500;
matrix.height = 500;
matrix.rotate = 0;
let options = new pdfService.PixelOptions();
options.isGray = false;
options.drawAnnotations = true;
options.isTransparent = true;
let pixmap: image.PixelMap = page.getAreaPixelMapWithOptions(matrix, 400, 400, options);
if (!pixmap) {
console.error('获取图片失败');
return;
}
const imgBuffer = await this.pixelMap2Buffer(pixmap);
const file = fs.openSync(
this.context.filesDir + `/${Date.now()}.bmp`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
await fs.write(file.fd, imgBuffer);
await fs.close(file.fd);
console.log('图片保存成功');
}
} catch (e) {
let error: BusinessError = e as BusinessError;
console.error(`保存图片失败: Code: ${error.code}, message: ${error.message}`);
}
})
}
.padding(20)
}
}
七、实用资源推荐
📚 推荐资料:
我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦