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.PolyStack;
  32 
  33 final class TransformingPathConsumer2D {
  34 
  35     private final RendererContext rdrCtx;
  36 
  37     // recycled ClosedPathDetector instance from detectClosedPath()
  38     private final ClosedPathDetector   cpDetector;
  39 
  40     // recycled PathConsumer2D instance from wrapPath2d()
  41     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
  42 
  43     // recycled PathConsumer2D instances from deltaTransformConsumer()
  44     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
  45     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
  46 
  47     // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
  48     private final DeltaScaleFilter     iv_DeltaScaleFilter     = new DeltaScaleFilter();
  49     private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
  50 
  51     // recycled PathTracer instances from tracer...() methods
  52     private final PathTracer tracerInput      = new PathTracer("[Input]");
  53     private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
  54     private final PathTracer tracerStroker    = new PathTracer("Stroker");
  55 
  56     TransformingPathConsumer2D(final RendererContext rdrCtx) {
  57         // used by RendererContext
  58         this.rdrCtx = rdrCtx;
  59         this.cpDetector = new ClosedPathDetector(rdrCtx);
  60     }
  61 
  62     PathConsumer2D wrapPath2d(Path2D.Float p2d)
  63     {
  64         return wp_Path2DWrapper.init(p2d);
  65     }
  66 
  67     PathConsumer2D traceInput(PathConsumer2D out) {
  68         return tracerInput.init(out);
  69     }
  70 
  71     PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
  72         return tracerCPDetector.init(out);
  73     }
  74 
  75     PathConsumer2D traceStroker(PathConsumer2D out) {
  76         return tracerStroker.init(out);
  77     }
  78 
  79     PathConsumer2D detectClosedPath(PathConsumer2D out)
  80     {
  81         return cpDetector.init(out);
  82     }
  83 
  84     PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
  85                                           AffineTransform at)
  86     {
  87         if (at == null) {
  88             return out;
  89         }
  90         final float mxx = (float) at.getScaleX();
  91         final float mxy = (float) at.getShearX();
  92         final float myx = (float) at.getShearY();
  93         final float myy = (float) at.getScaleY();
  94 
  95         if (mxy == 0.0f && myx == 0.0f) {
  96             if (mxx == 1.0f && myy == 1.0f) {
  97                 return out;
  98             } else {
  99                 // Scale only
 100                 if (rdrCtx.doClip) {
 101                     // adjust clip rectangle (ymin, ymax, xmin, xmax):
 102                     adjustClipScale(rdrCtx.clipRect, mxx, myy);
 103                 }
 104                 return dt_DeltaScaleFilter.init(out, mxx, myy);
 105             }
 106         } else {
 107             if (rdrCtx.doClip) {
 108                 // adjust clip rectangle (ymin, ymax, xmin, xmax):
 109                 adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy);
 110             }
 111             return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
 112         }
 113     }
 114 
 115     private static void adjustClipOffset(final float[] clipRect) {
 116         clipRect[0] += Renderer.RDR_OFFSET_Y;
 117         clipRect[1] += Renderer.RDR_OFFSET_Y;
 118         clipRect[2] += Renderer.RDR_OFFSET_X;
 119         clipRect[3] += Renderer.RDR_OFFSET_X;
 120     }
 121 
 122     private static void adjustClipScale(final float[] clipRect,
 123                                         final float mxx, final float myy)
 124     {
 125         adjustClipOffset(clipRect);
 126 
 127         // Adjust the clipping rectangle (iv_DeltaScaleFilter):
 128         clipRect[0] /= myy;
 129         clipRect[1] /= myy;
 130         clipRect[2] /= mxx;
 131         clipRect[3] /= mxx;
 132     }
 133 
 134     private static void adjustClipInverseDelta(final float[] clipRect,
 135                                                final float mxx, final float mxy,
 136                                                final float myx, final float myy)
 137     {
 138         adjustClipOffset(clipRect);
 139 
 140         // Adjust the clipping rectangle (iv_DeltaTransformFilter):
 141         final float det = mxx * myy - mxy * myx;
 142         final float imxx =  myy / det;
 143         final float imxy = -mxy / det;
 144         final float imyx = -myx / det;
 145         final float imyy =  mxx / det;
 146 
 147         float xmin, xmax, ymin, ymax;
 148         float x, y;
 149         // xmin, ymin:
 150         x = clipRect[2] * imxx + clipRect[0] * imxy;
 151         y = clipRect[2] * imyx + clipRect[0] * imyy;
 152 
 153         xmin = xmax = x;
 154         ymin = ymax = y;
 155 
 156         // xmax, ymin:
 157         x = clipRect[3] * imxx + clipRect[0] * imxy;
 158         y = clipRect[3] * imyx + clipRect[0] * imyy;
 159 
 160         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 161         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 162 
 163         // xmin, ymax:
 164         x = clipRect[2] * imxx + clipRect[1] * imxy;
 165         y = clipRect[2] * imyx + clipRect[1] * imyy;
 166 
 167         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 168         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 169 
 170         // xmax, ymax:
 171         x = clipRect[3] * imxx + clipRect[1] * imxy;
 172         y = clipRect[3] * imyx + clipRect[1] * imyy;
 173 
 174         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 175         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 176 
 177         clipRect[0] = ymin;
 178         clipRect[1] = ymax;
 179         clipRect[2] = xmin;
 180         clipRect[3] = xmax;
 181     }
 182 
 183     PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
 184                                                  AffineTransform at)
 185     {
 186         if (at == null) {
 187             return out;
 188         }
 189         float mxx = (float) at.getScaleX();
 190         float mxy = (float) at.getShearX();
 191         float myx = (float) at.getShearY();
 192         float myy = (float) at.getScaleY();
 193 
 194         if (mxy == 0.0f && myx == 0.0f) {
 195             if (mxx == 1.0f && myy == 1.0f) {
 196                 return out;
 197             } else {
 198                 return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy);
 199             }
 200         } else {
 201             final float det = mxx * myy - mxy * myx;
 202             return iv_DeltaTransformFilter.init(out,
 203                                                 myy / det,
 204                                                -mxy / det,
 205                                                -myx / det,
 206                                                 mxx / det);
 207         }
 208     }
 209 
 210     static final class DeltaScaleFilter implements PathConsumer2D {
 211         private PathConsumer2D out;
 212         private float sx, sy;
 213 
 214         DeltaScaleFilter() {}
 215 
 216         DeltaScaleFilter init(PathConsumer2D out,
 217                               float mxx, float myy)
 218         {
 219             this.out = out;
 220             sx = mxx;
 221             sy = myy;
 222             return this; // fluent API
 223         }
 224 
 225         @Override
 226         public void moveTo(float x0, float y0) {
 227             out.moveTo(x0 * sx, y0 * sy);
 228         }
 229 
 230         @Override
 231         public void lineTo(float x1, float y1) {
 232             out.lineTo(x1 * sx, y1 * sy);
 233         }
 234 
 235         @Override
 236         public void quadTo(float x1, float y1,
 237                            float x2, float y2)
 238         {
 239             out.quadTo(x1 * sx, y1 * sy,
 240                        x2 * sx, y2 * sy);
 241         }
 242 
 243         @Override
 244         public void curveTo(float x1, float y1,
 245                             float x2, float y2,
 246                             float x3, float y3)
 247         {
 248             out.curveTo(x1 * sx, y1 * sy,
 249                         x2 * sx, y2 * sy,
 250                         x3 * sx, y3 * sy);
 251         }
 252 
 253         @Override
 254         public void closePath() {
 255             out.closePath();
 256         }
 257 
 258         @Override
 259         public void pathDone() {
 260             out.pathDone();
 261         }
 262 
 263         @Override
 264         public long getNativeConsumer() {
 265             return 0;
 266         }
 267     }
 268 
 269     static final class DeltaTransformFilter implements PathConsumer2D {
 270         private PathConsumer2D out;
 271         private float mxx, mxy, myx, myy;
 272 
 273         DeltaTransformFilter() {}
 274 
 275         DeltaTransformFilter init(PathConsumer2D out,
 276                                   float mxx, float mxy,
 277                                   float myx, float myy)
 278         {
 279             this.out = out;
 280             this.mxx = mxx;
 281             this.mxy = mxy;
 282             this.myx = myx;
 283             this.myy = myy;
 284             return this; // fluent API
 285         }
 286 
 287         @Override
 288         public void moveTo(float x0, float y0) {
 289             out.moveTo(x0 * mxx + y0 * mxy,
 290                        x0 * myx + y0 * myy);
 291         }
 292 
 293         @Override
 294         public void lineTo(float x1, float y1) {
 295             out.lineTo(x1 * mxx + y1 * mxy,
 296                        x1 * myx + y1 * myy);
 297         }
 298 
 299         @Override
 300         public void quadTo(float x1, float y1,
 301                            float x2, float y2)
 302         {
 303             out.quadTo(x1 * mxx + y1 * mxy,
 304                        x1 * myx + y1 * myy,
 305                        x2 * mxx + y2 * mxy,
 306                        x2 * myx + y2 * myy);
 307         }
 308 
 309         @Override
 310         public void curveTo(float x1, float y1,
 311                             float x2, float y2,
 312                             float x3, float y3)
 313         {
 314             out.curveTo(x1 * mxx + y1 * mxy,
 315                         x1 * myx + y1 * myy,
 316                         x2 * mxx + y2 * mxy,
 317                         x2 * myx + y2 * myy,
 318                         x3 * mxx + y3 * mxy,
 319                         x3 * myx + y3 * myy);
 320         }
 321 
 322         @Override
 323         public void closePath() {
 324             out.closePath();
 325         }
 326 
 327         @Override
 328         public void pathDone() {
 329             out.pathDone();
 330         }
 331 
 332         @Override
 333         public long getNativeConsumer() {
 334             return 0;
 335         }
 336     }
 337 
 338     static final class Path2DWrapper implements PathConsumer2D {
 339         private Path2D.Float p2d;
 340 
 341         Path2DWrapper() {}
 342 
 343         Path2DWrapper init(Path2D.Float p2d) {
 344             this.p2d = p2d;
 345             return this;
 346         }
 347 
 348         @Override
 349         public void moveTo(float x0, float y0) {
 350             p2d.moveTo(x0, y0);
 351         }
 352 
 353         @Override
 354         public void lineTo(float x1, float y1) {
 355             p2d.lineTo(x1, y1);
 356         }
 357 
 358         @Override
 359         public void closePath() {
 360             p2d.closePath();
 361         }
 362 
 363         @Override
 364         public void pathDone() {}
 365 
 366         @Override
 367         public void curveTo(float x1, float y1,
 368                             float x2, float y2,
 369                             float x3, float y3)
 370         {
 371             p2d.curveTo(x1, y1, x2, y2, x3, y3);
 372         }
 373 
 374         @Override
 375         public void quadTo(float x1, float y1, float x2, float y2) {
 376             p2d.quadTo(x1, y1, x2, y2);
 377         }
 378 
 379         @Override
 380         public long getNativeConsumer() {
 381             throw new InternalError("Not using a native peer");
 382         }
 383     }
 384 
 385     static final class ClosedPathDetector implements PathConsumer2D {
 386 
 387         private final RendererContext rdrCtx;
 388         private final PolyStack stack;
 389 
 390         private PathConsumer2D out;
 391 
 392         ClosedPathDetector(final RendererContext rdrCtx) {
 393             this.rdrCtx = rdrCtx;
 394             this.stack = (rdrCtx.stats != null) ?
 395                 new PolyStack(rdrCtx,
 396                         rdrCtx.stats.stat_cpd_polystack_types,
 397                         rdrCtx.stats.stat_cpd_polystack_curves,
 398                         rdrCtx.stats.hist_cpd_polystack_curves,
 399                         rdrCtx.stats.stat_array_cpd_polystack_curves,
 400                         rdrCtx.stats.stat_array_cpd_polystack_types)
 401                 : new PolyStack(rdrCtx);
 402         }
 403 
 404         ClosedPathDetector init(PathConsumer2D out) {
 405             this.out = out;
 406             return this; // fluent API
 407         }
 408 
 409         /**
 410          * Disposes this instance:
 411          * clean up before reusing this instance
 412          */
 413         void dispose() {
 414             stack.dispose();
 415         }
 416 
 417         @Override
 418         public void pathDone() {
 419             // previous path is not closed:
 420             finish(false);
 421             out.pathDone();
 422 
 423             // TODO: fix possible leak if exception happened
 424             // Dispose this instance:
 425             dispose();
 426         }
 427 
 428         @Override
 429         public void closePath() {
 430             // path is closed
 431             finish(true);
 432             out.closePath();
 433         }
 434 
 435         @Override
 436         public void moveTo(float x0, float y0) {
 437             // previous path is not closed:
 438             finish(false);
 439             out.moveTo(x0, y0);
 440         }
 441 
 442         private void finish(final boolean closed) {
 443             rdrCtx.closedPath = closed;
 444             stack.pullAll(out);
 445         }
 446 
 447         @Override
 448         public void lineTo(float x1, float y1) {
 449             stack.pushLine(x1, y1);
 450         }
 451 
 452         @Override
 453         public void curveTo(float x3, float y3,
 454                             float x2, float y2,
 455                             float x1, float y1)
 456         {
 457             stack.pushCubic(x1, y1, x2, y2, x3, y3);
 458         }
 459 
 460         @Override
 461         public void quadTo(float x2, float y2, float x1, float y1) {
 462             stack.pushQuad(x1, y1, x2, y2);
 463         }
 464 
 465         @Override
 466         public long getNativeConsumer() {
 467             throw new InternalError("Not using a native peer");
 468         }
 469     }
 470 
 471     static final class PathTracer implements PathConsumer2D {
 472         private final String prefix;
 473         private PathConsumer2D out;
 474 
 475         PathTracer(String name) {
 476             this.prefix = name + ": ";
 477         }
 478 
 479         PathTracer init(PathConsumer2D out) {
 480             this.out = out;
 481             return this; // fluent API
 482         }
 483 
 484         @Override
 485         public void moveTo(float x0, float y0) {
 486             log("moveTo (" + x0 + ", " + y0 + ')');
 487             out.moveTo(x0, y0);
 488         }
 489 
 490         @Override
 491         public void lineTo(float x1, float y1) {
 492             log("lineTo (" + x1 + ", " + y1 + ')');
 493             out.lineTo(x1, y1);
 494         }
 495 
 496         @Override
 497         public void curveTo(float x1, float y1,
 498                             float x2, float y2,
 499                             float x3, float y3)
 500         {
 501             log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ") P3(" + x3 + ", " + y3 + ')');
 502             out.curveTo(x1, y1, x2, y2, x3, y3);
 503         }
 504 
 505         @Override
 506         public void quadTo(float x1, float y1, float x2, float y2) {
 507             log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ')');
 508             out.quadTo(x1, y1, x2, y2);
 509         }
 510 
 511         @Override
 512         public void closePath() {
 513             log("closePath");
 514             out.closePath();
 515         }
 516 
 517         @Override
 518         public void pathDone() {
 519             log("pathDone");
 520             out.pathDone();
 521         }
 522 
 523         private void log(final String message) {
 524             System.out.println(prefix + message);
 525         }
 526 
 527         @Override
 528         public long getNativeConsumer() {
 529             throw new InternalError("Not using a native peer");
 530         }
 531     }
 532 }