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

Exception thrown while stroking curves to Graphics2D

XMLWordPrintable

    • 2d
    • x86
    • linux

      ADDITIONAL SYSTEM INFORMATION :
      CentOS running java-1.8.0-openjdk-1.8.0.191.b12-0.el7_5.x86_64
      Error does not occur running SunMicrosystem JVM

      A DESCRIPTION OF THE PROBLEM :
      Code is drawing PDF downloaded from web. Error occurs in sun.java2d.pisces.Dasher.goTo(Dasher.java:151) which is called via sun.java2d.pipe.LoopPipe.getStrokeSpans(LoopPipe.java:278) . Problem occurs if either bezier curve control point is an endpoint, or if the two control points are the same, where "same" is defined as the same pixel, ie: (Math.abs(x0-x2) < OnePixel) && (Math.abs(y0-y2) < OnePixel)

      REGRESSION : Last worked in version 8u191

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Download https://www.credit-suisse.com/media/assets/corporate/docs/about-us/investor-relations/financial-disclosures/financial-reports/csg-ar-2016-en.pdf. Extract page 437 (penultimate page). Parse PDF and stroke paths. Note that many other PDF have similar problems (0.25% of PDF that in my database?)

      Simpler reproduction may be to hand-code a Java Path2D object with bezier curves with overlapping control points and endpoints. That code is attached.

      To mask the error, can take the path and convert SEG_CUBICTO to SEG_QUADTO to SEG_LINETO. This has been tested and works.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Path should be drawn.
      ACTUAL -
      Dec30 15:13, ID#7, Uncaught Error: Unable to Stroke shape (null)
                      java.lang.InternalError: Unable to Stroke shape (null)
                      at sun.java2d.pipe.LoopPipe.getStrokeSpans(LoopPipe.java:285)
                      at sun.java2d.pipe.LoopPipe.draw(LoopPipe.java:201)
                      at sun.java2d.pipe.PixelToParallelogramConverter.draw(PixelToParallelogramConverter.java:148)
                      at sun.java2d.SunGraphics2D.draw(SunGraphics2D.java:2497)
                      at com.funanalysis.pdf.ShapeHandler.stroke(ShapeHandler.java:328)
                      at com.funanalysis.pdf.ShapeHandler.access$20(ShapeHandler.java:315)
                      at com.funanalysis.pdf.ShapeHandler$OP_fsb.process(ShapeHandler.java:3146)
                      at com.funanalysis.pdf.PDFHandler.parse(PDFHandler.java:542)
                      at com.funanalysis.pdf.TextHandler.parse(TextHandler.java:248)
                      at com.funanalysis.pdf.TextLayout.layout(TextLayout.java:72)
                      at com.funanalysis.pdf.TextExtractor.getTextFrags(TextExtractor.java:99)
                      at com.funanalysis.pdf.TextExtractor.getText(TextExtractor.java:88)
                      at com.funanalysis.secparser.SecDocHandler.addText(SecDocHandler.java:441)
                      at com.funanalysis.secparser.SecDoc$PDFDocument.processEnd(SecDoc.java:1369)
                      at com.funanalysis.html.SgmlParser.popHandler(SgmlParser.java:805)
                      at com.funanalysis.html.BinaryHandler.processTag(BinaryHandler.java:97)
                      at com.funanalysis.html.SgmlParser.parseTag(SgmlParser.java:749)
                      at com.funanalysis.html.SgmlParser.parseHtml(SgmlParser.java:404)
                      at com.funanalysis.html.SgmlParser.getContent(SgmlParser.java:237)
                      at com.funanalysis.html.SgmlParser.getContent(SgmlParser.java:179)
                      at com.funanalysis.secparser.SecDoc.getContent(SecDoc.java:128)
                      at com.funanalysis.secparser.AbsSecDoc.getContent(AbsSecDoc.java:314)
                      at com.funanalysis.secparser.SecParser.parseDocs(SecParser.java:1281)
                      at com.funanalysis.secparser.SecParser.debug(SecParser.java:570)
                      at com.funanalysis.ui.App.doGet(App.java:286)
                      at javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
                      at com.funanalysis.servlet.FunServlet.service(FunServlet.java:308)
                      at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
                      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
                      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
                      at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
                      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
                      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
                      at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
                      at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
                      at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:610)
                      at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
                      at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
                      at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
                      at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
                      at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
                      at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1070)
                      at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:611)
                      at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
                      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
                      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
                      at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
                      at java.lang.Thread.run(Thread.java:748)
                     Caused by: java.lang.ArrayIndexOutOfBoundsException
                      at java.lang.System.arraycopy(Native Method)
                      at sun.java2d.pisces.Dasher.goTo(Dasher.java:151)
                      at sun.java2d.pisces.Dasher.somethingTo(Dasher.java:253)
                      at sun.java2d.pisces.Dasher.curveTo(Dasher.java:540)
                      at sun.java2d.pisces.TransformingPathConsumer2D$DeltaScaleFilter.curveTo(TransformingPathConsumer2D.java:314)
                      at sun.java2d.pipe.RenderingEngine.feedConsumer(RenderingEngine.java:373)
                      at sun.java2d.pisces.PiscesRenderingEngine.pathTo(PiscesRenderingEngine.java:484)
                      at sun.java2d.pisces.PiscesRenderingEngine.strokeTo(PiscesRenderingEngine.java:363)
                      at sun.java2d.pisces.PiscesRenderingEngine.strokeTo(PiscesRenderingEngine.java:163)
                      at sun.java2d.pisces.PiscesRenderingEngine.strokeTo(PiscesRenderingEngine.java:142)
                      at sun.java2d.pipe.LoopPipe.getStrokeSpans(LoopPipe.java:278)
                      ... 47 more


      ---------- BEGIN SOURCE ----------
      Simpler reproduction may be to hand-code the following Path2D, then draw it onto a canvas at 75dpi:
      BufferedImage image = new BufferedImage(649,838,BufferedImage.TYPE_INT_RGB);
      Graphics2D graphics = image.createGraphics();

      // Set rendering hints - but probably not needed graphics.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT);
      graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
      graphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_DEFAULT);
      graphics.setRenderingHint(RenderingHints.KEY_DITHERING,RenderingHints.VALUE_DITHER_DISABLE);
      graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
      graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
      graphics.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_SPEED);
      graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_DEFAULT);
      graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
      graphics.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST,240); // Range 100-250. Higher value boosts contrast of black ink

      // Add transform to convert from user-space (cropBox) to pixels (width&height).
      double scale = 0.001040694523285;
      AffineTransform xform = new AffineTransform(scale,0,0,scale,0,0)
      graphics.setTransform(xform);

      // Set stroke shape - default is probably OK
      float[] dash = { 1000000, 0 };
      graphics.setStroke(new BasicStroke(1000,0,0,4,dash,0);

      // Draw black on white background
      int[] buf = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
      Arrays.fill(buf,0xffffff);
      graphics.setColor(new Color(0));

      // Create path that fails and draw it.
      Path2D.Double path = getPath(); // See below
      graphics.draw(path);

      /** getPath
                 Create path with control points same as starting and/or ending point
                 Points that land on same final pixel value are "same" for purposes of failure.
       */
      private Path2D.Double getPath() {
               Path2D.Double = new Path2D.Double();
      path.moveTo(410663,735073.5);
      path.lineTo(410663,734939.5);
      path.curveTo(410470,735002.5,410234,735053.5,409953,735091.5);
      path.curveTo(409675,735127.5,409534,735245.5,409534,735444.5);
      path.curveTo(409534,735544.5,409573,735626.5,409648,735692.5);
      path.curveTo(409723,735755.5,409831,735789.5,409971,735789.5);
      path.curveTo(410155,735789.5,410316,735734.5,410454,735628.5);
      path.curveTo(410593,735519.5,410663,735333.5,410663,735073.5);
      path.curveTo(410593,735519.5,410663,735333.5,410663,735073.5);
      path.moveTo(411152,736029.5);
      path.lineTo(410765,736029.5);
      path.curveTo(410728,735961.5,410703,735870.5,410688,735761.5);
      path.curveTo(410441,735974.5,410171,736081.5,409883,736081.5);
      path.curveTo(409654,736081.5,409474,736022.5,409339,735909.5);
      path.curveTo(409206,735793.5,409140,735641.5,409140,735453.5);
      path.curveTo(409140,735272.5,409203,735125.5,409331,735009.5);
      path.curveTo(409460,734891.5,409681,734814.5,409999,734774.5);
      path.lineTo(410346,734724.5);
      path.curveTo(410473,734703.5,410579,734678.5,410663,734647.5);
      path.curveTo(410663,734538.5,410661,734459.5,410653,734414.5);
      path.curveTo(410647,734368.5,410626,734321.5,410593,734271.5);
      path.curveTo(410561,734221.5,410506,734180.5,410431,734153.5);
      path.curveTo(410354,734123.5,410257,734108.5,410133,734108.5);
      path.curveTo(409974,734108.5,409849,734138.5,409756,734196.5);
      path.curveTo(409666,734253.5,409600,734364.5,409566,734522.5);
      path.lineTo(409203,734475.5);
      path.curveTo(409244,734253.5,409346,734085.5,409509,733974.5);
      path.curveTo(409675,733861.5,409901,733806.5,410189,733806.5);
      path.curveTo(410450,733806.5,410643,733845.5,410765,733919.5);
      path.curveTo(410885,733997.5,410962,734089.5,410989,734200.5);
      path.curveTo(411018,734311.5,411032,734452.5,411032,734624.5);
      path.lineTo(411032,735118.5);
      path.curveTo(411032,735422.5,411039,735623.5,411050,735725.5);
      path.curveTo(411061,735827.5,411096,735929.5,411152,736029.5);
      path.curveTo(411061,735827.5,411096,735929.5,411152,736029.5);
               return path;
      }


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

      CUSTOMER SUBMITTED WORKAROUND :
      Replace "graphics.draw(Path2D path)" with a call to "stroke(graphics,path)"

      /** stroke
      * openjdk has bug trying to stroke cubic bezier where one of the control points is an endpoint
      * Mask the bug by converting to quadratic bezier with a single control point.
      *
      * eg: TGA, Cik#736744, SecDoc[0001062993-08-001254]. Embedded Exhibit99-1.pdf, pg11
      *
      * eg: Credit-Suisse, Cik#1053092,
      * https://www.credit-suisse.com/media/assets/corporate/docs/about-us/investor-relations/financial-disclosures/financial-reports/csg-ar-2016-en.pdf, pg437
      *
      * @param Graphics2D graphics
      * @param Path2D.Double path
      */
      private void stroke(Graphics2D graphics, Path2D.Double path) {
      try {
      printPath(null,path);
      graphics.draw(path);
      } catch (Throwable e) {
      if (false) printPath(e,path);

      double dx = 960; // 960 user-space units is one pixel in test code I supplied
      Path2D.Double path2 = new Path2D.Double();
      PathIterator iterator = path.getPathIterator(new AffineTransform());
      double[] coords = new double[6];
      double x=0,y=0;
      while (!iterator.isDone()) {
      int type = iterator.currentSegment(coords);
      if (type==PathIterator.SEG_MOVETO) {
      path2.moveTo(x=coords[0],y=coords[1]);
      } else if (type==PathIterator.SEG_LINETO) {
      path2.lineTo(x=coords[0],y=coords[1]);
      } else if (type==PathIterator.SEG_QUADTO) {
      if ((Math.abs(x-coords[0]) < dx) && (Math.abs(y-coords[1]) < dx)) {
      path2.lineTo(x=coords[2],y=coords[3]);
      } else if ((Math.abs(coords[0]-coords[2]) < dx) && (Math.abs(coords[1]-coords[3]) < dx)) {
      path2.lineTo(x=coords[2],y=coords[3]);
      } else {
      path2.quadTo(coords[0],coords[1],x=coords[2],y=coords[3]);
      }
      } else {
      if ((Math.abs(x-coords[0]) < dx) && (Math.abs(y-coords[1]) < dx)) {
      if ((Math.abs(coords[0]-coords[2]) < dx) && (Math.abs(coords[1]-coords[3]) < dx)) {
      path2.lineTo(x=coords[4],y=coords[5]);
      } else if ((Math.abs(coords[2]-coords[4]) < dx) && (Math.abs(coords[3]-coords[5]) < dx)) {
      path2.lineTo(x=coords[4],y=coords[5]);
      } else {
      path2.quadTo(coords[2],coords[3],x=coords[4],y=coords[5]);
      }
      } else if ((Math.abs(coords[0]-coords[2]) < dx) && (Math.abs(coords[1]-coords[3]) < dx)) {
      if ((Math.abs(coords[2]-coords[4]) < dx) && (Math.abs(coords[3]-coords[5]) < dx)) {
      path2.lineTo(x=coords[4],y=coords[5]);
      } else {
      path2.quadTo(coords[2],coords[3],x=coords[4],y=coords[5]);
      }
      } else if ((Math.abs(coords[2]-coords[4]) < dx) && (Math.abs(coords[3]-coords[5]) < dx)) {
      path2.quadTo(coords[0],coords[1],x=coords[4],y=coords[5]);
      } else {
      path2.curveTo(coords[0],coords[1],coords[2],coords[3],x=coords[4],y=coords[5]);
      }
      }
      iterator.next();
      }

      try {
      graphics.draw(path2);
      logger.log(Level.INFO,"Masked bug in openjdk graphics software");
      } catch (Throwable e2) {
      logger.log(Level.INFO,"Error stroking path. Ignoring path.");
      printPath(e2,path2);
      }
      }
      }

      /** printPath
      * @param Throwable e
      * @param StringBuffer16 msg
      * @param Path2D.Double path
      */
      private void printPath(Throwable e, Path2D.Double path) {
      StringBuffer16 msg = new StringBuffer16();
      if (e != null) msg.append(e.getMessage());
      if (path==null) {
      msg.append(": path==null");
      } else {
      msg.append(": path!=null.");
      PathIterator iterator = path.getPathIterator(new AffineTransform());
      double[] coords = new double[6];
      while (!iterator.isDone()) {
      int type = iterator.currentSegment(coords);
      if (type==PathIterator.SEG_MOVETO) {
      msg.append("\nSEG_MOVETO <").append(coords[0]).append(',').append(coords[1]).append('>');
      } else if (type==PathIterator.SEG_LINETO) {
      msg.append("\nSEG_LINETO <").append(coords[0]).append(',').append(coords[1]).append('>');
      } else if (type==PathIterator.SEG_QUADTO) {
      msg.append("\nSEG_QUADTO <").append(coords[0]).append(',').append(coords[1]).append('>');
      msg.append(", <").append(coords[2]).append(',').append(coords[3]).append('>');
      } else {
      msg.append("\nSEG_CUBICTO <").append(coords[0]).append(',').append(coords[1]).append('>');
      msg.append(", <").append(coords[2]).append(',').append(coords[3]).append('>');
      msg.append(", <").append(coords[4]).append(',').append(coords[5]).append('>');
      }
      iterator.next();
      }
      }
      logger.log(Level.INFO,msg.toString());
      }



      FREQUENCY : always


            pardesha Pardeep Sharma
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: