安卓的PdfRenderer类生成低质量图像

25

我正在使用API 21及以上的PdfRenderer在我的应用程序中显示PDF,并且我注意到页面的质量非常差。

我还按照Google提供的示例使用PdfRenderer,以下是我为页面创建Bitmap的方式:

//mCurrentPage is a PdfRenderer.Page and mImageView is an ImageView
Bitmap bitmap = Bitmap.createBitmap(mCurrentPage.getWidth(), 
                    mCurrentPage.getHeight(),
                    Bitmap.Config.ARGB_8888);
mCurrentPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
mImageView.setImageBitmap(bitmap);

据我所知,ARGB_8888 是显示位图的最佳质量,因此我使用了它。这样做有什么问题吗?

编辑

PdfRenderer 类和传统的 Pdf 阅读器之间存在巨大的差异:

enter image description here enter image description here


当您缩放或切换焦点时,PDF 的质量是否会改变? - Sipty
1
@Sipty 不,它没有。 - Giorgio Antonioli
这个类的限制感觉真是让人害怕!我建议你使用AA技术,再加上其他一些方法,来提高位图的质量。如果这样还不行,那么下一步最好的选择可能就是使用外部库了。我猜测Android开发人员可能没有使用亚像素渲染,或者是特定字体的问题。市面上有一些非常棒的库,可以为你提供大量额外的有用选项。 - Sipty
1
@Sipty,实际上我正在使用一个外部库,在API 21以下的版本中。但是,考虑到Android SDK在API 21以上提供了这个类,我尝试着去实现它。 - Giorgio Antonioli
哦,哇,差别是... 哎呀。你能否提供你正在使用的库的链接,以备将来参考? - Sipty
1
@Sipty https://github.com/JoanZapata/android-pdfview 但是截图来自Adobe Reader。 - Giorgio Antonioli
3个回答

58

`ARGB_8888` 只是用于颜色质量,而打印/显示质量与分辨率有关(在屏幕上显示多少每英寸的点数)。

例如,如果您有一个400 DPI屏幕(每英寸400个点),想要以此质量显示PDF,则应通过以像素作为其大小的 Bitmap.createBitmap() 渲染位图:

Bitmap bitmap = Bitmap.createBitmap(
    getResources().getDisplayMetrics().densityDpi * mCurrentPage.getWidth() / 72,                        
    getResources().getDisplayMetrics().densityDpi * mCurrentPage.getHeight() / 72,
    Bitmap.Config.ARGB_8888
);

其中:

  1. getResources().getDisplayMetrics().densityDpi 是目标 DPI 分辨率。
  2. mCurrentPage.getWidth() 返回以 Postscript 点为单位的宽度,每个 pt 等于 1/72 英寸。
  3. 72(DPI)是默认的 PDF 分辨率。

因此,将 #2 除以 72,我们得到英寸,再乘上 DPI,我们就得到像素。换句话说,为了匹配显示设备的打印质量,您应该增加呈现图像的尺寸,因为默认的 PDF 分辨率是 72 DPI。请同时查看此帖子


1
我明白了,谢谢你的回答,帮了我很多! - Giorgio Antonioli
1
Eugene M - 很棒的回答。我有一个问题。现在我提高了质量,但应用程序变得非常缓慢。我猜它正在耗尽内存。你知道解决方案吗?谢谢。 - Thiago
3
注意不要使用这个 densityDpi / 72 - 我曾经使用过,结果在新的高 DPI 设备上会导致 OutOfMemory 错误。所以现在,我只是将我的高度和宽度乘以2来获得更清晰的图像,效果还不错。 - Anthony Chuinard
4
这将生成一个位图,足以在与PDF页面相同尺寸的设备上实现全分辨率。由于大多数设备要小得多,所需的内存远远超过需要的内存。对于我的420 DPI Nexus 5x上的8.5"x11" PDF,这将生成一个69 MB的位图。改用getResources().getDisplayMetrics().widthPixels/heightPixels(或按照blae的下面的答案进行缩放)可以给我一个6.5 MB的位图,内存使用量不到原来的10%,这已经是我的设备可以呈现的最佳位图了。唯一需要更高分辨率的情况是当用户需要对特定部分进行缩放时。 - Oded
1
在一些具有大屏幕但中等/低DPI(如平板电脑)的设备上,渲染的位图可能不足以填充整个屏幕。例如,在Nexus 9上(1536x2048和320 dpi),此代码生成的位图仅具有1133x1634的分辨率。按比例增加这些尺寸以达到精确所需像素将是更明智的解决方案。 - soshial
显示剩余5条评论

7

我有点晚了。标记的答案没有帮助到我,因为位图太大,无法渲染。我发现原始帖子是因为我遇到了完全相同的问题,也许这个答案会帮助其他人。

我搜索了一下,看看是否有一个简洁的解决方案来缩放位图,同时保持比例。我找到了这个网站:

https://guides.codepath.com/android/Working-with-the-ImageView#scaling-a-bitmap

这不是什么复杂的东西, 我只需要添加两行代码:

int height = DeviceDimensionsHelper.getDisplayHeight(this);
Bitmap scaledBitmap = BitmapUtil.scaleToFitHeight(bitmap, height);

我按照网站建议添加了类似的帮助程序/实用程序类(因此实际上不止两行 ;))。高度来自于


context.getResources().getDisplayMetrics().widthPixels;

缩放只是将设备高度除以原始位图高度来获取一个因子,然后将其乘以原始位图宽度以找到缩放后的宽度。在我的Nexus 5上这个方法非常完美。我还没有能够在多个设备上测试它,但它看起来是一个稳定的解决方案。

不是万能的解决方案,但比被接受的答案更好。 - farhan patel
"DeviceDimensionsHelper.getDisplayHeight" 这是手机显示屏的高度,如果有人正在使用ImageView渲染,那么它的高度是多少? - Himanshu Mori

1

PdfRenderer一样,您将在ImageView中显示PDF页面,最简单的方法是创建一个位图,其最大大小为您的ImageView,并添加一些数学运算以保留PDF的比例。由于PDF通常用于打印,因此它们的分辨率始终高于屏幕,因此您不会冒着获得像素化图像的风险。为了确保ImageView已分配其measuredWidthmeasuredHeight,请在代码中使用ImageView::post()(请原谅我的Kotlin):

    pdfPageImageView.post {
        val viewWidth = pdfPageImageView.measuredWidth
        val viewHeight = pdfPageImageView.measuredHeight
        val pageWidth = page.width
        val pageHeight = page.height
        val pageRatio = 1.0 * pageWidth / pageHeight
        val viewRatio = 1.0 * viewWidth / viewHeight
        val bitmapWidth: Int
        val bitmapHeight: Int
        if (pageRatio > viewRatio) {
            bitmapWidth = viewWidth
            bitmapHeight = (viewWidth / pageRatio).toInt()
        } else {
            bitmapHeight = viewHeight
            bitmapWidth = (viewHeight * pageRatio).toInt()
        }
        val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
        page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
        pdfPageImageView.setImageBitmap(bitmap)
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接