< prev index next >

src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java

Print this page


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

  31 import sun.java2d.marlin.Helpers.IndexStack;
  32 import sun.java2d.marlin.Helpers.PolyStack;
  33 
  34 final class TransformingPathConsumer2D {
  35 



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

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




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


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






 506         PathClipFilter(final RendererContext rdrCtx) {
 507             this.clipRect = rdrCtx.clipRect;


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





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


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


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


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



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




















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

 633             }




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


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


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

























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




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


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

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























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




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


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































































































































































































































































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


 791 
 792         @Override
 793         public void quadTo(float x1, float y1, float x2, float y2) {
 794             log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ')');
 795             out.quadTo(x1, y1, x2, y2);
 796         }
 797 
 798         @Override
 799         public void closePath() {
 800             log("closePath");
 801             out.closePath();
 802         }
 803 
 804         @Override
 805         public void pathDone() {
 806             log("pathDone");
 807             out.pathDone();
 808         }
 809 
 810         private void log(final String message) {
 811             System.out.println(prefix + message);
 812         }
 813 
 814         @Override
 815         public long getNativeConsumer() {
 816             throw new InternalError("Not using a native peer");
 817         }
 818     }
 819 }
   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 sun.awt.geom.PathConsumer2D;
  29 import java.awt.geom.AffineTransform;
  30 import java.awt.geom.Path2D;
  31 import java.util.Arrays;
  32 import sun.java2d.marlin.Helpers.IndexStack;
  33 import sun.java2d.marlin.Helpers.PolyStack;
  34 
  35 final class TransformingPathConsumer2D {
  36 
  37     // higher uncertainty in float variant for huge shapes > 10^7
  38     static final float CLIP_RECT_PADDING = 1.0f;
  39 
  40     private final RendererContext rdrCtx;
  41 
  42     // recycled ClosedPathDetector instance from detectClosedPath()
  43     private final ClosedPathDetector   cpDetector;
  44 
  45     // recycled PathClipFilter instance from pathClipper()
  46     private final PathClipFilter       pathClipper;
  47 
  48     // recycled PathConsumer2D instance from wrapPath2D()
  49     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
  50 
  51     // recycled PathConsumer2D instances from deltaTransformConsumer()
  52     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
  53     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
  54 
  55     // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
  56     private final DeltaScaleFilter     iv_DeltaScaleFilter     = new DeltaScaleFilter();
  57     private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
  58 
  59     // recycled PathTracer instances from tracer...() methods
  60     private final PathTracer tracerInput      = new PathTracer("[Input]");
  61     private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
  62     private final PathTracer tracerFiller     = new PathTracer("Filler");
  63     private final PathTracer tracerStroker    = new PathTracer("Stroker");
  64     private final PathTracer tracerDasher     = new PathTracer("Dasher");
  65 
  66     TransformingPathConsumer2D(final RendererContext rdrCtx) {
  67         // used by RendererContext
  68         this.rdrCtx = rdrCtx;
  69         this.cpDetector = new ClosedPathDetector(rdrCtx);
  70         this.pathClipper = new PathClipFilter(rdrCtx);
  71     }
  72 
  73     PathConsumer2D wrapPath2D(Path2D.Float p2d) {
  74         return wp_Path2DWrapper.init(p2d);
  75     }
  76 
  77     PathConsumer2D traceInput(PathConsumer2D out) {
  78         return tracerInput.init(out);
  79     }
  80 
  81     PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
  82         return tracerCPDetector.init(out);
  83     }
  84 
  85     PathConsumer2D traceFiller(PathConsumer2D out) {
  86         return tracerFiller.init(out);
  87     }
  88 
  89     PathConsumer2D traceStroker(PathConsumer2D out) {
  90         return tracerStroker.init(out);
  91     }
  92 
  93     PathConsumer2D traceDasher(PathConsumer2D out) {
  94         return tracerDasher.init(out);
  95     }
  96 
  97     PathConsumer2D detectClosedPath(PathConsumer2D out) {
  98         return cpDetector.init(out);
  99     }
 100 
 101     PathConsumer2D pathClipper(PathConsumer2D out) {
 102         return pathClipper.init(out);
 103     }
 104 
 105     PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
 106                                           AffineTransform at)
 107     {
 108         if (at == null) {
 109             return out;
 110         }
 111         final float mxx = (float) at.getScaleX();
 112         final float mxy = (float) at.getShearX();
 113         final float myx = (float) at.getShearY();
 114         final float myy = (float) at.getScaleY();
 115 
 116         if (mxy == 0.0f && myx == 0.0f) {


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


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

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

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

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

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

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

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



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


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



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

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


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