Android Studio中如何减小生成的PDF文件大小
问题原因
你当前代码生成PDF体积过大的核心原因是直接将全分辨率原始Bitmap绘制到PDF画布:Android原生PdfDocument默认会将传入的Bitmap以原始像素格式嵌入PDF文档,不会自动做分辨率适配、编码压缩;你设置的inScaled = false更是强制加载原图全尺寸数据,哪怕图片在PDF里只占几百pt的显示区域,实际嵌入的是原图几千像素的完整数据,再加上默认用ARGB_8888格式存储像素(每像素占4字节),体积自然居高不下。你之前用ColorMatrix转灰度只是修改了绘制颜色,并没有减少Bitmap本身的像素数据量,所以压缩效果有限。
可落地的优化方案
按优先级做以下修改,最终单页PDF体积可以控制在1~2MB以内,清晰度完全满足屏幕查看和普通打印需求:
- 第一步:按PDF实际显示尺寸下采样解码Bitmap,砍掉冗余像素
PDF默认采用72DPI的坐标体系,你的图片在A4页面(595842pt)里的最大显示尺寸只有436270pt,哪怕按150DPI的打印清晰度要求,需要的实际像素尺寸也仅为908*563px,完全不需要加载原图动辄几千像素的全尺寸数据。
解码图片时不要设置inScaled = false,先通过inJustDecodeBounds获取原图尺寸,计算合适的inSampleSize做下采样,只加载需要尺寸的Bitmap,这一步可以减少70%~90%的像素数据量。
参考代码:BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inJustDecodeBounds = true; BitmapFactory.decodeFile(img_Uri.getPath(), opt); // 按150DPI计算图片实际需要的像素尺寸,仅屏幕查看可把150f改成72f,体积更小 int targetWidth = (int) (436 * 150f / 72f); int targetHeight = (int) (270 * 150f / 72f); // 计算采样率 int sampleSize = 1; if (opt.outHeight > targetHeight || opt.outWidth > targetWidth) { int halfHeight = opt.outHeight / 2; int halfWidth = opt.outWidth / 2; while ((halfHeight / sampleSize) >= targetHeight && (halfWidth / sampleSize) >= targetWidth) { sampleSize *= 2; } } opt.inSampleSize = sampleSize; opt.inJustDecodeBounds = false; // 图片无透明通道,用RGB_565格式代替默认ARGB_8888,像素数据量直接减半 opt.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap bmp = BitmapFactory.decodeFile(img_Uri.getPath(), opt); - 第二步:提前把Bitmap缩放到绘制尺寸,不要靠Canvas做缩放
你当前代码通过myCanvas.scale()做缩放,PDF里实际嵌入的还是全尺寸的Bitmap数据,只是显示的时候缩放。应该在绘制前先把Bitmap缩放到和目标显示尺寸完全匹配的大小,再调用drawBitmap,保证PDF里嵌入的位图没有多余像素。
参考代码:// 先通过你原来的xy方法计算图片在PDF里的实际显示宽高和坐标 int[] xyImg = xy(bmp.getWidth(), bmp.getHeight(), 298, 175); // 直接把Bitmap缩放到最终显示的尺寸 Bitmap scaledBmp = Bitmap.createScaledBitmap(bmp, xyImg[0], xyImg[1], true); bmp.recycle(); - 第三步:灰度转换直接作用于缩放后的小尺寸Bitmap,减少运算量同时不额外占空间
你原来的ColorMatrix灰度逻辑可以保留,但要作用在缩放后的小尺寸Bitmap上,不要对全尺寸原图做处理。处理完直接用这个缩放后的灰度Bitmap绘制,不需要再给Canvas加缩放参数,直接按坐标绘制即可:PdfDocument.PageInfo myPageInfo = new PdfDocument.PageInfo.Builder(595, 842, 1).create(); PdfDocument.Page myPage = myPDFDoc.startPage(myPageInfo); Canvas myCanvas = myPage.getCanvas(); Paint paint = new Paint(); paint.setAntiAlias(true); // 直接给画笔加灰度滤镜 ColorMatrix cm = new ColorMatrix(); cm.setSaturation(0); paint.setColorFilter(new ColorMatrixColorFilter(cm)); // 直接按计算好的坐标绘制缩放后的位图,不需要Canvas做缩放 myCanvas.drawBitmap(scaledBmp, xyImg[2], xyImg[3], paint); myPDFDoc.finishPage(myPage); scaledBmp.recycle(); - 可选优化:进一步压缩位图质量
如果对清晰度要求不高,可以在绘制前把缩放后的Bitmap压缩成质量为60~80的JPEG字节流,再重新解码成Bitmap绘制,利用JPEG的有损压缩进一步减少像素数据的体积,肉眼基本看不出差异。
参考代码:ByteArrayOutputStream baos = new ByteArrayOutputStream(); scaledBmp.compress(Bitmap.CompressFormat.JPEG, 70, baos); Bitmap compressedBmp = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.size()); scaledBmp.recycle(); // 后续用compressedBmp绘制即可
优化效果
以上步骤全部做完后,单页带图PDF的体积基本可以稳定在1MB上下,如果用72DPI的屏幕适配标准,体积可以压到500KB以内,完全满足常规使用需求,不会出现之前十几二十MB的情况。
内容的提问来源于stack exchange,提问作者Dina
相关产品推荐
相关产品推荐

