Fast 2D hardware scaling of images (without GL?)

Looking for a fast way to scale up a pretty big (1280x720) backbuffer with decent quality (not nearest-neighbour) to fit different resolution monitors for fullscreen mode. This would have to be something fast enough to execute at runtime, presumably using hardware accelerated scaling.

I can do this with GL fairly easily, wondering if there is a way in Java2D land. I’ve mucked around with various different approaches to achieve this, including toggling ddscale=true (which seemed to have no effect for me).

Anyone have a solid tried-and-true method of achieving fast runtime scaling in Java2D to support multiple resolutions?

Thanks


Graphics2D g2d = (Graphics2D) g;
// or BILINEAR for speed
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);

g2d.drawImage(img, x, y, width, height, null); // scale width, height

Yeah BurntPizza, that is what I tried, but it is very, very slow. ~2ms for nearest neighbour, ~40ms for bilinear, ~145ms for bicubic. ddscale=true seems to have no effect.

Are you using that particular drawImage call, or a different one? What type of image?

Yes, exactly that. Here is the line. It’s an Image.


// elsewhere in the Canvas
buffer_ = createImage(bufW, bufH);

// In the render loop
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(buffer_, 0, 0, getWidth(), getHeight(), null);

I should point out that generally speaking all the rendering code runs very very fast (hundreds of images in <10ms), it’s just this scaling that seems slow and it clearly seems to not be running on the hardware

What does buffer_.getType() yield?

BufferedImage@632f19fe: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 1280 height = 800 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0

So I think that’s TYPE_INT_RGB. I seem to recall reading that the quickest type of rendering is whatever the canvas returns out of createImage so that the format matches.

Hmm, yeah INT_RGB is fast, what is the hardware exactly?

ATI HD5770. Not the fanciest video card, but I do similar scaling in GL multiple times per frame and it is very very fast so I feel like this is clearly running in software and not HW.

Does this code run fast for you? In a blank canvas scaling an image 1280x800 up to 1920x1200?

FRAPS reports ~530 fps on an HD 4670 (lol!)


import java.awt.*;
import javax.swing.*;

@SuppressWarnings("serial")
public class Scaling extends JPanel {
	
	private BufferedImage buffer = new BufferedImage(1280, 720, BufferedImage.TYPE_INT_RGB);
	
	public static void main(String[] a) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.add(new Scaling());
		// screen is 1080p
		frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
		frame.setVisible(true);
		
		while (true) {
			frame.repaint();
		}
	}
	
	@Override
	public void paint(Graphics g) {
		Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g2d.drawImage(buffer, 0, 0, getWidth(), getHeight(), null);
	}
}

EDIT: changed to bilinear, derp. FPS remains unchanged.

If you don’t figure out J2D, then I have implemented it in software pretty quickly before, although I can’t find the code now, here’s some resources:

http://www.compuphase.com/graphic/scale2.htm
http://tech-algorithm.com/articles/bilinear-image-scaling/ <-- I believe this is what I used

Haha what in the world. Is that right? How is this so fast for you… Like if you nanoTime() around that call what do you get (in ms?) Does it change depending on RenderingHints.BICUBIC?

Updated:

import java.awt.*;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Scaling extends JPanel {
	
	private BufferedImage buffer = new BufferedImage(1280, 720, BufferedImage.TYPE_INT_RGB);
	private JFrame frame;
	
	private long lastSec = System.nanoTime();
	private int fps = 0;
	
	Scaling(JFrame f) {
		frame = f;
	}
	
	public static void main(String[] a) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.add(new Scaling(frame));
		frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
		frame.setVisible(true);
		
		while (true) {
			frame.repaint();
		}
	}
	
	@Override
	public void paint(Graphics g) {
		Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g2d.drawImage(buffer, 0, 0, getWidth(), getHeight(), null);
		
		if (System.nanoTime() - lastSec > 1000000000) {
			lastSec = System.nanoTime();
			frame.setTitle("FPS: " + fps);
			fps = 0;
		}
		fps++;
	}
}

More reliable timing code says ~250 fps for bilinear (same as NN), and… 2 for bicubic. Yep.

And what is getWidth() and getHeight() in your example? What are you scaling up to? (I am scaling up to 1920x1200 with setfullscreen enabled)

1920 x 1080. FPS is unchanged with 1920 x 1200 in the drawImage call.

Hmm. Welp. Thank you! This is very useful information but simultaneously pretty depressing that it seems like some sort of hardware thing. I will probably look at this more heavily tomorrow and see if I can write a software implementation that’s not too bad.

Really want to avoid converting this whole renderer over to GL!

I noticed that you are painting onto a JPanel whereas I am actually painting on a Canvas. like 8 years ago I recall testing the two and discovering that Canvas ran faster than JPanel but maybe for some reason using Canvas in this case eliminates the HW acceleration? Will see if I can try out JPanel.

Hmm nope. JPanel ~25ms even just for nearest neighbour, 60ms for Bilinear. Took a look at imgScalr and this “library” just calls g.drawImage()!!

Running out of ideas here :frowning:

Have you tried it on any other machines? I’m betting it’s just an anomaly with yours. Drivers?

I haven’t, but ultimately it needs to run on my machine (and presumably any other machines with the same issue). I’m still a bit skeptical about your example - maybe it needs something drawn to the buffer for it to slow down?

Does buffer.getCapabilities(gc).isAccelerated() return false or true for you? I get “false”.

It is true.