【问题标题】:Draw quality of Single Bitmap vs Multiple Bitmaps on Canvas (Android)在 Canvas (Android) 上绘制单个位图与多个位图的质量
【发布时间】:2024-03-08 16:09:02
【问题描述】:

我有一组小图像。如果我在画布上单独绘制这些图像,与我在屏幕大小的大位图上绘制它们并在画布上绘制该位图的情况相比,绘制质量显着降低。特别是线条会失真。见下图(右侧)。

从下面的代码来看,画布也支持缩放(scaling)。此问题发生在小规模因素上。

问题是如何将多张小图的绘制量提高到大图的标准。

这是在画布上绘制的多个位图的代码

 canvas.scale(game.mScaleFactor, game.mScaleFactor);
 canvas.translate(game.mPosX, game.mPosY);

 for (int i = 0; i < game.clusters.size(); i++) {

                Cluster cluster = game.clusters.get(i);
                canvas.drawBitmap(cluster.Picture, cluster.left,
                            cluster.top, canvasPaint);

            }

这是单个位图的代码,game.board 是一个屏幕大小的图像,上面绘制了所有的小位图。

 canvas.scale(game.mScaleFactor, game.mScaleFactor);
 canvas.translate(game.mPosX, game.mPosY);

 canvas.drawBitmap(game.board, matrix, canvasPaint)

画笔设置了以下属性。`所有位图都是Bitmap.Config.ARGB_8888。

    canvasPaint.setAntiAlias(true);
    canvasPaint.setFilterBitmap(true);
    canvasPaint.setDither(true);`

【问题讨论】:

  • 将高分辨率图像渲染到固定尺寸的画布上意味着与使用较小尺寸的源图像相比,每个目标像素具有更多的源像素,结果是较大图像的下采样质量更好。要为较小的图像获得相同的结果,请从相同的高分辨率源以它们需要渲染的分辨率创建它们(即比例应为 1)
  • 感谢您的评论。大位图是画布大小的 2 倍(绘制到最大缩放级别)。小图像是大位图的片段,尺寸较小,但比例为 2。因此比例因子范围为 0.5 到 1(表示缩放级别为 2)。当比例大小为 0.5(即正常画布大小但图像的一半)时会出现此问题。形成右侧图像线在 0.5 级受到显着影响。你认为我必须根据缩放级别创建多个图像吗?还是所有小图像都应该是大图像的大小?鉴于在某些情况下我有 400 个小图像,这两种方法都有问题。
  • 我仔细查看了图像,较低的质量与双线性下采样产生的质量损失相匹配。您对canvasPaint.setFilterBitmap(true); 进行了双线性过滤,因此您无法通过任何编程方式来提高质量。由于锯齿的损失仅在高对比度线条上很明显,因此最好的选择是将拼图轮廓渲染为动态位图上的路径,从原始图像中删除线条这将为您提供一致的线条质量。跨度>
  • 好主意。我试过但不能使用,因为性能影响很大。仅绘制 400 张图像需要 30 毫秒(帧时间),但图像 + 轮廓需要 150 毫秒。
  • 图像必须一次全部缩放。但是,如果您分别缩放每个部分,然后将它们再次组合在一起,它们之间的边界将看起来不太好。部分的分离应该在整体缩放之后进行。

标签: android canvas bitmap zooming


【解决方案1】:

我能想到一对,取决于你画拼图的边界。

您遇到的问题是,当缩放单个图像时,线条会与图像的其余部分一起过滤,并且看起来很平滑(混合正确)。当拼图逐块绘制时,过滤器会读取拼图上的相邻像素并将它们与拼图混合。

方法 1

第一种方法(很容易做到)是以游戏的逻辑大小渲染到 FBO (RTT),然后使用全屏四边形将整个纹理缩放到画布上。这将得到与single 相同的结果,因为像素混合涉及相邻的部分。

方法 B

使用出血来解决问题。当您切割拼图时,包括相邻拼图的重叠部分。不要将丢弃的像素设置为零,只需将 alpha 设置为零。这将导致您的混合函数拾取与放置在单个图像上相同的值。此外,将边框线加倍,但将外部边框 alpha 设置为零。

接近决赛

最后一个是最复杂的,但对于任何缩放都将是平滑的 (AF)。

将拼图的 Alpha 通道转换为有符号距离场,并使用专用着色器进行渲染,该着色器可在任何距离处平滑输出。另外,SDF可以让你在渲染的时候用shader来绘制轮廓,轮廓会很平滑。

事实上,您的 SDF 可以是一个单独的纹理,您可以将其加载到第二个纹理阶段。将源图像绑定为 tex 单元 0,将 tex 单元 1 上的 sdf 拼图切口绑定,并使用 SDF 着色器确定来自 SDF 的 alpha 和来自 tex0 的颜色,然后混合根据 SDF 计算的轮廓。

http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf

https://github.com/Chlumsky/msdfgen

http://catlikecoding.com/sdf-toolkit/docs/texture-generator/

SDF 是从布尔映射生成的。您的拼图剪裁需要从单色剪裁开始,然后使用上面列出的工具或类似工具转换为 SDF(离线)。 Valve 和 LibGDX 有示例 SDF 着色器,以及上面列出的工具。

【讨论】:

  • 你是对的。边界是主要问题。你能解释一下方法1吗?这与我的问题中解释的单图像方法相同吗?在大图上画小图,在最终画布上画大图?
  • 我尝试了第二种方法。这大大减少了这个问题。你知道如何在像素上选择性地将 alpha 设置为零。我目前正在使用 porterDuffPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));切割碎片的图像。我可以使用什么技术来实现第二种方法。
  • 方法 #1 很慢,但很容易执行。而不是直接绘制到屏幕上,而是绘制到 FBO(渲染到纹理)。然后将渲染的纹理绘制到屏幕上。有几个关于这方面的 Nehe 教程。
  • 至于 PorterDuff,我花了一点时间查看 PorterDuff 以及 ComposeShader 提供的方程,我不得不说手动复制像素会更容易。您想要的是源图像颜色和拼图 alpha。所有的 PorterDuff 模式都没有这个方程,我不知道如何编写一个自定义的 ComposeShader 来满足这个问题。在 OpenGLES 中,您可以分离 alpha 和颜色混合函数。一旦你弄清楚 RenderTargets 是如何工作的,组合这些片段会更快。您甚至可以编写一个着色器来执行此操作。
  • 看起来 Render-To-Texture 是 OpenGL。我没有使用 OpenGL。 OpenGL 的性能低于画布上的多图像方法。我将尝试第二种方法并报告结果。顺便说一句,有一种方法可以在不使用 canvas.scale 的情况下实现缩放。如果我使用*.com/questions/35678175/… 绘制位图,绘制质量还可以。那么我可以用它来实现缩放和平移其他方式,将画布比例保持在 1。
最近更新 更多