Dear OpenJDK 2D developers community.

I have found a nasty 2D rendering bug in the latest OpenJDK 1.7.60
version. It probably affects all other OpenJDK versions, too. The bug
does not appear in OracleJDK 1.7.0.60, so this is purely an OpenJDK issue.

I do not know of any other place where to report this issue, so I'm now
submitting my small test program to this mailing list (attached). The
test program demonstrates the bug, and also documents my findings in detail.

I have tracked the bug into 'sun.java2d.pisces.PiscesCache' and
'sun.java2d.pipe.AAShapePipe' classes, but I do not understand the
theory behind the Java rendering pipelines well enough to suggest a
patch. I think that the problem should be easy to fix for anyone
familiar with the rendering pipelines and corresponding "tiling" technique.

I think so because this is a Pure Java issue, it seems to be a simple
"off by one pixel" kind of error, and because all the other pipe
implementations except 'AAShapePipe' seem to work, and also because
corresponding OracleJDK implementation works.



I also think this is a very relevant bug in general, because in its'
current state, OpenJDK can't be reliably used for anti-aliased rendering
that also uses custom Composite implementations. Both of these features
are absolutely required for any high-quality graphical applications,
including the one I'm developing. That application is also already in
widespread international use, and it's a real shame that users now have
to choose between perfect OracleJDK and crappy OpenJDK results.


I'm happy to answer questions and provide addition information if
necessary. I also just joined this mailing list, so I will be monitoring
any discussion this message may spur.


  Best regards

    Rami Hänninen
import java.awt.Color;
import java.awt.Composite;
import java.awt.CompositeContext;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

/**
 * Test program that demonstrates a bug in OpenJDK 1.7.0.60 (and
 * probably in all other OpenJDK versions, too).
 *
 * @see #main(String[])
 *
 * @author Rami Hänninen
 */

public class OpenJDKFillBug
{
  /**
   * Test program that demonstrates a bug in OpenJDK 1.7.0.60 (and
   * probably in all other OpenJDK versions, too). To see the bug, simply run
   * the 'main' program with OpenJDK. The bug makes the 'g2d.fill'
   * method fail with the following exception:
   * 
   * <PRE>
   * Exception in thread "main" java.awt.image.RasterFormatException: (x + width) is outside raster
   *   at sun.awt.image.IntegerInterleavedRaster.createWritableChild(IntegerInterleavedRaster.java:467)
   *   at sun.awt.image.IntegerInterleavedRaster.createChild(IntegerInterleavedRaster.java:514)
   *   at sun.java2d.pipe.GeneralCompositePipe.renderPathTile(GeneralCompositePipe.java:106)
   *   at sun.java2d.pipe.AAShapePipe.renderTiles(AAShapePipe.java:201)
   *   at sun.java2d.pipe.AAShapePipe.renderPath(AAShapePipe.java:159)
   *   at sun.java2d.pipe.AAShapePipe.fill(AAShapePipe.java:68)
   *   at sun.java2d.pipe.PixelToParallelogramConverter.fill(PixelToParallelogramConverter.java:164)
   *   at sun.java2d.pipe.ValidatePipe.fill(ValidatePipe.java:160)
   *   at sun.java2d.SunGraphics2D.fill(SunGraphics2D.java:2466)
   *   at OpenJDKFillBug.main(OpenJDKFillBug.java:55)
   * </PRE>
   * 
   * The bug is OpenJDK specific. This program runs with Oracle JDK
   * just fine (it still does not do anything sensible, but it does
   * not fail with a RasterFormatException, either).
   * 
   * <P>
   * 
   * The bug is related to sun.java2d.pisces.PiscesCache constructor
   * that accepts '(int minx,int miny,int maxx,int maxy)' arguments:
   * the internal 'bboxX1' and 'bboxY1' are set to values one greater
   * than given maximum X and Y values. Those maximum values are then
   * later used in AAShapePipe' class 'renderTiles' method, where a
   * Y/X loop eventually calls 'GeneralCompositePipe' class
   * 'renderPathTile' method. In that method, the operation will
   * eventually call 'IntegerInterleavedRaster' class
   * 'createWritableChild' method with arguments:
   * 
   * <UL>
   * <LI>x=800
   * <LI>y=0
   * <LI>width=2 (this value is too high: should be 1)
   * <LI>height=32
   * <LI>x0=0
   * <LI>y0=0
   * <LI>bandList[]=null
   * </UL>
   * 
   * This calls for a sub-raster with bounds that fall outside the
   * original raster, and therefore the 'createWritableChild' method
   * correctly throws 'RasterFormatException'.
   * 
   * <P>
   * 
   * The bug is closely related to the use of a custom Composite
   * implementation, which are quite rare. The application where this
   * bug was first detected implements a high-quality PDF rendering
   * engine that needs custom Composite operations to properly
   * implement PDF advanced color blending and masking operators.
   */

  public static void main(String args[])
  {
    BufferedImage bi = new BufferedImage(801,1202,BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = bi.createGraphics();
    GeneralPath gp = new GeneralPath();
    AffineTransform m = new AffineTransform(2.483489907915543,
                                            0.0,
                                            0.0,
                                            -2.4844977263331955,
                                            0.0,
                                            1202.0);
    Composite c = new CustomComposite();

    gp.moveTo(-4.511, -14.349);
    gp.lineTo(327.489, -14.349);
    gp.lineTo(327.489, 494.15);
    gp.lineTo(-4.511, 494.15);
    gp.closePath();
    
    g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
                         RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
                         RenderingHints.VALUE_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
                         RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST,
                         Integer.valueOf(140));
    g2d.setRenderingHint(RenderingHints.KEY_DITHERING,
                         RenderingHints.VALUE_DITHER_ENABLE);
    g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                         RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                         RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                         RenderingHints.VALUE_STROKE_NORMALIZE);
    g2d.setPaint(Color.red);
    g2d.setComposite(c);
    g2d.setTransform(m);
    g2d.fill(gp);
    g2d.dispose();
  }
  
  // === CustomComposite ===

  /**
   * Dummy custom Composite implementation.
   */

  public static class CustomComposite implements Composite
  {
    @Override
    public CompositeContext createContext(ColorModel srcColorModel,
                                          ColorModel dstColorModel,
                                          RenderingHints hints)
    {
      return new CustomCompositeContext();
    }
  
    // === CustomCompositeContext ===

    /**
     * Dummy custom CompositeContext implementation.
     */
    
    public static class CustomCompositeContext implements CompositeContext
    {

      @Override
      public void dispose()
      {
        // NOP
      }

      @Override
      public void compose(Raster src,Raster dstIn,WritableRaster dstOut)
      {
        // NOP
      }
    }
  }
}

Reply via email to