< prev index next >

modules/javafx.graphics/src/main/java/com/sun/marlin/DTransformingPathConsumer2D.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.transform.BaseTransform;
  30 import com.sun.marlin.DHelpers.IndexStack;
  31 import com.sun.marlin.DHelpers.PolyStack;

  32 
  33 public final class DTransformingPathConsumer2D {
  34 



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

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




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


 469     static final class PathClipFilter implements DPathConsumer2D {
 470 
 471         private DPathConsumer2D out;
 472 
 473         // Bounds of the drawing region, at pixel precision.
 474         private final double[] clipRect;
 475 
 476         private final double[] corners = new double[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 double cx0, cy0;
 491 






 492         PathClipFilter(final DRendererContext 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 DPathConsumer2D out,
 503                             final double rdrOffX,
 504                             final double rdrOffY)
 505         {
 506             this.out = out;
 507 
 508             // add a small rounding error:
 509             final double margin = 1e-3d;
 510 
 511             final double[] _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 double[] _corners = corners;
 552                     final double[] _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 double x0, final double y0) {
 591             finishPath();
 592 
 593             final int outcode = DHelpers.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 double xe, final double ye) {
 601             final int outcode0 = this.cOutCode;
 602             final int outcode1 = DHelpers.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 double x1, final double y1,
 670                             final double x2, final double y2,
 671                             final double xe, final double ye)
 672         {
 673             final int outcode0 = this.cOutCode;


 674             final int outcode3 = DHelpers.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 &= DHelpers.outcode(x1, y1, clipRect);
 683                 sideCode &= DHelpers.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 double x1, final double y1,
 706                            final double xe, final double ye)
 707         {
 708             final int outcode0 = this.cOutCode;

 709             final int outcode2 = DHelpers.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 &= DHelpers.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 DPathConsumer2D {
 740         private final String prefix;
 741         private DPathConsumer2D out;
 742 
 743         PathTracer(String name) {
 744             this.prefix = name + ": ";
 745         }
 746 
 747         PathTracer init(DPathConsumer2D out) {
 748             this.out = out;
 749             return this; // fluent API
 750         }
 751 
 752         @Override
 753         public void moveTo(double x0, double y0) {
 754             log("moveTo (" + x0 + ", " + y0 + ')');
 755             out.moveTo(x0, y0);


 772 
 773         @Override
 774         public void quadTo(double x1, double y1, double x2, double 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.transform.BaseTransform;
  30 import com.sun.marlin.DHelpers.IndexStack;
  31 import com.sun.marlin.DHelpers.PolyStack;
  32 import java.util.Arrays;
  33 
  34 public final class DTransformingPathConsumer2D {
  35 
  36     // smaller uncertainty in double variant
  37     static final double CLIP_RECT_PADDING = 0.25d;
  38 
  39     private final DRendererContext rdrCtx;
  40 
  41     // recycled ClosedPathDetector instance from detectClosedPath()
  42     private final ClosedPathDetector   cpDetector;
  43 
  44     // recycled PathClipFilter instance from pathClipper()
  45     private final PathClipFilter       pathClipper;
  46 
  47     // recycled DPathConsumer2D instance from wrapPath2D()
  48     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
  49 
  50     // recycled DPathConsumer2D instances from deltaTransformConsumer()
  51     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
  52     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
  53 
  54     // recycled DPathConsumer2D instances from inverseDeltaTransformConsumer()
  55     private final DeltaScaleFilter     iv_DeltaScaleFilter     = new DeltaScaleFilter();
  56     private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
  57 
  58     // recycled PathTracer instances from tracer...() methods
  59     private final PathTracer tracerInput      = new PathTracer("[Input]");
  60     private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
  61     private final PathTracer tracerFiller     = new PathTracer("Filler");
  62     private final PathTracer tracerStroker    = new PathTracer("Stroker");
  63     private final PathTracer tracerDasher     = new PathTracer("Dasher");
  64 
  65     DTransformingPathConsumer2D(final DRendererContext rdrCtx) {
  66         // used by RendererContext
  67         this.rdrCtx = rdrCtx;
  68         this.cpDetector = new ClosedPathDetector(rdrCtx);
  69         this.pathClipper = new PathClipFilter(rdrCtx);
  70     }
  71 
  72     public DPathConsumer2D wrapPath2D(Path2D p2d) {
  73         return wp_Path2DWrapper.init(p2d);
  74     }
  75 
  76     public DPathConsumer2D traceInput(DPathConsumer2D out) {
  77         return tracerInput.init(out);
  78     }
  79 
  80     public DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
  81         return tracerCPDetector.init(out);
  82     }
  83 
  84     public DPathConsumer2D traceFiller(DPathConsumer2D out) {
  85         return tracerFiller.init(out);
  86     }
  87 
  88     public DPathConsumer2D traceStroker(DPathConsumer2D out) {
  89         return tracerStroker.init(out);
  90     }
  91 
  92     public DPathConsumer2D traceDasher(DPathConsumer2D out) {
  93         return tracerDasher.init(out);
  94     }
  95 
  96     public DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
  97         return cpDetector.init(out);
  98     }
  99 
 100     public DPathConsumer2D pathClipper(DPathConsumer2D out,
 101                                        final double rdrOffX,
 102                                        final double rdrOffY)
 103     {
 104         return pathClipper.init(out, rdrOffX, rdrOffY);
 105     }
 106 
 107     public DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
 108                                                   BaseTransform at,
 109                                                   final double rdrOffX,
 110                                                   final double rdrOffY)
 111     {
 112         if (at == null) {
 113             return out;
 114         }
 115         final double mxx = at.getMxx();


 478     static final class PathClipFilter implements DPathConsumer2D {
 479 
 480         private DPathConsumer2D out;
 481 
 482         // Bounds of the drawing region, at pixel precision.
 483         private final double[] clipRect;
 484 
 485         private final double[] corners = new double[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 double cx0, cy0;
 500 
 501         // The current point OUTSIDE
 502         private double cox0, coy0;
 503 
 504         private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
 505         private final CurveClipSplitter curveSplitter;
 506 
 507         PathClipFilter(final DRendererContext 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 DPathConsumer2D out,
 520                             final double rdrOffX,
 521                             final double rdrOffY)
 522         {
 523             this.out = out;
 524 
 525             // add a small rounding error:
 526             final double margin = 1e-3d;
 527 
 528             final double[] _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 double[] _corners = corners;
 574                     final double[] _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 double x0, final double y0) {
 615             finishPath();
 616 
 617             this.cOutCode = DHelpers.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 double xe, final double ye) {
 626             final int outcode0 = this.cOutCode;
 627             final int outcode1 = DHelpers.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 double x1, final double y1,
 720                             final double x2, final double y2,
 721                             final double xe, final double ye)
 722         {
 723             final int outcode0 = this.cOutCode;
 724             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 725             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
 726             final int outcode3 = DHelpers.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 double x1, final double y1,
 784                            final double xe, final double ye)
 785         {
 786             final int outcode0 = this.cOutCode;
 787             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 788             final int outcode2 = DHelpers.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 double LEN_TH = MarlinProperties.getSubdividerMinLength();
 846         static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d);
 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 double[] clipRect;
 854 
 855         // clip rectangle (ymin, ymax, xmin, xmax) including padding:
 856         final double[] clipRectPad = new double[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 double[] middle = new double[MAX_N_CURVES * 8 + 2];
 862         // t values at subdivision points
 863         private final double[] subdivTs = new double[MAX_N_CURVES];
 864 
 865         // dirty curve
 866         private final DCurve curve;
 867 
 868         CurveClipSplitter(final DRendererContext 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 double[] _clipRect = clipRect;
 882             final double[] _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 double x0, final double y0,
 896                           final double x1, final double y1,
 897                           final int outCodeOR,
 898                           final DPathConsumer2D out)
 899         {
 900             if (TRACE) {
 901                 MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
 902             }
 903 
 904             if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
 905                 return false;
 906             }
 907 
 908             final double[] 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 double x0, final double y0,
 916                           final double x1, final double y1,
 917                           final double x2, final double y2,
 918                           final int outCodeOR,
 919                           final DPathConsumer2D 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 && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
 926                 return false;
 927             }
 928 
 929             final double[] 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 double x0, final double y0,
 938                            final double x1, final double y1,
 939                            final double x2, final double y2,
 940                            final double x3, final double y3,
 941                            final int outCodeOR,
 942                            final DPathConsumer2D 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 && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
 949                 return false;
 950             }
 951 
 952             final double[] 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 DPathConsumer2D out)
 963         {
 964             final double[] mid = middle;
 965             final double[] subTs = subdivTs;
 966 
 967             if (init_clipRectPad) {
 968                 init_clipRectPad = false;
 969                 initPaddedClip();
 970             }
 971 
 972             final int nSplits = DHelpers.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             double prevT = 0.0d;
 984 
 985             for (int i = 0, off = 0; i < nSplits; i++, off += type) {
 986                 final double t = subTs[i];
 987 
 988                 DHelpers.subdivideAt((t - prevT) / (1.0d - 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 double[] pts,
1003                                 final int off, final DPathConsumer2D 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 double 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 double[] middle = new double[MAX_N_CURVES * 6 + 2];
1032         // t values at subdivision points
1033         private final double[] subdivTs = new double[MAX_N_CURVES - 1];
1034 
1035         // dirty curve
1036         private final DCurve curve;
1037 
1038         CurveBasicMonotonizer(final DRendererContext rdrCtx) {
1039             this.curve = rdrCtx.curve;
1040         }
1041 
1042         public void init(final double lineWidth) {
1043             this.lw2 = (lineWidth * lineWidth) / 4.0d;
1044         }
1045 
1046         CurveBasicMonotonizer curve(final double x0, final double y0,
1047                                     final double x1, final double y1,
1048                                     final double x2, final double y2,
1049                                     final double x3, final double y3)
1050         {
1051             final double[] 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 double[] subTs = subdivTs;
1058             final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
1059 
1060             double prevT = 0.0d;
1061             for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
1062                 final double t = subTs[i];
1063 
1064                 DHelpers.subdivideCubicAt((t - prevT) / (1.0d - 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 double x0, final double y0,
1074                                    final double x1, final double y1,
1075                                    final double x2, final double y2)
1076         {
1077             final double[] 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 double[] subTs = subdivTs;
1083             final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
1084 
1085             double prevt = 0.0d;
1086             for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
1087                 final double t = subTs[i];
1088                 DHelpers.subdivideQuadAt((t - prevt) / (1.0d - 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 DPathConsumer2D {
1099         private final String prefix;
1100         private DPathConsumer2D out;
1101 
1102         PathTracer(String name) {
1103             this.prefix = name + ": ";
1104         }
1105 
1106         PathTracer init(DPathConsumer2D out) {
1107             this.out = out;
1108             return this; // fluent API
1109         }
1110 
1111         @Override
1112         public void moveTo(double x0, double y0) {
1113             log("moveTo (" + x0 + ", " + y0 + ')');
1114             out.moveTo(x0, y0);


1131 
1132         @Override
1133         public void quadTo(double x1, double y1, double x2, double 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 >