JDK-4916948 : DrawImage does not interpolate with a translation-only AffineTransform
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 1.2.1,1.4.2
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2003-09-03
  • Updated: 2004-01-26
  • Resolved: 2004-01-26
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
5.0 b36Fixed
Related Reports
Relates :  
Relates :  
Description

Name: jk109818			Date: 09/03/2003


FULL PRODUCT VERSION :
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2-b28)
Java HotSpot(TM) Client VM (build 1.4.2-b28, mixed mode)

FULL OS VERSION :
Microsoft Windows 2000 [Versione 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
I discovered that drawing an image in a Graphics2D context (both on-screen and from a bufferedimage) with a translation-only AffineTransform at non-integer coordinates does not interpolate the drawing as expected.

Note that the relevant Hints have been set in the context:
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a graphics2d context;
Set best quality hints;
Set a translation-only transform at non-integer coordinates (i.e. 0.7,0.5)
Draw an image with drawImage(img,x,y,null);

Suggestion: better compile/run the sample source.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The image should be interpolated, but it is not.
ACTUAL -
The image is not interpolated. It is plotted at is was at integer coordinates.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package test.bug;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;

public class TransImageBug extends Panel implements java.awt.event.MouseMotionListener
{
// storage
private static BufferedImage bi = new BufferedImage(60, 50, BufferedImage.TYPE_INT_ARGB);
private float px = 0, py = 0;

// create some test gfx
static
{
Graphics2D g2 = (Graphics2D) bi.getGraphics();
g2.setColor(Color.white);
g2.fillRect(0, 0, bi.getWidth() - 1, bi.getHeight() - 1);
g2.setColor(Color.black);
g2.fillRect(1, 1, bi.getWidth() - 3, bi.getHeight() - 3);
g2.setColor(Color.white);
g2.fillRect(5, 5, bi.getWidth() - 11, bi.getHeight() - 11);
g2.setColor(Color.black);
g2.drawString("CIAO!", 20, 30);
}

// paint on raster
BufferedImage raster = new BufferedImage(600, 400, BufferedImage.TYPE_INT_ARGB);

public TransImageBug()
{
super();
setBackground(Color.gray);
}
public static void main(String args[])
{
System.out.println("Starting test");

TransImageBug canvas = new TransImageBug();
Frame frame = new Frame();
frame.setSize(640, 480);

canvas.addMouseMotionListener(canvas);

frame.add("Center", canvas);
frame.requestFocus();
frame.setEnabled(true);

frame.show();
}

//
public void mouseDragged(java.awt.event.MouseEvent e)
{
}

//
public void mouseMoved(java.awt.event.MouseEvent e)
{
px = e.getX() / 10f;
py = e.getY() / 10f;
repaint();
}

//
public void paint(Graphics g)
{
// get graphics from the raster
Graphics2D g2d = (Graphics2D) raster.getGraphics();

// set hints
g2d.setColor(Color.gray);
g2d.fillRect(0, 0, raster.getWidth(), raster.getHeight());
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

// draw
AffineTransform at = new AffineTransform();

g2d.setTransform(at);
g2d.setColor(Color.white);
g2d.drawString("drawing at (" + px + "," + py + ")", 10, 10);

// this code blits the image bi at px,py
// the image is blitted at float coords but it is not interpolated as expected !
at = AffineTransform.getTranslateInstance(px, py + 50);
g2d.setTransform(at);
g2d.drawImage(bi, 0, 0, null);

// this code blits the image bi at px,py, but with a do-nothing shear transform
// the image is blitted at float coords an now it is interpolated..
at = AffineTransform.getTranslateInstance(px + 65, py + 50);
at.shear(.0000001, .0000001); // <- workaround
g2d.setTransform(at);
g2d.drawImage(bi, 0, 0, null);

// paint raster on canvas
g.drawImage(raster, 0, 0, null);
}

// avoid flickering, please
public void update(Graphics g)
{
paint(g);
}
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
There's one, very simple:

Concatenate a do-noting shear transform to the original transform:

at.shear(.0000001, .0000001);

Now the drawImage interpolates correctly.

It should be a really simple problem in the image drawing code of Java2D. Just add a test to see if the current transform transates to non-integer coords and do the interpolation. I'm sure you do the same with shear, scale, etc. so that you can save some time by simply blitting pixels without interpolating (saving time).

I'm sure it's a 5 min. fix for you !
(Incident Review ID: 201505) 
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: tiger-beta2 FIXED IN: tiger-beta2 INTEGRATED IN: tiger-b36 tiger-beta2
14-06-2004

EVALUATION We don't distinguish between simple 1.1-style integer only translations and Java2-style floating point translations in our internal transform State variable, but we do have another variable that tells whether or not a Java2 transformation method has been executed. With some simple changes, this could become a better way to choose an image rendering algorithm: - We should modify the DrawImage pipeline to key off of "complexTransform" instead of "transformState" when deciding to short-circuit the transformation stages during interpolated operations. - Make sure that when the interpolation mode is set to NearestNeighbor that we use the old transformState tests as that algorithm doesn't show any changes for sub-pixel translations. - We should modify invalidateTransform to check if a translation in the matrix is integer only so that we correctly return to the simpler non-complex state after calls to "translate(20.0, 30.0)" and calls to setTransform(<some simple integer translation matrix>) That should then make us honor the non-integer translations in the drawImage calls... ###@###.### 2003-11-13 The changes are somewhat more complicated than stated above. The image pipeline has many stages and many pathways that explode from the many parameters that feed into it, including: - the transform in the Graphics2D and its state - a transform supplied in some variants of drawImage() whether as an AffineTransform parameter or width/height params - the interpolation hint - whether or not we have a direct implementation of image scaling for a given destination surface type - possible background color being passed around All of these various parameters cause the chain to try to collapse different base drawImage method calls into common calling chains and then to break them apart as implementation possibilities are investigated. At each stage of these operations that try to make sense of what the final operation involves, the methods perform tests on the data to see which implementation choice to make. Many of those tests will ignore, or truncate, or preserve the fractional parts of the translation components of the Graphics2D transform and the transform that may be supplied by the caller, or constructed by the pipeline to represent the implicit scaling when width/height parameters are supplied. Untying the many interdependencies on this chain will involve quite a few changes to various parts of our image pipeline that will need to be carefully reviewed for risk assessment. This change is very unlikely to be accepted for Tiger beta1 at this point and will have to be very thoroughly designed and tested in order to be accepted for Tiger beta2 as well. Additionally, in testing the behavior using a simple hack for one variant of the pipeline and using the workaround stated in the description (adding an extremely tiny scale or shear to fool the pipeline into honoring the full transform), it appears that we have bugs in our interpolation algorithms that cause the lower and right edges of sub-pixel translated images to blink on and off as the image is moved by small amounts almost as if the lower/right edges were still being translated by an integer amount. This is most likely due to incorrect edge termination conditions when dealing with transforms that have non-integer translations. Until we get a better handle on the can of worms that this change is uncovering, we cannot commit decisively to fixing this bug for Tiger but we might be able to fix some aspects of it and leave the more complicated remaining fixes for a future release... ###@###.### 2003-11-14 There were quite a few changes, but in making them we also took the time to simplify a lot of the logic in the image pipeline so that the existing logic was easier to debug and change to be compliant with the new decisions. A side benefit is faster sub-image scaling in the OpenGL pipelines for the dxy12,sxy12 variant of the drawImage call. The change will not appear in beta1, but is now in the beta2 workspace and will receive extensive testing there for release in the beta2 time frame. ###@###.### 2003-12-17
17-12-2003