JDK-7043054 : REGRESSION: JDK 7 b126 : Wrong userBounds in Paint.createContext()
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 6u10,7
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic,linux
  • CPU: generic,x86
  • Submitted: 2011-05-09
  • Updated: 2014-10-24
  • Resolved: 2011-05-25
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.
JDK 7
7 b143Fixed
Related Reports
Duplicate :  
Relates :  
Description
SYNOPSIS
--------
REGRESSION: Wrong userBounds in Paint.createContext()


JDK VERSION
-----------
JDK 7 b126 and up (including b140)
JDK 6u12 and up (including 6u25)

OPERATING SYSTEM
----------------
Reported on Windows.
Reproducbile on Solaris x86.
Probably platform independent.

PROBLEM DESCRIPTION
-------------------
The userBounds parameter that gets passed to the createContext method of a user-defined java.awt.Paint implementation is wrong (namely, it reflects the device bounds, not the user bounds), when anti-aliasing is enabled on the Graphics2D object.

The API specification says:

-- 
The Paint object's createContext method is called to create a PaintContext. The PaintContext stores the contextual information about the current rendering operation and the information necessary to generate the colors.

The createContext method is passed the bounding boxes of the graphics object being filled in user space and in device space, the ColorModel in which the colors should be generated, and the transform used to map user space into device space.
-- 

The attached test case shows a panel that displays a blue cloud in a given rectangle (similar to what RadialGradientPaint does), normalized in such a way that the fill color at the border of the rectangle should be white.

In Sun JDK 1.6.0_11 or older, and in JDK 7 b125 or older, the output is:

createContext: userBounds = java.awt.Rectangle[x=3,y=3,width=511,height=245], deviceBounds = java.awt.Rectangle[x=76,y=122,width=511,height=245]

and the drawing is correct.  See attached image "correct.png".

In Sun JDK 1.6.0_12 or newer, and in JDK 7 b126 or newer, the output is

createContext: userBounds = java.awt.geom.Rectangle2D$Double[x=76.0,y=122.0,w=511.0,h=245.0], deviceBounds = java.awt.Rectangle[x=76,y=122,width=511,height=245]

and the drawing is wrong.  See attached image "incorrect.png".

TESTCASE
--------
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

// Test the userBounds parameter in the createContext method of Paint.

public class TestPaintContextUserBounds {

    static class BlueCloudPaintContext implements PaintContext {
        private final AffineTransform inverseTransform;
        private final ColorModel cm;
        BlueCloudPaintContext(AffineTransform inverseTransform) {
            this.inverseTransform = inverseTransform;
            this.cm = new DirectColorModel(24, 0xff0000, 0x00ff00, 0x0000ff);
        }
        public void dispose() {
        }
        public ColorModel getColorModel() {
            return cm;
        }
        public Raster getRaster(int x0, int y0, int width, int height) {
            WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
            for (int y = y0; y < y0 + height; y++) {
                for (int x = x0; x < x0 + width; x++) {
                    // Map the point (x,y) back to the square [0,1]x[0,1].
                    Point2D p = inverseTransform.transform(new Point2D.Double(x, y), null);
                    double px = p.getX();
                    double py = p.getY();
                    double value = Math.exp(-((px-0.5)*(px-0.5)+(py-0.5)*(py-0.5))) * 4*px*(1.0-px) * 4*py*(1.0-py);
                    int r = (int)Math.round(255*(1.0-value));
                    int g = (int)Math.round(255*(1.0-value));
                    int b = 255;
                    raster.setPixel(x - x0, y - y0, new int[] { r, g, b});
                }
            }
            return raster;
        }
    }

    static class BlueCloudPaint implements Paint {
        public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform transform, RenderingHints hints) {
            System.err.println("createContext: userBounds = "+userBounds+", deviceBounds = "+deviceBounds);
            //Thread.dumpStack();
            transform = new AffineTransform(transform);
            transform.concatenate(new AffineTransform(userBounds.getWidth(), 0,
                                                      0, userBounds.getHeight(),
                                                      userBounds.getX(), userBounds.getY()));
            AffineTransform inverseTransform;
            try {
                inverseTransform = transform.createInverse();
            } catch (NoninvertibleTransformException e) {
                inverseTransform = new AffineTransform();
            }
            return new BlueCloudPaintContext(inverseTransform);
        }
        public int getTransparency() {
            return OPAQUE;
        }
    }

    static class MainPanel extends JComponent {
        public void paintComponent(Graphics g) {
            int width = getWidth();
            int height = getHeight();

            ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            ((Graphics2D)g).setPaint(Color.red);
            ((Graphics2D)g).setStroke(new BasicStroke(3));
            g.drawRect(1, 1, width-3, height-3);

            ((Graphics2D)g).setPaint(new BlueCloudPaint());
            g.fillRect(3, 3, width-6, height-6);
        }
    }

    static class MyFrame extends JFrame {
        MyFrame() {
            setDefaultCloseOperation(EXIT_ON_CLOSE);

            getContentPane().setLayout(new BorderLayout());

            JButton topButton = new JButton("Paint.createContext bug");
            topButton.setPreferredSize(new Dimension(400, 119));
            topButton.setSize(new Dimension(400, 119));
            getContentPane().add(topButton, BorderLayout.NORTH);

            JButton leftButton = new JButton("Left");
            leftButton.setPreferredSize(new Dimension(73, 200));
            leftButton.setSize(new Dimension(73, 200));
            getContentPane().add(leftButton, BorderLayout.WEST);

            MainPanel mainPanel = new MainPanel();
            getContentPane().add(mainPanel, BorderLayout.CENTER);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(
                                  new Runnable() {
                                      public void run() {
                                          MyFrame frame = new MyFrame();
                                          frame.setSize(400, 400);
                                          frame.setVisible(true);
                                      }
                                  });
    }
}

Comments
EVALUATION Solved by supplying user space bounding coordinates to the new ParallelogramPipe calls which can be used to generate proper bounds for the Paint.createContext methods in the lower layers.
11-05-2011

EVALUATION Apparently introduced by the fix for 6766342: Improve performance of Ductus rasterizer
09-05-2011