vendredi 27 février 2015

Weird performance issues with image scaling using drawImage in Java

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.


Aucun commentaire:

Enregistrer un commentaire