旋转 BufferedImage 时如何产生清晰的绘制结果?

Posted

技术标签:

【中文标题】旋转 BufferedImage 时如何产生清晰的绘制结果?【英文标题】:How can you produce sharp paint results when rotating a BufferedImage? 【发布时间】:2011-10-07 03:00:46 【问题描述】:

一种尝试的方法是使用TexturePaintg.fillRect() 来绘制图像。但是,这需要您在每次绘制图像时创建一个新的 TexturePaint 和 Rectangle2D 对象,这并不理想 - 无论如何也无济于事。

当我使用g.drawImage(BufferedImage,...) 时,旋转后的图像显得模糊/柔和。

我熟悉 RenderingHints 和双缓冲(我想这就是我正在做的事情),我只是觉得很难相信您不能轻松有效地旋转在 Java 中生成清晰结果的图像。

使用TexturePaint 的代码如下所示。

Grahics2D g2d = (Graphics2D)g; 
g2d.setPaint(new TexturePaint(bufferedImage, new Rectangle2D.Float(0,0,50,50)));
g2d.fillRect(0,0,50,50);

我正在使用AffineTransform 将手牌旋转成扇形。 快速绘制漂亮图像的最佳方法是什么?

这是截图:9 很清晰,但其余的牌肯定没有那么清晰。

问题可能出在我创建每个卡片图像并将其存储在数组中时。 这是我目前的做法:

// i from 0 to 52, card codes.
...
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
BufferedImage img = gc.createCompatibleImage(86, 126, Transparency.TRANSLUCENT);

    Graphics2D g = img.createGraphics();
    setRenderingHints(g);
    g.drawImage(shadow, 0, 0, 86, 126, null);
    g.drawImage(white, 3, 3, 80, 120, null);
    g.drawImage(suit, 3, 3, 80, 120, null);
    g.drawImage(value, 3, 3, 80, 120, null);
    g.dispose();

    cardImages[i] = img;


private void setRenderingHints(Graphics2D g)
    g.setRenderingHint(KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g.setRenderingHint(KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);

我应该如何以不同的方式处理这个问题? 谢谢。

编辑:没有渲染提示

设置 AA 提示没有任何区别。此外,在创建图像时设置 RenderingHints 也没有什么区别。只有当它们使用AffineTransform 旋转并使用g.drawImage(...) 绘制时,它们似乎才会模糊。 上图显示了默认(最近邻)和双线性插值之间的区别。

这是我目前绘制它们的方式(比 TexturePaint 快得多):

// GamePanel.java
private void paintCard(Graphics2D g, int code, int x, int y)
    g.drawImage(imageLoader.getCard(code), x, y, 86, 126, null);
  

// ImageLoader.java
public BufferedImage getCard(int code)
    return cardImages[code];
  

我所有的卡片都是80x120,阴影.png是86x126,这样在卡片周围留下3px的半透明阴影。我知道这不是一个真实的阴影,但看起来还不错。

所以问题就变成了...... 如何在旋转 BufferedImage 时产生清晰的绘制结果?

参考上一个关于扇形牌手的问题:How can you detect a mouse-click event on an Image object in Java?

赏金编辑: 好的,经过多次讨论,我制作了一些测试 .svg 卡,看看 SVG Salamander 将如何渲染它们。不幸的是,性能很糟糕。我的实现足够干净,看到双缓冲 BufferedImage 的绘画速度非常快。这意味着我已经完成了一个完整的循环,我又回到了原来的问题。

我会将 50 赏金奖励给任何能给我提供解决方案来获得锐利 BufferedImage 旋转的人。建议在绘画之前使图像大于所需大小并缩小比例,并使用双三次插值。如果这些是唯一可能的解决方案,那么我真的不知道从哪里开始,我可能只需要处理模糊的旋转 - 因为这两者都会造成性能挫折。

如果我能找到办法做好这件事,我就能完成我的游戏。 谢谢大家。 :)

【问题讨论】:

我看不到你在哪里设置RenderingHints @Moonbeam:RenderingHints 对图像没有帮助,而仅对我们绘制的形状有帮助。如果您查看 API,它指出:“ANTIALIASING 提示控制 Graphics2D 对象的几何渲染方法是否会尝试减少沿形状边缘的锯齿伪影。”所以他是对的甚至不要在这里尝试使用它们。 @Rudi:如果你在内存图像中保持更大的尺寸,并在可视化之前旋转后缩小尺寸怎么办? @paranoid-android 将图像旋转任意角度意味着破坏数据;图像越大,丢失的数据越少,因此如果您负担不起切换到矢量图形,它可能是唯一的选择 @paranoid-android 因为我只给出了一个提示而没有实现,如果你用一些示例代码回答自己并接受它会更好。请注意,我没有建议放大(这是有损的),只是为了在可视化之前保持更大的图像并缩小 【参考方案1】:

旋转光栅化图像(例如 BufferedImage)时,会丢失数据。最好的解决方案是将图像保存得比您需要的大,并在绘制时动态缩小。我发现你需要的 1.5 倍大小是一个很好的起点。

然后,当您绘制图像时,动态调整大小:

g.drawImage(bufferedImage, x, y, desiredWidth, desiredHeight, observer);

推荐使用双线性插值进行旋转。

建议归功于 guido。

【讨论】:

【参考方案2】:

这个建议在你的设计中可能有点晚了,但可能值得一提。

如果大量旋转和动画是 UI 的一部分,那么光栅化图像可能是不适合使用的技术;特别是对于具有大量曲线的复杂图像。等到你尝试缩放你的画布。我可能会建议查看基于矢量的图形库。它们将呈现您想要的各种效果,而产生伪影的可能性较小。

http://xmlgraphics.apache.org/batik/using/swing.htm

【讨论】:

感谢您指出这一点。下次我会考虑这种方法。事实上,我的游戏是可扩展的,所有的宽度、高度和位置都与面板大小相关。我已经检查了它们,并且正在以正确的尺寸绘制卡片。在上面的示例中,我只是对尺寸进行了硬编码,以适应默认面板尺寸 1280x720。 @paranoid:我想我在某处看到过公共领域的 SVG 图像打牌,所以已经有合适的图像可供您使用。让我看看能不能再找到它们。 编辑: 这里有一些:svg-cards @Hovercraft Full Of Eels:由于缺乏支持/可靠的答案,我将在路径上重新设计我的卡片组并导出为 SVG 文件,然后使用 Batik 进行转码/渲染。如果这种方法效果很好,我会接受你的建议作为答案。给我几天时间。 @paranoid:这不是我的建议,而是 mbarnes,但无论如何,是的,如果可行,请接受它。祝你好运! @mbarnes 我发现蜡染很难使用。我可能会就它的使用提出一个新问题。【参考方案3】:

AffineTransformOp 中设置插值类型以及抗锯齿值可能会带来一些改进。键入TYPE_BICUBIC,虽然速度较慢,但​​通常质量最好;概述了一个示例here。请注意,您可以提供多个RenderingHints。另一个陷阱来自于每次渲染图像时未能应用提示。您可能还需要调整背景的透明度,如here 建议的那样。最后,考虑创建一个sscce,其中包含您的一张实际图像。

【讨论】:

我今天将创建一个 sscce,因为 AffineTransformOp 似乎没有做任何不同的事情 - 假设我使用它正确。我在哪里以及如何发布/分享 sscce?谢谢。 您可以编辑您的问题,粘贴代码,选择它,单击 图标,然后保存编辑后的问题。还有一个图像图标。由于RenderingHints 的行为本质上是暗示性的,因此您的主机操作系统和Java 平台版本可能是相关的。 啊希望我所做的不会太远?见编辑。谢谢。 我发布了截图here。我看到抗锯齿或双三次插值有明显的好处,尽管平台可能会有所不同。 谢谢。我可以看到双三次也有轻微的改进,但是渲染太慢了,真的不值得。我可能会考虑在绘制之前将图像文件放大并缩小尺寸。

以上是关于旋转 BufferedImage 时如何产生清晰的绘制结果?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不产生 y 旋转抖动的情况下统一使用触摸输入来钳制游戏对象的 z 旋转

struts2产生验证码时, 绘图BufferedImage对象 已经弄好了,发送的用啥输出流呢?

Java BufferedImage 内存消耗

如何插值旋转?

(转)Java中Image的水平翻转缩放与自由旋转操作

使用 AffineTransform 旋转图像