< prev index next >

modules/javafx.graphics/src/main/java/com/sun/marlin/TransformingPathConsumer2D.java

Print this page


   1 /*
   2  * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.marlin;
  27 
  28 import com.sun.javafx.geom.Path2D;
  29 import com.sun.javafx.geom.PathConsumer2D;
  30 import com.sun.javafx.geom.transform.BaseTransform;
  31 import com.sun.marlin.Helpers.IndexStack;
  32 import com.sun.marlin.Helpers.PolyStack;

  33 
  34 public final class TransformingPathConsumer2D {
  35 



  36     private final RendererContext rdrCtx;
  37 
  38     // recycled ClosedPathDetector instance from detectClosedPath()
  39     private final ClosedPathDetector   cpDetector;
  40 
  41     // recycled PathClipFilter instance from pathClipper()
  42     private final PathClipFilter       pathClipper;
  43 
  44     // recycled PathConsumer2D instance from wrapPath2D()
  45     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
  46 
  47     // recycled PathConsumer2D instances from deltaTransformConsumer()
  48     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
  49     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
  50 
  51     // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
  52     private final DeltaScaleFilter     iv_DeltaScaleFilter     = new DeltaScaleFilter();
  53     private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
  54 
  55     // recycled PathTracer instances from tracer...() methods
  56     private final PathTracer tracerInput      = new PathTracer("[Input]");
  57     private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
  58     private final PathTracer tracerFiller     = new PathTracer("Filler");
  59     private final PathTracer tracerStroker    = new PathTracer("Stroker");

  60 
  61     TransformingPathConsumer2D(final RendererContext rdrCtx) {
  62         // used by RendererContext
  63         this.rdrCtx = rdrCtx;
  64         this.cpDetector = new ClosedPathDetector(rdrCtx);
  65         this.pathClipper = new PathClipFilter(rdrCtx);
  66     }
  67 
  68     public PathConsumer2D wrapPath2D(Path2D p2d) {
  69         return wp_Path2DWrapper.init(p2d);
  70     }
  71 
  72     public PathConsumer2D traceInput(PathConsumer2D out) {
  73         return tracerInput.init(out);
  74     }
  75 
  76     public PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
  77         return tracerCPDetector.init(out);
  78     }
  79 
  80     public PathConsumer2D traceFiller(PathConsumer2D out) {
  81         return tracerFiller.init(out);
  82     }
  83 
  84     public PathConsumer2D traceStroker(PathConsumer2D out) {
  85         return tracerStroker.init(out);
  86     }
  87 




  88     public PathConsumer2D detectClosedPath(PathConsumer2D out) {
  89         return cpDetector.init(out);
  90     }
  91 
  92     public PathConsumer2D pathClipper(PathConsumer2D out,
  93                                       final float rdrOffX,
  94                                       final float rdrOffY)
  95     {
  96         return pathClipper.init(out, rdrOffX, rdrOffY);
  97     }
  98 
  99     public PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
 100                                                  BaseTransform at,
 101                                                  final float rdrOffX,
 102                                                  final float rdrOffY)
 103     {
 104         if (at == null) {
 105             return out;
 106         }
 107         final float mxx = (float) at.getMxx();


 469     static final class PathClipFilter implements PathConsumer2D {
 470 
 471         private PathConsumer2D out;
 472 
 473         // Bounds of the drawing region, at pixel precision.
 474         private final float[] clipRect;
 475 
 476         private final float[] corners = new float[8];
 477         private boolean init_corners = false;
 478 
 479         private final IndexStack stack;
 480 
 481         // the current outcode of the current sub path
 482         private int cOutCode = 0;
 483 
 484         // the cumulated (and) outcode of the complete path
 485         private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 486 
 487         private boolean outside = false;
 488 
 489         // The current point OUTSIDE
 490         private float cx0, cy0;
 491 






 492         PathClipFilter(final RendererContext rdrCtx) {
 493             this.clipRect = rdrCtx.clipRect;


 494             this.stack = (rdrCtx.stats != null) ?
 495                 new IndexStack(rdrCtx,
 496                         rdrCtx.stats.stat_pcf_idxstack_indices,
 497                         rdrCtx.stats.hist_pcf_idxstack_indices,
 498                         rdrCtx.stats.stat_array_pcf_idxstack_indices)
 499                 : new IndexStack(rdrCtx);
 500         }
 501 
 502         PathClipFilter init(final PathConsumer2D out,
 503                             final double rdrOffX,
 504                             final double rdrOffY)
 505         {
 506             this.out = out;
 507 
 508             // add a small rounding error:
 509             final float margin = 1e-3f;
 510 
 511             final float[] _clipRect = this.clipRect;
 512             // Adjust the clipping rectangle with the renderer offsets
 513             _clipRect[0] -= margin - rdrOffY;
 514             _clipRect[1] += margin + rdrOffY;
 515             _clipRect[2] -= margin - rdrOffX;
 516             _clipRect[3] += margin + rdrOffX;
 517 





 518             this.init_corners = true;
 519             this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 520 
 521             return this; // fluent API
 522         }
 523 
 524         /**
 525          * Disposes this instance:
 526          * clean up before reusing this instance
 527          */
 528         void dispose() {
 529             stack.dispose();
 530         }
 531 
 532         private void finishPath() {
 533             if (outside) {
 534                 // criteria: inside or totally outside ?
 535                 if (gOutCode == 0) {
 536                     finish();
 537                 } else {


 548                 if (init_corners) {
 549                     init_corners = false;
 550 
 551                     final float[] _corners = corners;
 552                     final float[] _clipRect = clipRect;
 553                     // Top Left (0):
 554                     _corners[0] = _clipRect[2];
 555                     _corners[1] = _clipRect[0];
 556                     // Bottom Left (1):
 557                     _corners[2] = _clipRect[2];
 558                     _corners[3] = _clipRect[1];
 559                     // Top right (2):
 560                     _corners[4] = _clipRect[3];
 561                     _corners[5] = _clipRect[0];
 562                     // Bottom Right (3):
 563                     _corners[6] = _clipRect[3];
 564                     _corners[7] = _clipRect[1];
 565                 }
 566                 stack.pullAll(corners, out);
 567             }
 568             out.lineTo(cx0, cy0);


 569         }
 570 
 571         @Override
 572         public void pathDone() {
 573             finishPath();
 574 
 575             out.pathDone();
 576 
 577             // TODO: fix possible leak if exception happened
 578             // Dispose this instance:
 579             dispose();
 580         }
 581 
 582         @Override
 583         public void closePath() {
 584             finishPath();
 585 
 586             out.closePath();
 587         }
 588 
 589         @Override
 590         public void moveTo(final float x0, final float y0) {
 591             finishPath();
 592 
 593             final int outcode = Helpers.outcode(x0, y0, clipRect);
 594             this.cOutCode = outcode;
 595             this.outside = false;
 596             out.moveTo(x0, y0);


 597         }
 598 
 599         @Override
 600         public void lineTo(final float xe, final float ye) {
 601             final int outcode0 = this.cOutCode;
 602             final int outcode1 = Helpers.outcode(xe, ye, clipRect);
 603             this.cOutCode = outcode1;
 604 
 605             final int sideCode = (outcode0 & outcode1);



 606 
 607             // basic rejection criteria:
 608             if (sideCode == 0) {
 609                 this.gOutCode = 0;
 610             } else {
 611                 this.gOutCode &= sideCode;
 612                 // keep last point coordinate before entering the clip again:
 613                 this.outside = true;
 614                 this.cx0 = xe;
 615                 this.cy0 = ye;




















 616 
 617                 clip(sideCode, outcode0, outcode1);
 618                 return;

 619             }




 620             if (outside) {
 621                 finish();
 622             }
 623             // clipping disabled:
 624             out.lineTo(xe, ye);


 625         }
 626 
 627         private void clip(final int sideCode,
 628                           final int outcode0,
 629                           final int outcode1)
 630         {
 631             // corner or cross-boundary on left or right side:
 632             if ((outcode0 != outcode1)
 633                     && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
 634             {
 635                 // combine outcodes:
 636                 final int mergeCode = (outcode0 | outcode1);
 637                 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
 638                 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
 639                 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
 640 
 641                 // add corners to outside stack:
 642                 switch (tbCode) {
 643                     case MarlinConst.OUTCODE_TOP:
 644 // System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
 645                         stack.push(off); // top
 646                         return;
 647                     case MarlinConst.OUTCODE_BOTTOM:
 648 // System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
 649                         stack.push(off + 1); // bottom
 650                         return;
 651                     default:
 652                         // both TOP / BOTTOM:
 653                         if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
 654 // System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
 655                             // top to bottom
 656                             stack.push(off); // top
 657                             stack.push(off + 1); // bottom
 658                         } else {
 659 // System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
 660                             // bottom to top
 661                             stack.push(off + 1); // bottom
 662                             stack.push(off); // top
 663                         }
 664                 }
 665             }
 666         }
 667 
 668         @Override
 669         public void curveTo(final float x1, final float y1,
 670                             final float x2, final float y2,
 671                             final float xe, final float ye)
 672         {
 673             final int outcode0 = this.cOutCode;


 674             final int outcode3 = Helpers.outcode(xe, ye, clipRect);
 675             this.cOutCode = outcode3;
 676 
 677             int sideCode = outcode0 & outcode3;
 678 
 679             if (sideCode == 0) {
 680                 this.gOutCode = 0;
 681             } else {
 682                 sideCode &= Helpers.outcode(x1, y1, clipRect);
 683                 sideCode &= Helpers.outcode(x2, y2, clipRect);
 684                 this.gOutCode &= sideCode;
 685 
 686                 // basic rejection criteria:
 687                 if (sideCode != 0) {

























 688                     // keep last point coordinate before entering the clip again:
 689                     this.outside = true;
 690                     this.cx0 = xe;
 691                     this.cy0 = ye;
 692 
 693                     clip(sideCode, outcode0, outcode3);
 694                     return;
 695                 }
 696             }




 697             if (outside) {
 698                 finish();
 699             }
 700             // clipping disabled:
 701             out.curveTo(x1, y1, x2, y2, xe, ye);


 702         }
 703 
 704         @Override
 705         public void quadTo(final float x1, final float y1,
 706                            final float xe, final float ye)
 707         {
 708             final int outcode0 = this.cOutCode;

 709             final int outcode2 = Helpers.outcode(xe, ye, clipRect);
 710             this.cOutCode = outcode2;
 711 
 712             int sideCode = outcode0 & outcode2;
 713 
 714             if (sideCode == 0) {
 715                 this.gOutCode = 0;
 716             } else {
 717                 sideCode &= Helpers.outcode(x1, y1, clipRect);
 718                 this.gOutCode &= sideCode;
 719 
 720                 // basic rejection criteria:
 721                 if (sideCode != 0) {























 722                     // keep last point coordinate before entering the clip again:
 723                     this.outside = true;
 724                     this.cx0 = xe;
 725                     this.cy0 = ye;
 726 
 727                     clip(sideCode, outcode0, outcode2);
 728                     return;
 729                 }
 730             }




 731             if (outside) {
 732                 finish();
 733             }
 734             // clipping disabled:
 735             out.quadTo(x1, y1, xe, ye);

































































































































































































































































 736         }
 737     }
 738 
 739     static final class PathTracer implements PathConsumer2D {
 740         private final String prefix;
 741         private PathConsumer2D out;
 742 
 743         PathTracer(String name) {
 744             this.prefix = name + ": ";
 745         }
 746 
 747         PathTracer init(PathConsumer2D out) {
 748             this.out = out;
 749             return this; // fluent API
 750         }
 751 
 752         @Override
 753         public void moveTo(float x0, float y0) {
 754             log("moveTo (" + x0 + ", " + y0 + ')');
 755             out.moveTo(x0, y0);


 772 
 773         @Override
 774         public void quadTo(float x1, float y1, float x2, float y2) {
 775             log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ')');
 776             out.quadTo(x1, y1, x2, y2);
 777         }
 778 
 779         @Override
 780         public void closePath() {
 781             log("closePath");
 782             out.closePath();
 783         }
 784 
 785         @Override
 786         public void pathDone() {
 787             log("pathDone");
 788             out.pathDone();
 789         }
 790 
 791         private void log(final String message) {
 792             System.out.println(prefix + message);
 793         }
 794     }
 795 }
   1 /*
   2  * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.marlin;
  27 
  28 import com.sun.javafx.geom.Path2D;
  29 import com.sun.javafx.geom.PathConsumer2D;
  30 import com.sun.javafx.geom.transform.BaseTransform;
  31 import com.sun.marlin.Helpers.IndexStack;
  32 import com.sun.marlin.Helpers.PolyStack;
  33 import java.util.Arrays;
  34 
  35 public final class TransformingPathConsumer2D {
  36 
  37     // higher uncertainty in float variant for huge shapes > 10^7
  38     static final float CLIP_RECT_PADDING = 1.0f;
  39 
  40     private final RendererContext rdrCtx;
  41 
  42     // recycled ClosedPathDetector instance from detectClosedPath()
  43     private final ClosedPathDetector   cpDetector;
  44 
  45     // recycled PathClipFilter instance from pathClipper()
  46     private final PathClipFilter       pathClipper;
  47 
  48     // recycled PathConsumer2D instance from wrapPath2D()
  49     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
  50 
  51     // recycled PathConsumer2D instances from deltaTransformConsumer()
  52     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
  53     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
  54 
  55     // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
  56     private final DeltaScaleFilter     iv_DeltaScaleFilter     = new DeltaScaleFilter();
  57     private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
  58 
  59     // recycled PathTracer instances from tracer...() methods
  60     private final PathTracer tracerInput      = new PathTracer("[Input]");
  61     private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
  62     private final PathTracer tracerFiller     = new PathTracer("Filler");
  63     private final PathTracer tracerStroker    = new PathTracer("Stroker");
  64     private final PathTracer tracerDasher     = new PathTracer("Dasher");
  65 
  66     TransformingPathConsumer2D(final RendererContext rdrCtx) {
  67         // used by RendererContext
  68         this.rdrCtx = rdrCtx;
  69         this.cpDetector = new ClosedPathDetector(rdrCtx);
  70         this.pathClipper = new PathClipFilter(rdrCtx);
  71     }
  72 
  73     public PathConsumer2D wrapPath2D(Path2D p2d) {
  74         return wp_Path2DWrapper.init(p2d);
  75     }
  76 
  77     public PathConsumer2D traceInput(PathConsumer2D out) {
  78         return tracerInput.init(out);
  79     }
  80 
  81     public PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
  82         return tracerCPDetector.init(out);
  83     }
  84 
  85     public PathConsumer2D traceFiller(PathConsumer2D out) {
  86         return tracerFiller.init(out);
  87     }
  88 
  89     public PathConsumer2D traceStroker(PathConsumer2D out) {
  90         return tracerStroker.init(out);
  91     }
  92 
  93     public PathConsumer2D traceDasher(PathConsumer2D out) {
  94         return tracerDasher.init(out);
  95     }
  96 
  97     public PathConsumer2D detectClosedPath(PathConsumer2D out) {
  98         return cpDetector.init(out);
  99     }
 100 
 101     public PathConsumer2D pathClipper(PathConsumer2D out,
 102                                       final float rdrOffX,
 103                                       final float rdrOffY)
 104     {
 105         return pathClipper.init(out, rdrOffX, rdrOffY);
 106     }
 107 
 108     public PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
 109                                                  BaseTransform at,
 110                                                  final float rdrOffX,
 111                                                  final float rdrOffY)
 112     {
 113         if (at == null) {
 114             return out;
 115         }
 116         final float mxx = (float) at.getMxx();


 478     static final class PathClipFilter implements PathConsumer2D {
 479 
 480         private PathConsumer2D out;
 481 
 482         // Bounds of the drawing region, at pixel precision.
 483         private final float[] clipRect;
 484 
 485         private final float[] corners = new float[8];
 486         private boolean init_corners = false;
 487 
 488         private final IndexStack stack;
 489 
 490         // the current outcode of the current sub path
 491         private int cOutCode = 0;
 492 
 493         // the cumulated (and) outcode of the complete path
 494         private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 495 
 496         private boolean outside = false;
 497 
 498         // The current point (TODO stupid repeated info)
 499         private float cx0, cy0;
 500 
 501         // The current point OUTSIDE
 502         private float cox0, coy0;
 503 
 504         private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
 505         private final CurveClipSplitter curveSplitter;
 506 
 507         PathClipFilter(final RendererContext rdrCtx) {
 508             this.clipRect = rdrCtx.clipRect;
 509             this.curveSplitter = rdrCtx.curveClipSplitter;
 510 
 511             this.stack = (rdrCtx.stats != null) ?
 512                 new IndexStack(rdrCtx,
 513                         rdrCtx.stats.stat_pcf_idxstack_indices,
 514                         rdrCtx.stats.hist_pcf_idxstack_indices,
 515                         rdrCtx.stats.stat_array_pcf_idxstack_indices)
 516                 : new IndexStack(rdrCtx);
 517         }
 518 
 519         PathClipFilter init(final PathConsumer2D out,
 520                             final double rdrOffX,
 521                             final double rdrOffY)
 522         {
 523             this.out = out;
 524 
 525             // add a small rounding error:
 526             final float margin = 1e-3f;
 527 
 528             final float[] _clipRect = this.clipRect;
 529             // Adjust the clipping rectangle with the renderer offsets
 530             _clipRect[0] -= margin - rdrOffY;
 531             _clipRect[1] += margin + rdrOffY;
 532             _clipRect[2] -= margin - rdrOffX;
 533             _clipRect[3] += margin + rdrOffX;
 534 
 535             if (MarlinConst.DO_CLIP_SUBDIVIDER) {
 536                 // adjust padded clip rectangle:
 537                 curveSplitter.init();
 538             }
 539 
 540             this.init_corners = true;
 541             this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 542 
 543             return this; // fluent API
 544         }
 545 
 546         /**
 547          * Disposes this instance:
 548          * clean up before reusing this instance
 549          */
 550         void dispose() {
 551             stack.dispose();
 552         }
 553 
 554         private void finishPath() {
 555             if (outside) {
 556                 // criteria: inside or totally outside ?
 557                 if (gOutCode == 0) {
 558                     finish();
 559                 } else {


 570                 if (init_corners) {
 571                     init_corners = false;
 572 
 573                     final float[] _corners = corners;
 574                     final float[] _clipRect = clipRect;
 575                     // Top Left (0):
 576                     _corners[0] = _clipRect[2];
 577                     _corners[1] = _clipRect[0];
 578                     // Bottom Left (1):
 579                     _corners[2] = _clipRect[2];
 580                     _corners[3] = _clipRect[1];
 581                     // Top right (2):
 582                     _corners[4] = _clipRect[3];
 583                     _corners[5] = _clipRect[0];
 584                     // Bottom Right (3):
 585                     _corners[6] = _clipRect[3];
 586                     _corners[7] = _clipRect[1];
 587                 }
 588                 stack.pullAll(corners, out);
 589             }
 590             out.lineTo(cox0, coy0);
 591             this.cx0 = cox0;
 592             this.cy0 = coy0;
 593         }
 594 
 595         @Override
 596         public void pathDone() {
 597             finishPath();
 598 
 599             out.pathDone();
 600 
 601             // TODO: fix possible leak if exception happened
 602             // Dispose this instance:
 603             dispose();
 604         }
 605 
 606         @Override
 607         public void closePath() {
 608             finishPath();
 609 
 610             out.closePath();
 611         }
 612 
 613         @Override
 614         public void moveTo(final float x0, final float y0) {
 615             finishPath();
 616 
 617             this.cOutCode = Helpers.outcode(x0, y0, clipRect);

 618             this.outside = false;
 619             out.moveTo(x0, y0);
 620             this.cx0 = x0;
 621             this.cy0 = y0;
 622         }
 623 
 624         @Override
 625         public void lineTo(final float xe, final float ye) {
 626             final int outcode0 = this.cOutCode;
 627             final int outcode1 = Helpers.outcode(xe, ye, clipRect);

 628 
 629             // Should clip
 630             final int orCode = (outcode0 | outcode1);
 631             if (orCode != 0) {
 632                 final int sideCode = (outcode0 & outcode1);
 633 
 634                 // basic rejection criteria:
 635                 if (sideCode == 0) {
 636                     // ovelap clip:
 637                     if (subdivide) {
 638                         // avoid reentrance
 639                         subdivide = false;
 640                         boolean ret;
 641                         // subdivide curve => callback with subdivided parts:
 642                         if (outside) {
 643                             ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
 644                                                           orCode, this);
 645                         } else {
 646                             ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
 647                                                           orCode, this);
 648                         }
 649                         // reentrance is done:
 650                         subdivide = true;
 651                         if (ret) {
 652                             return;
 653                         }
 654                     }
 655                     // already subdivided so render it
 656                 } else {
 657                     this.cOutCode = outcode1;
 658                     this.gOutCode &= sideCode;
 659                     // keep last point coordinate before entering the clip again:
 660                     this.outside = true;
 661                     this.cox0 = xe;
 662                     this.coy0 = ye;
 663 
 664                     clip(sideCode, outcode0, outcode1);
 665                     return;
 666                 }
 667             }
 668 
 669             this.cOutCode = outcode1;
 670             this.gOutCode = 0;
 671 
 672             if (outside) {
 673                 finish();
 674             }
 675             // clipping disabled:
 676             out.lineTo(xe, ye);
 677             this.cx0 = xe;
 678             this.cy0 = ye;
 679         }
 680 
 681         private void clip(final int sideCode,
 682                           final int outcode0,
 683                           final int outcode1)
 684         {
 685             // corner or cross-boundary on left or right side:
 686             if ((outcode0 != outcode1)
 687                     && ((sideCode & MarlinConst.OUTCODE_MASK_L_R) != 0))
 688             {
 689                 // combine outcodes:
 690                 final int mergeCode = (outcode0 | outcode1);
 691                 final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
 692                 final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
 693                 final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
 694 
 695                 // add corners to outside stack:
 696                 switch (tbCode) {
 697                     case MarlinConst.OUTCODE_TOP:

 698                         stack.push(off); // top
 699                         return;
 700                     case MarlinConst.OUTCODE_BOTTOM:

 701                         stack.push(off + 1); // bottom
 702                         return;
 703                     default:
 704                         // both TOP / BOTTOM:
 705                         if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {

 706                             // top to bottom
 707                             stack.push(off); // top
 708                             stack.push(off + 1); // bottom
 709                         } else {

 710                             // bottom to top
 711                             stack.push(off + 1); // bottom
 712                             stack.push(off); // top
 713                         }
 714                 }
 715             }
 716         }
 717 
 718         @Override
 719         public void curveTo(final float x1, final float y1,
 720                             final float x2, final float y2,
 721                             final float xe, final float ye)
 722         {
 723             final int outcode0 = this.cOutCode;
 724             final int outcode1 = Helpers.outcode(x1, y1, clipRect);
 725             final int outcode2 = Helpers.outcode(x2, y2, clipRect);
 726             final int outcode3 = Helpers.outcode(xe, ye, clipRect);



 727 
 728             // Should clip
 729             final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
 730             if (orCode != 0) {
 731                 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;


 732 
 733                 // basic rejection criteria:
 734                 if (sideCode == 0) {
 735                     // ovelap clip:
 736                     if (subdivide) {
 737                         // avoid reentrance
 738                         subdivide = false;
 739                         // subdivide curve => callback with subdivided parts:
 740                         boolean ret;
 741                         if (outside) {
 742                             ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
 743                                                            x2, y2, xe, ye,
 744                                                            orCode, this);
 745                         } else {
 746                             ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
 747                                                            x2, y2, xe, ye,
 748                                                            orCode, this);
 749                         }
 750                         // reentrance is done:
 751                         subdivide = true;
 752                         if (ret) {
 753                             return;
 754                         }
 755                     }
 756                     // already subdivided so render it
 757                 } else {
 758                     this.cOutCode = outcode3;
 759                     this.gOutCode &= sideCode;
 760                     // keep last point coordinate before entering the clip again:
 761                     this.outside = true;
 762                     this.cox0 = xe;
 763                     this.coy0 = ye;
 764 
 765                     clip(sideCode, outcode0, outcode3);
 766                     return;
 767                 }
 768             }
 769 
 770             this.cOutCode = outcode3;
 771             this.gOutCode = 0;
 772 
 773             if (outside) {
 774                 finish();
 775             }
 776             // clipping disabled:
 777             out.curveTo(x1, y1, x2, y2, xe, ye);
 778             this.cx0 = xe;
 779             this.cy0 = ye;
 780         }
 781 
 782         @Override
 783         public void quadTo(final float x1, final float y1,
 784                            final float xe, final float ye)
 785         {
 786             final int outcode0 = this.cOutCode;
 787             final int outcode1 = Helpers.outcode(x1, y1, clipRect);
 788             final int outcode2 = Helpers.outcode(xe, ye, clipRect);



 789 
 790             // Should clip
 791             final int orCode = (outcode0 | outcode1 | outcode2);
 792             if (orCode != 0) {
 793                 final int sideCode = outcode0 & outcode1 & outcode2;

 794 
 795                 // basic rejection criteria:
 796                 if (sideCode == 0) {
 797                     // ovelap clip:
 798                     if (subdivide) {
 799                         // avoid reentrance
 800                         subdivide = false;
 801                         // subdivide curve => callback with subdivided parts:
 802                         boolean ret;
 803                         if (outside) {
 804                             ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
 805                                                           xe, ye, orCode, this);
 806                         } else {
 807                             ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
 808                                                           xe, ye, orCode, this);
 809                         }
 810                         // reentrance is done:
 811                         subdivide = true;
 812                         if (ret) {
 813                             return;
 814                         }
 815                     }
 816                     // already subdivided so render it
 817                 } else {
 818                     this.cOutCode = outcode2;
 819                     this.gOutCode &= sideCode;
 820                     // keep last point coordinate before entering the clip again:
 821                     this.outside = true;
 822                     this.cox0 = xe;
 823                     this.coy0 = ye;
 824 
 825                     clip(sideCode, outcode0, outcode2);
 826                     return;
 827                 }
 828             }
 829 
 830             this.cOutCode = outcode2;
 831             this.gOutCode = 0;
 832 
 833             if (outside) {
 834                 finish();
 835             }
 836             // clipping disabled:
 837             out.quadTo(x1, y1, xe, ye);
 838             this.cx0 = xe;
 839             this.cy0 = ye;
 840         }
 841     }
 842 
 843     static final class CurveClipSplitter {
 844 
 845         static final float LEN_TH = MarlinProperties.getSubdividerMinLength();
 846         static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0f);
 847 
 848         private static final boolean TRACE = false;
 849 
 850         private static final int MAX_N_CURVES = 3 * 4;
 851 
 852         // clip rectangle (ymin, ymax, xmin, xmax):
 853         final float[] clipRect;
 854 
 855         // clip rectangle (ymin, ymax, xmin, xmax) including padding:
 856         final float[] clipRectPad = new float[4];
 857         private boolean init_clipRectPad = false;
 858 
 859         // This is where the curve to be processed is put. We give it
 860         // enough room to store all curves.
 861         final float[] middle = new float[MAX_N_CURVES * 8 + 2];
 862         // t values at subdivision points
 863         private final float[] subdivTs = new float[MAX_N_CURVES];
 864 
 865         // dirty curve
 866         private final Curve curve;
 867 
 868         CurveClipSplitter(final RendererContext rdrCtx) {
 869             this.clipRect = rdrCtx.clipRect;
 870             this.curve = rdrCtx.curve;
 871         }
 872 
 873         void init() {
 874             this.init_clipRectPad = true;
 875         }
 876 
 877         private void initPaddedClip() {
 878             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
 879             // adjust padded clip rectangle (ymin, ymax, xmin, xmax):
 880             // add a rounding error (curve subdivision ~ 0.1px):
 881             final float[] _clipRect = clipRect;
 882             final float[] _clipRectPad = clipRectPad;
 883 
 884             _clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
 885             _clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
 886             _clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;
 887             _clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;
 888 
 889             if (TRACE) {
 890                 MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "
 891                                         + "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");
 892             }
 893         }
 894 
 895         boolean splitLine(final float x0, final float y0,
 896                           final float x1, final float y1,
 897                           final int outCodeOR,
 898                           final PathConsumer2D out)
 899         {
 900             if (TRACE) {
 901                 MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
 902             }
 903 
 904             if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
 905                 return false;
 906             }
 907 
 908             final float[] mid = middle;
 909             mid[0] = x0;  mid[1] = y0;
 910             mid[2] = x1;  mid[3] = y1;
 911 
 912             return subdivideAtIntersections(4, outCodeOR, out);
 913         }
 914 
 915         boolean splitQuad(final float x0, final float y0,
 916                           final float x1, final float y1,
 917                           final float x2, final float y2,
 918                           final int outCodeOR,
 919                           final PathConsumer2D out)
 920         {
 921             if (TRACE) {
 922                 MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
 923             }
 924 
 925             if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
 926                 return false;
 927             }
 928 
 929             final float[] mid = middle;
 930             mid[0] = x0;  mid[1] = y0;
 931             mid[2] = x1;  mid[3] = y1;
 932             mid[4] = x2;  mid[5] = y2;
 933 
 934             return subdivideAtIntersections(6, outCodeOR, out);
 935         }
 936 
 937         boolean splitCurve(final float x0, final float y0,
 938                            final float x1, final float y1,
 939                            final float x2, final float y2,
 940                            final float x3, final float y3,
 941                            final int outCodeOR,
 942                            final PathConsumer2D out)
 943         {
 944             if (TRACE) {
 945                 MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
 946             }
 947 
 948             if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
 949                 return false;
 950             }
 951 
 952             final float[] mid = middle;
 953             mid[0] = x0;  mid[1] = y0;
 954             mid[2] = x1;  mid[3] = y1;
 955             mid[4] = x2;  mid[5] = y2;
 956             mid[6] = x3;  mid[7] = y3;
 957 
 958             return subdivideAtIntersections(8, outCodeOR, out);
 959         }
 960 
 961         private boolean subdivideAtIntersections(final int type, final int outCodeOR,
 962                                                  final PathConsumer2D out)
 963         {
 964             final float[] mid = middle;
 965             final float[] subTs = subdivTs;
 966 
 967             if (init_clipRectPad) {
 968                 init_clipRectPad = false;
 969                 initPaddedClip();
 970             }
 971 
 972             final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type,
 973                                                         outCodeOR, clipRectPad);
 974 
 975             if (TRACE) {
 976                 MarlinUtils.logInfo("nSplits: "+ nSplits);
 977                 MarlinUtils.logInfo("subTs: " + Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));
 978             }
 979             if (nSplits == 0) {
 980                 // only curve support shortcut
 981                 return false;
 982             }
 983             float prevT = 0.0f;
 984 
 985             for (int i = 0, off = 0; i < nSplits; i++, off += type) {
 986                 final float t = subTs[i];
 987 
 988                 Helpers.subdivideAt((t - prevT) / (1.0f - prevT),
 989                                      mid, off, mid, off, type);
 990                 prevT = t;
 991             }
 992 
 993             for (int i = 0, off = 0; i <= nSplits; i++, off += type) {
 994                 if (TRACE) {
 995                     MarlinUtils.logInfo("Part Curve " + Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));
 996                 }
 997                 emitCurrent(type, mid, off, out);
 998             }
 999             return true;
