鸿蒙学习实战之路-PDF转换指定页面或指定区域为图片

鸿蒙学习实战之路-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)
  }
}

七、实用资源推荐

📚 推荐资料


我是盐焗西兰花,

不教理论,只给你能跑的代码和避坑指南。

下期见!🥦

相关推荐
Miguo94well2 小时前
Flutter框架跨平台鸿蒙开发——节日祝福语APP的开发流程
flutter·harmonyos·鸿蒙·节日
鄭郑2 小时前
【Playwright学习笔记 06】用户视觉定位的方法
笔记·学习
楼田莉子2 小时前
Linux系统小项目——“主从设计模式”进程池
linux·服务器·开发语言·c++·vscode·学习
Easonmax2 小时前
基础入门 React Native 鸿蒙跨平台开发:主页Tab导航完整实现(底部导航+页面切换+图标切换)
react native·harmonyos
云边散步2 小时前
godot2D游戏教程系列一(9)-终结
学习·游戏·游戏开发
jrlong2 小时前
DataWhale大模型基础与量化微调task4学习笔记(第 1章:参数高效微调_LoRA 方法详解)
笔记·学习
Easonmax2 小时前
【鸿蒙pc命令行适配】tig(git命令行可视化)工具移植实战:解决ncurses库依赖、terminfo终端适配与环境配置全流程
git·华为·harmonyos
aqi002 小时前
新书《鸿蒙HarmonyOS 6应用开发:从零基础到App上线》出版啦
harmonyos·鸿蒙·harmony
猛扇赵四那边好嘴.2 小时前
Flutter 框架跨平台鸿蒙开发 - 星座运势详解:探索星座奥秘
flutter·华为·harmonyos