使用 Graphics2D 以亚像素级精度绘制图像

发布于 2024-12-23 21:58:44 字数 1056 浏览 2 评论 0原文

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

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

有没有办法为 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中图像的亚像素渲染的预期效果?

I am currently attempting to draw images on the screen at a regular rate like in a video game.

Unfortunately, because of the rate at which the image is moving, some frames are identical because the image has not yet moved a full pixel.

Is there a way to provide float values to Graphics2D for on-screen position to draw the image, rather than int values?

Initially here is what I had done:

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

This of course thresholds, so the picture doesn't move between pixels, but skips from one to the next.

The next method was to set the paint color to a texture instead and draw at a specified position. Unfortunately, this produced incorrect results that showed tiling rather than correct antialiasing.

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());

What should I do to achieve the desired effect of subpixel rendering of an Image in Java?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

我最亲爱的 2024-12-30 21:58:44

您可以使用 BufferedImage 和 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

You can use a BufferedImage and AffineTransform, draw to the buffered image, then draw the buffered image to the component in the paint event.

    /* 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);
    }

Check my full example here: http://pastebin.com/hSAkYWqM

赢得她心 2024-12-30 21:58:44

您可以使用亚像素精度自行合成图像,但这需要您做更多的工作。简单的双线性插值对于游戏来说应该足够好了。下面是执行此操作的伪 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;
}

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

You can composite the image yourself using sub-pixel accuracy, but it's more work on your part. Simple bilinear interpolation should work well enough for a game. Below is psuedo-C++ code for doing it.

Normally, to draw a sprite at location (a,b), you'd do something like this:

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
}

To do sub-pixel rendering, you do the same loop, but account for the sub-pixel offset like so:

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
}

The bilinearInterp() function would look something like this:

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;
}

This should use no additional memory, but will take additional time to render.

巨坚强 2024-12-30 21:58:44

在做了像劳伦斯兰提出的事情后,我成功解决了我的问题。

最初,我有以下代码,其中 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);

}

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

I successfully solved my problem after doing something like lawrencealan proposed.

Originally, I had the following code, where g is transformed to a 16:9 coordinate system before the method is called:

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);
    }

}

However, as noted by the questioner Kaushik Shankar, turning the double positions into integers makes the image "jump" around, and turning the double dimensions into integers makes it scale "jumpy" (why the hell does g.drawImage not accept doubles?!). What I found working for me was the following:

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);

}

Seems like a stupid way of doing it though.

不醒的梦 2024-12-30 21:58:44

相应地更改图像的分辨率,不存在具有子像素坐标的位图之类的东西,因此基本上您可以做的就是创建一个比您想要渲染到屏幕上的图像更大的内存图像,但允许您“子像素” “ 准确性。

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

例如:100x100 的图像和 50x50 调整大小/重新采样的对应图像:

resampling

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

Change the resolution of your image accordingly, there's no such thing as a bitmap with sub-pixel coordinates, so basically what you can do is create an in memory image larger than what you want rendered to the screen, but allows you "sub-pixel" accuracy.

When you draw to the larger image in memory, you copy and resample that into the smaller render visible to the end user.

For example: a 100x100 image and it's 50x50 resized / resampled counterpart:

resampling

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文