Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8049352

Graphics2D.fill(Shape) operation always fails with a custom Composite

    Details

    • Type: Bug
    • Status: Closed
    • Priority: P4
    • Resolution: Duplicate
    • Affects Version/s: 7u60, 8
    • Fix Version/s: None
    • Component/s: client-libs
    • Labels:
    • Subcomponent:
      2d
    • CPU:
      x86_64
    • OS:
      linux

      Description

      FULL PRODUCT VERSION :
      java version "1.7.0_60"
      OpenJDK Runtime Environment (fedora-2.5.0.1.fc20-x86_64 u60-b30)
      OpenJDK 64-Bit Server VM (build 24.60-b09, mixed mode)


      ADDITIONAL OS VERSION INFORMATION :
      Linux xxx 3.14.4-200.fc20.x86_64 #1 SMP Tue May 13 13:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
      (This bug is (most probably) OS independent.)

      EXTRA RELEVANT SYSTEM CONFIGURATION :
      (This bug is configuration independent.)

      A DESCRIPTION OF THE PROBLEM :
      Rendering to a BufferedImage with a simple Graphics2D.fill(Shape) operation fails with a 'RasterFormatException' when Graphics2D has been configured to use a anti-aliasing and a custom composer.

      Analysis shows that the bug is in 'AAShapePipe' implementation that mismanages rendering tile boundary coordinates, and passes "off by one" values to IntegerInterleavedRaster.createChild(...) method, which then responds (correctly) by throwing a "(x + width) is outside raster" RasterFormatException.

      The bug is OpenJDK specific. The same operation works just fine with Oracle JDK implementation.

      Please see the documentation in the associated simple program for details.

      ADDITIONAL REGRESSION INFORMATION:
      (Not a regression.)

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Compile and then run the associated executable test case with OpenJDK 1.7.0.60 implementation, and observe how it fails with a RasterFormatException.

      (Please also run the test case with Oracle JDK 1.7.60, and observe how the operation does NOT fail.)

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The Graphics2D.fill(Shape) operation should return without failing with RasterFormatException.
      ACTUAL -
      The Graphics2D.fill(Shape) operation is carried out only partially, and after invoking the CustomCompositeContex.compose(...) just a few times, the fill operation fails with a RasterFormatException (outside the CustomCompositeContext).

      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)

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      (The bug does not cause a crash, just an unwarranted exception.)

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      OpenJDKFillBug.java:

      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[])
       */

      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
            }
          }
        }
      }

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

      CUSTOMER SUBMITTED WORKAROUND :
      Disabling anti-aliasing or a custom Composite avoids the problem because then the rendering is not internally directed to buggy 'sun.java2d.pipe.AAShapePipe' and 'sun.java2d.pisces.PiscesCache' classes.

      However, disabling anti-aliasing significantly degrades general rendering visual quality, and disabling custom Composite disables the advanced visual effects that standard Composite implementation just can't produce, so both workarounds are unacceptable in practice.

      Another workaround is to not to use OpenJDK at all, and switch to for example OracleJDK, which in this case works correctly.

      Yet another workaround is to surround the 'fill' operation with a try/catch, and after a failure just continue with the rest of the rendering, and accept fact that OpenJDK rendering fails and produces inferior visual results compared to OracleJDK.

      SUPPORT :
      YES

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                prr Philip Race
                Reporter:
                webbuggrp Webbug Group
              • Votes:
                0 Vote for this issue
                Watchers:
                1 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: