使用Graphics2D使用子像素级精度绘制图像

时间:2011-12-30 06:42:07

标签: java image graphics graphics2d subpixel

我目前正试图以正常速度在屏幕上绘制图像,就像在视频游戏中一样。

不幸的是,由于图像移动的速度,一些帧是相同的,因为图像尚未移动整个像素。

有没有办法为Graphics2D提供float值,以便在屏幕上绘制图像,而不是int值?

最初这是我所做的:

BufferedImage srcImage = sprite.getImage ( );
Position imagePosition = ... ; //Defined elsewhere
g.drawImage ( srcImage, (int) imagePosition.getX(), (int) imagePosition.getY() );

这当然是阈值,所以图片不会在像素之间移动,而是会从一个像素跳到另一个像素。

下一个方法是将绘画颜色设置为纹理,然后在指定位置绘制。不幸的是,这产生了不正确的结果,显示了平铺而不是正确的抗锯齿。

g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );

BufferedImage srcImage = sprite.getImage ( );

g.setPaint ( new TexturePaint ( srcImage, new Rectangle2D.Float ( 0, 0, srcImage.getWidth ( ), srcImage.getHeight ( ) ) ) );

AffineTransform xform = new AffineTransform ( );

xform.setToIdentity ( );
xform.translate ( onScreenPos.getX ( ), onScreenPos.getY ( ) );
g.transform ( xform );

g.fillRect(0, 0, srcImage.getWidth(), srcImage.getHeight());

如何在Java中实现图像的子像素渲染所需的效果?

4 个答案:

答案 0 :(得分:8)

您可以使用BufferedImageAffineTransform,绘制到缓冲的图像,然后将缓冲的图像绘制到绘图事件中的组件。

    /* overrides the paint method */
    @Override
    public void paint(Graphics g) {
        /* clear scene buffer */
        g2d.clearRect(0, 0, (int)width, (int)height);

        /* draw ball image to the memory image with transformed x/y double values */
        AffineTransform t = new AffineTransform();
        t.translate(ball.x, ball.y); // x/y set here, ball.x/y = double, ie: 10.33
        t.scale(1, 1); // scale = 1 
        g2d.drawImage(image, t, null);

        // draw the scene (double percision image) to the ui component
        g.drawImage(scene, 0, 0, this);
    }

点击此处查看完整示例:http://pastebin.com/hSAkYWqM

答案 1 :(得分:3)

您可以使用子像素精度自行合成图像,但您需要做的更多工作。简单的双线性插值应该适用于游戏。下面是伪造的C ++代码。

通常,要在位置(a,b)绘制精灵,你可以这样做:

for (x = a; x < a + sprite.width; x++)
{
    for (y = b; y < b + sprite.height; y++)
    {
        *dstPixel = alphaBlend (*dstPixel, *spritePixel);
        dstPixel++;
        spritePixel++;
    }
    dstPixel += destLineDiff; // Move to start of next destination line
    spritePixel += spriteLineDiff; // Move to start of next sprite line
}

要进行子像素渲染,您可以执行相同的循环,但会考虑子像素偏移,如下所示:

float xOffset = a - floor (a);
float yOffset = b - floor (b);
for (x = floor(a), spriteX = 0; x < floor(a) + sprite.width + 1; x++, spriteX++)
{
    for (y = floor(b), spriteY = 0; y < floor (b) + sprite.height + 1; y++, spriteY++)
    {
        spriteInterp = bilinearInterp (sprite, spriteX + xOffset, spriteY + yOffset);
        *dstPixel = alphaBlend (*dstPixel, spriteInterp);
        dstPixel++;
        spritePixel++;
    }
    dstPixel += destLineDiff; // Move to start of next destination line
    spritePixel += spriteLineDiff; // Move to start of next sprite line
}

bilinearInterp()函数看起来像这样:

Pixel bilinearInterp (Sprite* sprite, float x, float y)
{
    // Interpolate the upper row of pixels
    Pixel* topPtr = sprite->dataPtr + ((floor (y) + 1) * sprite->rowBytes) + floor(x) * sizeof (Pixel);
    Pixel* bottomPtr = sprite->dataPtr + (floor (y) * sprite->rowBytes) + floor (x) * sizeof (Pixel);

    float xOffset = x - floor (x);
    float yOffset = y - floor (y);

    Pixel top = *topPtr + ((*(topPtr + 1) - *topPtr) * xOffset;
    Pixel bottom = *bottomPtr + ((*(bottomPtr + 1) - *bottomPtr) * xOffset;
    return bottom + (top - bottom) * yOffset;
}

这应该不使用额外的内存,但需要额外的时间来渲染。

答案 2 :(得分:1)

在做了像劳伦斯兰提议之后,我成功地解决了我的问题。

最初,我有以下代码,其中g在调用方法之前转换为16:9坐标系:

private void drawStar(Graphics2D g, Star s) {

    double radius = s.getRadius();
    double x = s.getX() - radius;
    double y = s.getY() - radius;
    double width = radius*2;
    double height = radius*2;

    try {

        BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png"));
        g.drawImage(image, (int)x, (int)y, (int)width, (int)height, this);

    } catch (IOException ex) {
        Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex);
    }

}

然而,正如提问者Kaushik Shankar所指出的那样,将双重位置转换为整数会使图像“跳跃”,并将双维度转换为整数使其成为“跳跃”(为什么地狱会g.drawImage不接受双打?!)。我发现为我工作的是以下内容:

private void drawStar(Graphics2D g, Star s) {

    AffineTransform originalTransform = g.getTransform();

    double radius = s.getRadius();
    double x = s.getX() - radius;
    double y = s.getY() - radius;
    double width = radius*2;
    double height = radius*2;

    try {

        BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png"));

        g.translate(x, y);
        g.scale(width/image.getWidth(), height/image.getHeight());

        g.drawImage(image, 0, 0, this);

    } catch (IOException ex) {
        Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex);
    }

    g.setTransform(originalTransform);

}

虽然看起来像是一种愚蠢的做法。

答案 3 :(得分:0)

相应地改变图像的分辨率,不存在具有子像素坐标的位图,所以基本上你可以做的是创建一个大于你想要渲染到屏幕的内存图像,但允许你“子像素“准确度。

当您在内存中绘制较大的图像时,将其复制并重新采样到最终用户可见的较小渲染中。

例如:一张100x100图片,它是50x50调整大小/重新采样的对应物:

resampling

请参阅:http://en.wikipedia.org/wiki/Resampling_%28bitmap%29