< prev index next >

src/java.desktop/share/classes/sun/java2d/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 sun.java2d.marlin;
  27 
  28 import java.awt.geom.AffineTransform;
  29 import java.awt.geom.Path2D;

  30 import sun.java2d.marlin.DHelpers.IndexStack;
  31 import sun.java2d.marlin.DHelpers.PolyStack;
  32 
  33 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     DPathConsumer2D wrapPath2D(Path2D.Double p2d) {
  68         return wp_Path2DWrapper.init(p2d);
  69     }
  70 
  71     DPathConsumer2D traceInput(DPathConsumer2D out) {
  72         return tracerInput.init(out);
  73     }
  74 
  75     DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
  76         return tracerCPDetector.init(out);
  77     }
  78 
  79     DPathConsumer2D traceFiller(DPathConsumer2D out) {
  80         return tracerFiller.init(out);
  81     }
  82 
  83     DPathConsumer2D traceStroker(DPathConsumer2D out) {
  84         return tracerStroker.init(out);
  85     }
  86 




  87     DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
  88         return cpDetector.init(out);
  89     }
  90 
  91     DPathConsumer2D pathClipper(DPathConsumer2D out) {
  92         return pathClipper.init(out);
  93     }
  94 
  95     DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
  96                                           AffineTransform at)
  97     {
  98         if (at == null) {
  99             return out;
 100         }
 101         final double mxx = at.getScaleX();
 102         final double mxy = at.getShearX();
 103         final double myx = at.getShearY();
 104         final double myy = at.getScaleY();
 105 
 106         if (mxy == 0.0d && myx == 0.0d) {


 482     static final class PathClipFilter implements DPathConsumer2D {
 483 
 484         private DPathConsumer2D out;
 485 
 486         // Bounds of the drawing region, at pixel precision.
 487         private final double[] clipRect;
 488 
 489         private final double[] corners = new double[8];
 490         private boolean init_corners = false;
 491 
 492         private final IndexStack stack;
 493 
 494         // the current outcode of the current sub path
 495         private int cOutCode = 0;
 496 
 497         // the cumulated (and) outcode of the complete path
 498         private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 499 
 500         private boolean outside = false;
 501 
 502         // The current point OUTSIDE
 503         private double cx0, cy0;
 504 






 505         PathClipFilter(final DRendererContext rdrCtx) {
 506             this.clipRect = rdrCtx.clipRect;


 507             this.stack = (rdrCtx.stats != null) ?
 508                 new IndexStack(rdrCtx,
 509                         rdrCtx.stats.stat_pcf_idxstack_indices,
 510                         rdrCtx.stats.hist_pcf_idxstack_indices,
 511                         rdrCtx.stats.stat_array_pcf_idxstack_indices)
 512                 : new IndexStack(rdrCtx);
 513         }
 514 
 515         PathClipFilter init(final DPathConsumer2D out) {
 516             this.out = out;
 517 
 518             // Adjust the clipping rectangle with the renderer offsets
 519             final double rdrOffX = DRenderer.RDR_OFFSET_X;
 520             final double rdrOffY = DRenderer.RDR_OFFSET_Y;
 521 
 522             // add a small rounding error:
 523             final double margin = 1e-3d;
 524 
 525             final double[] _clipRect = this.clipRect;
 526             _clipRect[0] -= margin - rdrOffY;
 527             _clipRect[1] += margin + rdrOffY;
 528             _clipRect[2] -= margin - rdrOffX;
 529             _clipRect[3] += margin + rdrOffX;
 530 





 531             this.init_corners = true;
 532             this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
 533 
 534             return this; // fluent API
 535         }
 536 
 537         /**
 538          * Disposes this instance:
 539          * clean up before reusing this instance
 540          */
 541         void dispose() {
 542             stack.dispose();
 543         }
 544 
 545         private void finishPath() {
 546             if (outside) {
 547                 // criteria: inside or totally outside ?
 548                 if (gOutCode == 0) {
 549                     finish();
 550                 } else {


 561                 if (init_corners) {
 562                     init_corners = false;
 563 
 564                     final double[] _corners = corners;
 565                     final double[] _clipRect = clipRect;
 566                     // Top Left (0):
 567                     _corners[0] = _clipRect[2];
 568                     _corners[1] = _clipRect[0];
 569                     // Bottom Left (1):
 570                     _corners[2] = _clipRect[2];
 571                     _corners[3] = _clipRect[1];
 572                     // Top right (2):
 573                     _corners[4] = _clipRect[3];
 574                     _corners[5] = _clipRect[0];
 575                     // Bottom Right (3):
 576                     _corners[6] = _clipRect[3];
 577                     _corners[7] = _clipRect[1];
 578                 }
 579                 stack.pullAll(corners, out);
 580             }
 581             out.lineTo(cx0, cy0);


 582         }
 583 
 584         @Override
 585         public void pathDone() {
 586             finishPath();
 587 
 588             out.pathDone();
 589 
 590             // TODO: fix possible leak if exception happened
 591             // Dispose this instance:
 592             dispose();
 593         }
 594 
 595         @Override
 596         public void closePath() {
 597             finishPath();
 598 
 599             out.closePath();
 600         }
 601 
 602         @Override
 603         public void moveTo(final double x0, final double y0) {
 604             finishPath();
 605 
 606             final int outcode = DHelpers.outcode(x0, y0, clipRect);
 607             this.cOutCode = outcode;
 608             this.outside = false;
 609             out.moveTo(x0, y0);


 610         }
 611 
 612         @Override
 613         public void lineTo(final double xe, final double ye) {
 614             final int outcode0 = this.cOutCode;
 615             final int outcode1 = DHelpers.outcode(xe, ye, clipRect);
 616             this.cOutCode = outcode1;
 617 
 618             final int sideCode = (outcode0 & outcode1);



 619 
 620             // basic rejection criteria:
 621             if (sideCode == 0) {
 622                 this.gOutCode = 0;
 623             } else {
 624                 this.gOutCode &= sideCode;
 625                 // keep last point coordinate before entering the clip again:
 626                 this.outside = true;
 627                 this.cx0 = xe;
 628                 this.cy0 = ye;




















 629 
 630                 clip(sideCode, outcode0, outcode1);
 631                 return;

 632             }




 633             if (outside) {
 634                 finish();
 635             }
 636             // clipping disabled:
 637             out.lineTo(xe, ye);


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


 687             final int outcode3 = DHelpers.outcode(xe, ye, clipRect);
 688             this.cOutCode = outcode3;
 689 
 690             int sideCode = outcode0 & outcode3;
 691 
 692             if (sideCode == 0) {
 693                 this.gOutCode = 0;
 694             } else {
 695                 sideCode &= DHelpers.outcode(x1, y1, clipRect);
 696                 sideCode &= DHelpers.outcode(x2, y2, clipRect);
 697                 this.gOutCode &= sideCode;
 698 
 699                 // basic rejection criteria:
 700                 if (sideCode != 0) {

























 701                     // keep last point coordinate before entering the clip again:
 702                     this.outside = true;
 703                     this.cx0 = xe;
 704                     this.cy0 = ye;
 705 
 706                     clip(sideCode, outcode0, outcode3);
 707                     return;
 708                 }
 709             }




 710             if (outside) {
 711                 finish();
 712             }
 713             // clipping disabled:
 714             out.curveTo(x1, y1, x2, y2, xe, ye);


 715         }
 716 
 717         @Override
 718         public void quadTo(final double x1, final double y1,
 719                            final double xe, final double ye)
 720         {
 721             final int outcode0 = this.cOutCode;

 722             final int outcode2 = DHelpers.outcode(xe, ye, clipRect);
 723             this.cOutCode = outcode2;
 724 
 725             int sideCode = outcode0 & outcode2;
 726 
 727             if (sideCode == 0) {
 728                 this.gOutCode = 0;
 729             } else {
 730                 sideCode &= DHelpers.outcode(x1, y1, clipRect);
 731                 this.gOutCode &= sideCode;
 732 
 733                 // basic rejection criteria:
 734                 if (sideCode != 0) {























 735                     // keep last point coordinate before entering the clip again:
 736                     this.outside = true;
 737                     this.cx0 = xe;
 738                     this.cy0 = ye;
 739 
 740                     clip(sideCode, outcode0, outcode2);
 741                     return;
 742                 }
 743             }




 744             if (outside) {
 745                 finish();
 746             }
 747             // clipping disabled:
 748             out.quadTo(x1, y1, xe, ye);


 749         }
 750 
 751         @Override
 752         public long getNativeConsumer() {
 753             throw new InternalError("Not using a native peer");
 754         }
 755     }
 756 































































































































































































































































 757     static final class PathTracer implements DPathConsumer2D {
 758         private final String prefix;
 759         private DPathConsumer2D out;
 760 
 761         PathTracer(String name) {
 762             this.prefix = name + ": ";
 763         }
 764 
 765         PathTracer init(DPathConsumer2D out) {
 766             this.out = out;
 767             return this; // fluent API
 768         }
 769 
 770         @Override
 771         public void moveTo(double x0, double y0) {
 772             log("moveTo (" + x0 + ", " + y0 + ')');
 773             out.moveTo(x0, y0);
 774         }
 775 
 776         @Override


 790 
 791         @Override
 792         public void quadTo(double x1, double y1, double x2, double y2) {
 793             log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ')');
 794             out.quadTo(x1, y1, x2, y2);
 795         }
 796 
 797         @Override
 798         public void closePath() {
 799             log("closePath");
 800             out.closePath();
 801         }
 802 
 803         @Override
 804         public void pathDone() {
 805             log("pathDone");
 806             out.pathDone();
 807         }
 808 
 809         private void log(final String message) {
 810             System.out.println(prefix + message);
 811         }
 812 
 813         @Override
 814         public long getNativeConsumer() {
 815             throw new InternalError("Not using a native peer");
 816         }
 817     }
 818 }
   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 sun.java2d.marlin;
  27 
  28 import java.awt.geom.AffineTransform;
  29 import java.awt.geom.Path2D;
  30 import java.util.Arrays;
  31 import sun.java2d.marlin.DHelpers.IndexStack;
  32 import sun.java2d.marlin.DHelpers.PolyStack;
  33 
  34 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     DPathConsumer2D wrapPath2D(Path2D.Double p2d) {
  73         return wp_Path2DWrapper.init(p2d);
  74     }
  75 
  76     DPathConsumer2D traceInput(DPathConsumer2D out) {
  77         return tracerInput.init(out);
  78     }
  79 
  80     DPathConsumer2D traceClosedPathDetector(DPathConsumer2D out) {
  81         return tracerCPDetector.init(out);
  82     }
  83 
  84     DPathConsumer2D traceFiller(DPathConsumer2D out) {
  85         return tracerFiller.init(out);
  86     }
  87 
  88     DPathConsumer2D traceStroker(DPathConsumer2D out) {
  89         return tracerStroker.init(out);
  90     }
  91 
  92     DPathConsumer2D traceDasher(DPathConsumer2D out) {
  93         return tracerDasher.init(out);
  94     }
  95 
  96     DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
  97         return cpDetector.init(out);
  98     }
  99 
 100     DPathConsumer2D pathClipper(DPathConsumer2D out) {
 101         return pathClipper.init(out);
 102     }
 103 
 104     DPathConsumer2D deltaTransformConsumer(DPathConsumer2D out,
 105                                           AffineTransform at)
 106     {
 107         if (at == null) {
 108             return out;
 109         }
 110         final double mxx = at.getScaleX();
 111         final double mxy = at.getShearX();
 112         final double myx = at.getShearY();
 113         final double myy = at.getScaleY();
 114 
 115         if (mxy == 0.0d && myx == 0.0d) {


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


 583                 if (init_corners) {
 584                     init_corners = false;
 585 
 586                     final double[] _corners = corners;
 587                     final double[] _clipRect = clipRect;
 588                     // Top Left (0):
 589                     _corners[0] = _clipRect[2];
 590                     _corners[1] = _clipRect[0];
 591                     // Bottom Left (1):
 592                     _corners[2] = _clipRect[2];
 593                     _corners[3] = _clipRect[1];
 594                     // Top right (2):
 595                     _corners[4] = _clipRect[3];
 596                     _corners[5] = _clipRect[0];
 597                     // Bottom Right (3):
 598                     _corners[6] = _clipRect[3];
 599                     _corners[7] = _clipRect[1];
 600                 }
 601                 stack.pullAll(corners, out);
 602             }
 603             out.lineTo(cox0, coy0);
 604             this.cx0 = cox0;
 605             this.cy0 = coy0;
 606         }
 607 
 608         @Override
 609         public void pathDone() {
 610             finishPath();
 611 
 612             out.pathDone();
 613 
 614             // TODO: fix possible leak if exception happened
 615             // Dispose this instance:
 616             dispose();
 617         }
 618 
 619         @Override
 620         public void closePath() {
 621             finishPath();
 622 
 623             out.closePath();
 624         }
 625 
 626         @Override
 627         public void moveTo(final double x0, final double y0) {
 628             finishPath();
 629 
 630             this.cOutCode = DHelpers.outcode(x0, y0, clipRect);

 631             this.outside = false;
 632             out.moveTo(x0, y0);
 633             this.cx0 = x0;
 634             this.cy0 = y0;
 635         }
 636 
 637         @Override
 638         public void lineTo(final double xe, final double ye) {
 639             final int outcode0 = this.cOutCode;
 640             final int outcode1 = DHelpers.outcode(xe, ye, clipRect);

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

 711                         stack.push(off); // top
 712                         return;
 713                     case MarlinConst.OUTCODE_BOTTOM:

 714                         stack.push(off + 1); // bottom
 715                         return;
 716                     default:
 717                         // both TOP / BOTTOM:
 718                         if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {

 719                             // top to bottom
 720                             stack.push(off); // top
 721                             stack.push(off + 1); // bottom
 722                         } else {

 723                             // bottom to top
 724                             stack.push(off + 1); // bottom
 725                             stack.push(off); // top
 726                         }
 727                 }
 728             }
 729         }
 730 
 731         @Override
 732         public void curveTo(final double x1, final double y1,
 733                             final double x2, final double y2,
 734                             final double xe, final double ye)
 735         {
 736             final int outcode0 = this.cOutCode;
 737             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
 738             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
 739             final int outcode3 = DHelpers.outcode(xe, ye, clipRect);



 740 
 741             // Should clip
 742             final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
 743             if (orCode != 0) {
 744                 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;


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



 802 
 803             // Should clip
 804             final int orCode = (outcode0 | outcode1 | outcode2);
 805             if (orCode != 0) {
 806                 final int sideCode = outcode0 & outcode1 & outcode2;

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


1149 
1150         @Override
1151         public void quadTo(double x1, double y1, double x2, double y2) {
1152             log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ')');
1153             out.quadTo(x1, y1, x2, y2);
1154         }
1155 
1156         @Override
1157         public void closePath() {
1158             log("closePath");
1159             out.closePath();
1160         }
1161 
1162         @Override
1163         public void pathDone() {
1164             log("pathDone");
1165             out.pathDone();
1166         }
1167 
1168         private void log(final String message) {
1169             MarlinUtils.logInfo(prefix + message);
1170         }
1171 
1172         @Override
1173         public long getNativeConsumer() {
1174             throw new InternalError("Not using a native peer");
1175         }
1176     }
1177 }
< prev index next >