2011-12-30 81 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