Drawing an image using subpixel precision using Graphics2D

I'm currently trying to draw images on the screen at normal speed, such as in a video game.

Unfortunately, due to the speed with which the image moves, some frames are identical because the image has not yet moved the full pixel.

Is there a way to provide float values ​​for Graphics2D to display positions on the screen, not int values?

Initially, this is what I did:

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

These, of course, are threshold values, so the image does not move between pixels, but is skipped from one to another.

The next method was to set the color of the paint on the texture and draw in the specified position. Unfortunately, this led to incorrect results, which showed that smoothing is not alternating, but correct smoothing.

 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 sub-pixel image rendering in Java?

+6
source share
4 answers

You can use BufferedImage and AffineTransform , draw a buffered image, and then draw a buffered image for the component in the drawing 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); } 

See my complete example here: http://pastebin.com/hSAkYWqM

+6
source

You can combine the image yourself using subpixel accuracy, but it works more on your part. Simple bilinear interpolation should work well enough for a game. Below is the psuedo-C ++ code for this.

Usually to draw a sprite in place (a, b) you 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 subpixel rendering, you do the same loop, but consider the subpixel offset like this:

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

It should not contain additional memory, but additional time will be required for rendering.

+3
source

I successfully solved my problem after I did something like the proposed Lawrencealan.

Initially, I had the following code, where g converted to a 16: 9 coordinate system before calling the method:

 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 the questioner Kaushik Shankar noted, turning double positions into integers causes the image to “jump” around, and turning double dimensions into integers makes it scalable “jumping” (why the hell g.drawImage doesn't accept doubles ?!). I found a job for me:

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

This seems like a dumb way to do this.

+1
source

Change the resolution of your image accordingly, there is no such thing as a bitmap image with subpixel coordinates, so basically you can create an image in memory larger than you want to display on the screen, but it allows you to "subpixel".

When you draw onto a larger image in memory, you copy and overfulfill it into a smaller visualization visible to the end user.

For example: a 100x100 image and its size 50 × 50 resized / resized:

resampling

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

0
source

Source: https://habr.com/ru/post/904733/


All Articles