United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4916948 : DrawImage does not interpolate with a translation-only AffineTransform

Details
Type:
Bug
Submit Date:
2003-09-03
Status:
Resolved
Updated Date:
2004-01-26
Project Name:
JDK
Resolved Date:
2004-01-26
Component:
client-libs
OS:
windows_2000
Sub-Component:
2d
CPU:
x86
Priority:
P3
Resolution:
Fixed
Affected Versions:
1.2.1,1.4.2
Fixed Versions:
5.0 (b36)

Related Reports
Relates:
Relates:

Sub Tasks

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
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
                                     
2003-12-17
CONVERTED DATA

BugTraq+ Release Management Values

COMMIT TO FIX:
tiger-beta2

FIXED IN:
tiger-beta2

INTEGRATED IN:
tiger-b36
tiger-beta2


                                     
2004-06-14



Hardware and Software, Engineered to Work Together