9

我目前正尝试像在视频游戏中一样以固定速率在屏幕上绘制图像。

不幸的是,由于图像移动的速率,一些帧是相同的,因为图像还没有移动一个完整的像素。

有没有办法为floatGraphics2D 提供值以在屏幕上绘制图像,而不是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

4 回答 4

9

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

    /* 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

于 2012-01-05T18:09:27.793 回答
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;
}

这应该不会使用额外的内存,但会花费额外的时间来渲染。

于 2011-12-30T19:42:08.600 回答
1

在做了类似劳伦塞兰提议的事情后,我成功地解决了我的问题。

最初,我有以下代码,在g调用方法之前将 where 转换为 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);

}

不过,这似乎是一种愚蠢的做法。

于 2014-11-27T08:33:38.170 回答
0

相应地更改图像的分辨率,没有像具有亚像素坐标的位图这样的东西,所以基本上你可以做的是创建一个比你想要渲染到屏幕上的更大的内存图像,但允许你“亚像素“ 准确性。

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

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

重采样

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

于 2011-12-30T07:03:35.293 回答