在Canvas上绘制单个位图与多个位图的质量对比(Android)

3

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

image

从下面的代码中可以看出,画布还支持缩放。这个问题发生在小比例因子上。

问题是如何提高多个小图片的绘制质量,使其达到大图片的标准。

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

 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)。 - Blindman67
感谢您的评论。大位图是画布大小的2倍(绘制到最大缩放级别)。小图像是大位图的片段,尺寸较小,但其比例为2。因此,比例因子范围为0.5至1(表示缩放级别为2)。当比例大小为0.5时(即正常画布大小但图像的一半),问题就会出现。从右侧图像可以看出,在0.5级别下,线条受到了显着影响。您认为我是否需要根据缩放级别创建多个图像?或者所有小图像都应该是大图像的大小?这两种方法都有问题,因为在某些情况下,我有400个小图像。 - pats
1
我仔细查看了图像,发现较低的质量与双线性下采样产生的质量损失相匹配。你在 canvasPaint.setFilterBitmap(true); 上使用了双线性过滤,因此从程序上讲,你无法改善质量。由于锯齿的丢失只在高对比度线条上才能注意到,最好的选择是在位图上动态渲染拼图轮廓路径,从原始图像中删除线条。这将为您提供一致的线条质量。 - Blindman67
好主意。我试过了,但不能使用,因为性能损失很大。仅绘制400张图像就需要30毫秒(帧时间),但加上轮廓需要150毫秒。 - pats
图像必须一次性缩放。但是,如果您分别缩放每个部分,然后再将它们组合在一起,它们之间的边界看起来就不好看。应该在整体缩放后对其进行部分分离。 - from56
我正在使用SurfaceView画布及其scale()方法进行缩放。如果我在图像上绘制部分并将其绘制到画布上,则帧速率会下降。是否有一种方法可以稍后绘制部分并同时进行缩放以改善边框问题? - pats
1个回答

2
我可以想到几种方法,这取决于你如何将拼图块的边界绘制出来。
你遇到的问题是,当单个图像被缩放时,线条与图像的其余部分一起被过滤,看起来很平滑(混合是 正确的)。当拼图逐个绘制时,过滤器会读取拼图块上相邻的像素并将其与块混合。 方式 1 第一个方法(易于实现)是以游戏的逻辑大小呈现到 FBO(RTT),然后使用全屏四边形将整个纹理缩放到画布上。这将使像素混合涉及相邻拼图块,从而得到与 single 同样的结果。 方式 B 使用溢出来解决问题。当裁剪拼图块时,包括相邻拼图块的重叠部分。不要将丢弃的像素设置为零,而是将 alpha 设置为零。这将使混合函数捕获与放置在单个图像上相同的值。此外,加倍边框线,但将外部边框的 alpha 设置为零。 最后的方法 这最后一个方法是最复杂的,但可以在任何缩放比例下流畅(AF)运行。
将拼图块的 alpha 通道转换为有符号距离场,并使用专用着色器进行渲染,在任何距离下都能平滑输出。此外,SDF 允许在渲染过程中使用着色器绘制轮廓线,并且轮廓线也会很平滑。
实际上,您的 SDF 可以是一个单独的纹理,您可以将其加载到第二个纹理阶段。将源图像绑定为纹理单元 0,将 sdf 拼图块剪裁(s)绑定为纹理单元 1,并使用 SDF 着色器从 SDF 中确定 alpha 值,从 tex0 中获取颜色,然后混合计算出的轮廓线。

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着色器,以及上面列出的工具。

你是正确的。边框是主要问题。你可以解释一下第一种方法吗?这与我问题中解释的单图像方法相同吗?在一个大图像上绘制小图像,然后将大图像绘制在最终画布上? - pats
我尝试了第二种方法。这显著减少了问题。你知道如何有选择性地将alpha设置为零吗?我目前正在使用porterDuffPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC))来剪切图像的碎片。我可以使用什么技术来实现第二种方法? - pats
方法1虽然慢,但易于执行。不要直接绘制到屏幕上,而是绘制到FBO(渲染到纹理)。然后将渲染的纹理绘制到屏幕上。关于此有几篇Nehe教程。 - James Poag
关于PorterDuff,我花了一点时间研究了PorterDuff提供的方程以及ComposeShader,但是我必须说手动复制像素会更容易。你需要的是源图像颜色和拼图块的alpha值。没有任何PorterDuff模式有这个方程,我也无法想出如何编写一个自定义的ComposeShader来解决这个问题。在OpenGLES中,您可以分离alpha和颜色混合函数。一旦您弄清楚RenderTargets的工作原理,组合这些块将会更快。您甚至可以编写一个着色器来完成这个任务。 - James Poag
矩阵可以相乘以合成它们的效果。我得查一下,但你可能能够将一个矩阵保存到画布中,以便缩放/平移所有未来的绘图。https://developer.android.com/reference/android/graphics/Canvas.html#save() - James Poag
显示剩余2条评论

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