2011-12-30 31 views
6

我目前正试图在屏幕上以像电视游戏中的常规速率绘制图像。使用Graphics2D使用亚像素级别准确性绘制图像

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

有没有办法提供float值到Graphics2D的屏幕上的位置来绘制图像,而不是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中图像子像素渲染的预期效果?

回答

6

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

+0

嘿劳伦!感谢您的回应。非常感谢您向我展示如何做到这一点! – 2012-01-05 23:03:20

+0

为什么t.scale(1,1); ?? AFAIK该指令什么都不做? (缩放至其实际尺寸?) – pakore 2013-03-01 14:34:11

0

相应地改变你的图像的分辨率,没有像亚像素坐标的位图这样的事情,所以基本上你可以做的是创建一个内存中的图像大于你想要渲染到屏幕上,但可以让你“子像素”的准确性。

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

例如:一个100x100的图像,它是50×50大小/重采样对应:

resampling

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

+0

这也实现了双缓冲,所以你可以做多次修改到内存中的图像,而不是更新它可为处理器激烈 – lawrencealan 2011-12-30 07:11:55

+0

图片球在屏幕上移动的用户界面,但它的移动速度很慢。它每秒移动1个像素。如果我每秒画两次到屏幕,那么第二次绘图将与第一次完全相同。第三次将显示可以一步移动一个像素的球。我想要的是将球部分移动,以便它可以像在两个像素之间一样绘制。这个功能在java中可用吗? – 2011-12-30 07:14:03

+0

你必须使用上述过程来模拟它。所以说你的渲染区域是800x600,你需要一个2x的存储器内图像 - 1600x1200 - 而不是在浮点级别移动,你移动整个像素...所以,而不是移动一个球0.5像素您移动它的更大的基于内存映像中1像素,而当你把它重新取样到可视区域(800×600),这将是模拟的亚像素精度.. – lawrencealan 2011-12-30 07:32:07

3

您可以合成图像自己使用的亚像素精度,但它更多的工作就你而言。简单的双线性插值对于游戏应该足够好。下面是用于这样做的psuedo-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; 
} 

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

+0

我会如何去做在java这样的操作?是否可以进行优化?我有很多对象,计算这样的东西似乎可能需要一些时间... – 2011-12-31 04:15:54

+0

我不是很流利的java,但大概你的精灵有一些结构,对吧?在基于C的语言中,通常有一个指向第一个像素的指针和每行的字节数。我想在Java中,你可能有一排像素数组,在行之间没有空白。因此,一个精灵实际上可能只是一个非常大的红色,绿色,蓝色,阿尔法(或可能是阿尔法,红色,绿色,蓝色)阵列。因此,不要计算像素结构的地址,而只需获取所需的偏移量。在bilinearInterp中,前两行只是计算所需点周围4个像素的索引。 – user1118321 2011-12-31 17:01:14

+0

这是正确的,但要计算我的代码中的每一帧可能不是最好的方法。我希望可能会有某种内置函数作为执行此操作的Java API的一部分。 – 2011-12-31 18:42:46

1

做这样的事情提出lawrencealan后,我成功地解决了我的问题。

原来我有以下代码,其中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将香卡指出,转动双位置到整数使图像“跳”周围,并将双维变成整数使其规模“跳动”(为什么地狱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); 

} 

似乎是一个愚蠢的做法虽然。

+0

感谢发布;我最终也做了类似的事情;它绝对不幸的是,drawImage不支持这一点。 – 2014-11-29 07:08:44

+0

非常感谢!此外,我使用'g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);'使它看起来更好。 – Ajay 2016-08-27 14:47:24