1

The following test code renders a randomly generated image four times into a JFrame using different methods for image scaling:

import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.image.*;
import java.awt.geom. *;

public class Java2DImageBenchmark extends JFrame
{
    static int w = 1024;
    static int h = 1024;

    int run = 0;

    public void paint( Graphics g )
    {
        if( g instanceof Graphics2D )
        {
            final Graphics2D g2 = ( Graphics2D ) g;

            // create image with random noise in each run
            Random r = new Random();
            BufferedImage bi = g2.getDeviceConfiguration().createCompatibleImage( w / 3, h / 3 );
            for( int y = 0; y < h / 3; y++ )
                for( int x = 0; x < w / 3; x++ )
                    bi.setRGB( x, y, r.nextInt() );

            // scaling transformation
            AffineTransform s = AffineTransform.getScaleInstance( 3, 3 );

            long time = System.currentTimeMillis();

            // test runtimes of different scaling approaches
            switch( run )
            {
                case 0:
                    g2.drawImage( bi, new AffineTransformOp( s, AffineTransformOp.TYPE_NEAREST_NEIGHBOR ), 0, 0 );
                    System.out.println( "drawImage TYPE_NEAREST_NEIGHBOR: " + ( System.currentTimeMillis() - time ) + "ms" );
                    break;
                case 1:
                    g2.drawImage( bi, new AffineTransformOp( s, AffineTransformOp.TYPE_BILINEAR ), 0, 0 );
                    System.out.println( "drawImage TYPE_BILINEAR: " + ( System.currentTimeMillis() - time ) + "ms" );
                    break;
                case 2:
                    g2.drawImage( bi, new AffineTransformOp( s, AffineTransformOp.TYPE_BICUBIC ), 0, 0 );
                    System.out.println( "drawImage TYPE_BICUBIC: " + ( System.currentTimeMillis() - time ) + "ms" );
                    break;
                case 3:
                    BufferedImage biScaled = g2.getDeviceConfiguration().createCompatibleImage( w, h );
                    ( ( Graphics2D ) biScaled.getGraphics() ).drawImage( bi, new AffineTransformOp( s, AffineTransformOp.TYPE_BICUBIC ), 0, 0 );
                    g2.drawImage( biScaled, new AffineTransformOp( new AffineTransform(), AffineTransformOp.TYPE_NEAREST_NEIGHBOR ), 0, 0 );
                    System.out.println( "drawImage TYPE_BICUBIC prescaled: " + ( System.currentTimeMillis() - time ) + "ms" );
                    break;
                case 4:
                    System.exit( 0 );
            }
            ++run;
            repaint();
        }
        else
        {
            g.drawString( "this component needs a Graphics2D for painting", 2, this.getHeight() - 2 );
        }
    }

    public static void main( String[] args )
    {
        Java2DImageBenchmark f = new Java2DImageBenchmark();
        f.setSize( w, h );
        f.setVisible( true );
        f.repaint();
    }
}

On my MacBook Pro, I get two very different results when I switch between integrated Intel HD Graphics 4000 and NVIDIA GeForce GT 650M (switching is done using gfxCardStatus):

Intel HD Graphics 4000
======================
drawImage TYPE_NEAREST_NEIGHBOR:   11ms
drawImage TYPE_BILINEAR:         1281ms
drawImage TYPE_BICUBIC:          1198ms
drawImage TYPE_BICUBIC prescaled:  82ms

NVIDIA GeForce GT 650M
======================
drawImage TYPE_NEAREST_NEIGHBOR:   14ms
drawImage TYPE_BILINEAR:          138ms
drawImage TYPE_BICUBIC:           141ms
drawImage TYPE_BICUBIC prescaled:  80ms

As you can see, there is a big difference in performance for both platforms when interpolation is used.

Tested java version have been 1.7.0_76 and 1.8.0_31 with almost identical results. Switching sun.java2d.opengl between true and false also makes no difference at all, which I find kind of suprising since sun.java2d.opengl=false should actually balance between the two graphics cards due to software rendering in my opinion.

I discovered this behavior since one of my Java applications is unusably slow on some platforms with Intel GPU.

But the last test case, where the image is prescaled to the desired size using an additional buffer, is what I really don't understand. It is the fastest in any case. How can this be?

Can anyone confirm this behavior and possibly provide some insight what's actually happening here? The above benchmark is absolutely counterintuitive to me.

porst17
  • 377
  • 3
  • 16
  • 2
    Beware of [micro-benchmarking](http://stackoverflow.com/q/2842695/230513). "Swing programs should override `paintComponent()` instead of overriding `paint()`."—[*Painting in AWT and Swing: The Paint Methods*](http://www.oracle.com/technetwork/java/painting-140037.html#callbacks). See also [*Initial Threads*](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html). – trashgod Feb 27 '15 at 11:18
  • 1
    To extend the 2nd part of @trashgod's advice, it is also recommended to do all custom painting in a `JComponent` instead of a top level container such as `JFrame`. The `paintComponent()` advice applies to any `JComponent`. – Andrew Thompson Feb 27 '15 at 11:30
  • I don't think this is related to micro benchmarking. More than 1s for scaling a 1024/3*1024/3 image on a decent Intel based laptop is ridiculously slow. It renders this feature absolutely useless in pratice. – porst17 Feb 27 '15 at 13:36
  • You are right concerning `paintComponent()`, initial threads and `JComponent`. I made some tests where I incorporated your advice, but it makes no real difference in this particular example. – porst17 Feb 27 '15 at 13:41
  • 1
    Remember, MacOS does not allow direct access to the hardware for acceleration, this is all done through the API/software layer – MadProgrammer Feb 27 '15 at 20:01

0 Answers0