You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何生成含文字与无失真图片的PDF?(Codename One实现)

解决Codename One手动构建PDF时的图片显示问题

先澄清一个误解

你看到的“类似SQLite数据库格式”其实是PDF对二进制数据的编码方式(通常是ASCII85或Base64),再加上PDF对象的结构化字典,并非SQLite格式。PDF图像数据的核心是对应格式的压缩二进制流,配合正确的过滤器声明。


针对JPG失真的解决方法

JPG失真的核心是二次压缩——如果你把JPG解码成Image对象再重新编码写入PDF,就会损失画质。正确的做法是直接复用JPG的原始压缩数据:

  • 不要通过Codename One的Image类处理JPG,直接读取原始文件的字节流。
  • 在PDF图像XObject中指定/Filter /DCTDecode(JPG原生压缩过滤器),直接写入原始JPG字节。

示例代码:

// 读取原始JPG字节
InputStream jpgIn = FileSystemStorage.getInstance().openInputStream("/path/to/photo.jpg");
byte[] rawJpg = Util.readInputStream(jpgIn);
jpgIn.close();

// 构建PDF图像对象(假设你有自定义的PDF对象处理类)
PdfXObject imgObj = new PdfXObject();
imgObj.setType("/XObject");
imgObj.setSubtype("/Image");
imgObj.put("/Width", 1200); // 替换为实际图片宽度
imgObj.put("/Height", 800); // 替换为实际图片高度
imgObj.put("/BitsPerComponent", 8);
imgObj.put("/ColorSpace", "/DeviceRGB");
imgObj.put("/Filter", "/DCTDecode");
imgObj.setRawData(rawJpg); // 直接写入原始JPG字节

// 在PDF页面中引用该图像
PdfPage currentPage = pdfDoc.getActivePage();
currentPage.getContentStream().append("q 0.5 0 0 0.5 50 50 cm /Img1 Do Q"); 
// 0.5是缩放比例,50,50是左下角坐标,/Img1是图像对象的名称

针对PNG不显示的解决方法

PNG不显示通常是因为透明通道处理不当,或者PDF未识别PNG的颜色模式。解决步骤:

  1. 若PNG是索引色模式,先转成RGB/A格式(Codename One的Image.createImage()会自动处理,但要确保获取完整的RGBA数据)。
  2. PDF支持透明需要通过/SMask(软蒙版)单独存储Alpha通道,不能直接把RGBA数据塞进普通图像对象。

示例代码:

// 读取PNG并获取像素数据
Image pngImg = Image.createImage("/path/to/icon.png");
int width = pngImg.getWidth();
int height = pngImg.getHeight();
int[] pixels = pngImg.getRGB();

// 1. 构建RGB像素字节流(PDF图像坐标是从下到上,所以反转y轴)
ByteArrayOutputStream rgbStream = new ByteArrayOutputStream();
for (int y = height - 1; y >= 0; y--) {
    for (int x = 0; x < width; x++) {
        int pixel = pixels[y * width + x];
        rgbStream.write((pixel >> 16) & 0xFF); // R
        rgbStream.write((pixel >> 8) & 0xFF);  // G
        rgbStream.write(pixel & 0xFF);         // B
    }
}
byte[] rgbData = rgbStream.toByteArray();
rgbStream.close();

// 2. 构建Alpha通道字节流(用于SMask)
ByteArrayOutputStream alphaStream = new ByteArrayOutputStream();
for (int y = height - 1; y >= 0; y--) {
    for (int x = 0; x < width; x++) {
        int pixel = pixels[y * width + x];
        alphaStream.write((pixel >> 24) & 0xFF); // Alpha值
    }
}
byte[] alphaData = alphaStream.toByteArray();
alphaStream.close();

// 3. 压缩RGB和Alpha数据(PDF用FlateDecode,即ZLIB压缩)
byte[] compressedRGB = compressData(rgbData);
byte[] compressedAlpha = compressData(alphaData);

// 4. 创建SMask对象(存储Alpha通道)
PdfXObject smaskObj = new PdfXObject();
smaskObj.setType("/XObject");
smaskObj.setSubtype("/Image");
smaskObj.put("/Width", width);
smaskObj.put("/Height", height);
smaskObj.put("/BitsPerComponent", 8);
smaskObj.put("/ColorSpace", "/DeviceGray");
smaskObj.put("/Filter", "/FlateDecode");
smaskObj.setRawData(compressedAlpha);

// 5. 创建主图像对象并关联SMask
PdfXObject imgObj = new PdfXObject();
imgObj.setType("/XObject");
imgObj.setSubtype("/Image");
imgObj.put("/Width", width);
imgObj.put("/Height", height);
imgObj.put("/BitsPerComponent", 8);
imgObj.put("/ColorSpace", "/DeviceRGB");
imgObj.put("/Filter", "/FlateDecode");
imgObj.put("/SMask", smaskObj.getReference()); // 关联蒙版
imgObj.setRawData(compressedRGB);

// 辅助压缩方法
private byte[] compressData(byte[] raw) throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    DeflaterOutputStream deflater = new DeflaterOutputStream(out);
    deflater.write(raw);
    deflater.close();
    return out.toByteArray();
}

两种实现方式的问题排查

  • StringBuilder拼接:绝对不能直接把二进制字节转成字符串(会丢失编码),必须先把二进制数据用ASCII85或Base64编码后再拼入字符串。
  • 直接写入流:要确保PDF对象的字典属性完整(Width、Height、Filter等缺一不可),流的写入顺序要符合PDF的文件结构(先写对象内容,再更新交叉引用表)。

正确提取插入图片二进制的核心原则

  1. 尽量复用原始压缩数据:JPG用DCTDecode直接写入原始字节,PNG的IDAT块数据可以直接用FlateDecode写入(无需解压缩再压缩)。
  2. 匹配PDF的图像规范:根据图片类型设置对应的Filter和ColorSpace,透明PNG必须添加SMask。
  3. 避免不必要的编码转换:不要把二进制数据转成普通字符串,要么用ASCII85/Base64编码,要么直接写入二进制流。

内容的提问来源于stack exchange,提问作者Eric

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 23:35:18