1000         }
1001 
1002         static void emitCurrent(final int type, final float[] pts,
1003                                 final int off, final PathConsumer2D out)
1004         {
1005             // if instead of switch (perf + most probable cases first)
1006             if (type == 8) {
1007                 out.curveTo(pts[off + 2], pts[off + 3],
1008                             pts[off + 4], pts[off + 5],
1009                             pts[off + 6], pts[off + 7]);
1010             } else if (type == 4) {
1011                 out.lineTo(pts[off + 2], pts[off + 3]);
1012             } else {
1013                 out.quadTo(pts[off + 2], pts[off + 3],
1014                            pts[off + 4], pts[off + 5]);
1015             }
1016         }
1017     }
1018 
1019     public static final class CurveBasicMonotonizer {
1020 
1021         private static final int MAX_N_CURVES = 11;
1022 
1023         // squared half line width (for stroker)
1024         private float lw2;
1025 
1026         // number of splitted curves
1027         int nbSplits;
1028 
1029         // This is where the curve to be processed is put. We give it
1030         // enough room to store all curves.
1031         final float[] middle = new float[MAX_N_CURVES * 6 + 2];
1032         // t values at subdivision points
1033         private final float[] subdivTs = new float[MAX_N_CURVES - 1];
1034 
1035         // dirty curve
1036         private final Curve curve;
1037 
1038         CurveBasicMonotonizer(final RendererContext rdrCtx) {
1039             this.curve = rdrCtx.curve;
1040         }
1041 
1042         public void init(final float lineWidth) {
1043             this.lw2 = (lineWidth * lineWidth) / 4.0f;
1044         }
1045 
1046         CurveBasicMonotonizer curve(final float x0, final float y0,
1047                                     final float x1, final float y1,
1048                                     final float x2, final float y2,
1049                                     final float x3, final float y3)
1050         {
1051             final float[] mid = middle;
1052             mid[0] = x0;  mid[1] = y0;
1053             mid[2] = x1;  mid[3] = y1;
1054             mid[4] = x2;  mid[5] = y2;
1055             mid[6] = x3;  mid[7] = y3;
1056 
1057             final float[] subTs = subdivTs;
1058             final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
1059 
1060             float prevT = 0.0f;
1061             for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1062                 final float t = subTs[i];
1063 
1064                 Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
1065                                           mid, off, mid, off, off + 6);
1066                 prevT = t;
1067             }
1068 
1069             this.nbSplits = nSplits;
1070             return this;
1071         }
1072 
1073         CurveBasicMonotonizer quad(final float x0, final float y0,
1074                                    final float x1, final float y1,
1075                                    final float x2, final float y2)
1076         {
1077             final float[] mid = middle;
1078             mid[0] = x0;  mid[1] = y0;
1079             mid[2] = x1;  mid[3] = y1;
1080             mid[4] = x2;  mid[5] = y2;
1081 
1082             final float[] subTs = subdivTs;
1083             final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
1084 
1085             float prevt = 0.0f;
1086             for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1087                 final float t = subTs[i];
1088                 Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
1089                                          mid, off, mid, off, off + 4);
1090                 prevt = t;
1091             }
1092 
1093             this.nbSplits = nSplits;
1094             return this;
1095         }
1096     }
1097 
1098     static final class PathTracer implements PathConsumer2D {
1099         private final String prefix;
1100         private PathConsumer2D out;
1101 
1102         PathTracer(String name) {
1103             this.prefix = name + ": ";
1104         }
1105 
1106         PathTracer init(PathConsumer2D out) {
1107             this.out = out;
1108             return this; // fluent API
1109         }
1110 
1111         @Override
1112         public void moveTo(float x0, float y0) {
1113             log("moveTo (" + x0 + ", " + y0 + ')');
1114             out.moveTo(x0, y0);


1131 
1132         @Override
1133         public void quadTo(float x1, float y1, float x2, float y2) {
1134             log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ')');
1135             out.quadTo(x1, y1, x2, y2);
1136         }
1137 
1138         @Override
1139         public void closePath() {
1140             log("closePath");
1141             out.closePath();
1142         }
1143 
1144         @Override
1145         public void pathDone() {
1146             log("pathDone");
1147             out.pathDone();
1148         }
1149 
1150         private void log(final String message) {
1151             MarlinUtils.logInfo(prefix + message);
1152         }
1153     }
1154 }
< prev index next >