Scale images as a single surface in the Java 2D API

In Java, there is a method called scale(double sx, double sy) in Graphics2D . But this method seems to scale images as separate surfaces , not a single surface . As a result, scaled images have sharp corners if the original images do not have additional width and height. The following screenshot demonstrates the problem:

enter image description here

Here is the code:

 import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; public class TestJava { static int scale = 10; public static class Test extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.scale(scale, scale); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); BufferedImage img = null; try { img = ImageIO.read(new File("Sprite.png")); } catch (IOException e) { e.printStackTrace(); } g2.drawImage(img, null, 5, 5); } } public static void main(String[] args) { JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); Test test = new Test(); test.setBackground(Color.WHITE); frame.add(test); frame.setSize(300, 350); frame.setVisible(true); } } 

One possible solution to the problem is the original image with additional width and height (in this case, "Sprite.png" ). But this does not seem to fix the problem. Therefore, I am looking for a programming method in Java to solve this problem, and not for using an image editor. What is the way to do this?

+5
source share
2 answers

Interesting question (+1). I think it’s easy to find a good solution for this: interpolation, when the image is always scaled inside the image, and I can’t imagine how it can blur the scaled pixels outside the image.

This leads to a fairly simple solution: you can add a 1-pixel marker around the entire image. In fact, this is the software solution that you have proposed for yourself. The result will look like this:

ScaledImage

(left is the original, and right is an additional 1-pixel)

Here, like MCVE, based on your example

 import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; public class ScaledPaint { static int scale = 10; public static class Test extends JPanel { BufferedImage image = createTestImage(); BufferedImage imageWithMargin = addMargin(image); @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.scale(scale, scale); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.drawImage(image, 5, 5, null); g2.drawImage(imageWithMargin, 30, 5, null); } } private static BufferedImage createTestImage() { BufferedImage image = new BufferedImage(20, 20, BufferedImage.TYPE_INT_ARGB); Graphics2D g = image.createGraphics(); g.setColor(Color.RED); g.drawOval(0, 0, 19, 19); g.dispose(); return image; } private static BufferedImage addMargin(BufferedImage image) { return addMargin(image, 1, 1, 1, 1); } private static BufferedImage addMargin(BufferedImage image, int left, int right, int top, int bottom) { BufferedImage newImage = new BufferedImage( image.getWidth() + left + right, image.getHeight() + top + bottom, BufferedImage.TYPE_INT_ARGB); Graphics2D g = newImage.createGraphics(); g.drawImage(image, left, top, null); g.dispose(); return newImage; } private static BufferedImage convertToARGB(BufferedImage image) { BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g = newImage.createGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); return newImage; } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { createAndShowGUI(); } }); } private static void createAndShowGUI() { JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); Test test = new Test(); test.setBackground(Color.WHITE); frame.add(test); frame.setSize(600, 350); frame.setVisible(true); } } 

But...

... one problem with this approach is already visible in the screenshot: the image is getting larger. And you will have to consider this when drawing an image. So if your original sprites had a good, predefined, easy-to-use size, such as 16x32, they would subsequently be 18x34 in size, which is rather strange for tiles. This may not be a problem, depending on how you handle the dimensions of the tiles. But if this is a problem, you can think of possible solutions. One solution might be ...

  • take input image 16x32
  • create 16x32 output image
  • draw an image with 16x32 input in the area (1,1) - (15,31) of the output image.

But given the fact that in sprites of this size, each pixel can be important, it can also have unwanted effects ...


Aside: Although I assume that the code you provided is intended only for MCVE, I would like to indicate (for others who could read this question and code):

  • You are using DO NOT upload images to the paintComponent method
  • For effective painting, any downloadable PNG (especially when it contains transparency) must be converted to an image of a known type. This can be done using the convertToARGB method in my code snippet.
+2
source

In your example, this is not an image that you are scaling, but you are setting a scaling transformation of the Graphics2D object that will apply to all operations performed in this graphics context.

If you want to scale the image, you have 2 options. Everything that I write below uses java.awt.Image , but since BufferedImage extends Image , all this applies to BufferedImage .

1. Image.getScaledInstance()

You can use the Image.getScaledInstance(int width, int height, int hints) method Image.getScaledInstance(int width, int height, int hints) . The third parameter ( hints ) indicates which scaling algorithm you want to use, which will affect the "quality" of the scaled image. Possible values:

 SCALE_DEFAULT, SCALE_FAST, SCALE_SMOOTH, SCALE_REPLICATE, SCALE_AREA_AVERAGING 

Try SCALE_AREA_AVERAGING and SCALE_SMOOTH for better images.

 // Scaled 3 times: Image img2 = img.getScaledInstance(img.getWidth(null)*3, img.getHeight(null)*3, Image.SCALE_AREA_AVERAGING); // Tip: you should cache the scaled image and not scale it in the paint() method! // To draw it at x=100, y=200 g2.drawImage(img2, 100, 200, null); 

2. Graphics.drawImage()

You can use different overloads of Graphics.drawImage() , where you can specify the size of the scalable image. You can "control" image quality with the KEY_INTERPOLATION rendering KEY_INTERPOLATION . It has 3 possible meanings:

 VALUE_INTERPOLATION_NEAREST_NEIGHBOR, VALUE_INTERPOLATION_BILINEAR, VALUE_INTERPOLATION_BICUBIC 

VALUE_INTERPOLATION_BILINEAR uses a bilinear interpolation algorithm of 4 nearest pixels. VALUE_INTERPOLATION_BICUBIC uses cubic interpolation from 9 neighboring pixels.

 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); // To draw image scaled 3 times, x=100, y=200: g2.drawImage(img, 100, 200, img.getWidth(null)*3, img.getHeight(null)*3, null); 

Sharp ribs

If you want to avoid sharp edges around the image, you should write a loop to move along the pixels along the edge of the image and set some transparency, for example. alpha = 0.5 (or alpha = 128). You can also do this on multiple rows / columns, for example. 0.8 alpha for the edge, 0.5 alpha for the 2nd line and 0.3 alpha for the 3rd line.

+3
source

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


All Articles