1 /*
   2  * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.marlin;
  27 
  28 import com.sun.javafx.geom.PathConsumer2D;
  29 import com.sun.javafx.geom.transform.BaseTransform;
  30 import com.sun.marlin.Helpers.PolyStack;
  31 
  32 public final class TransformingPathConsumer2D {
  33 
  34     private final RendererContext rdrCtx;
  35 
  36     // recycled ClosedPathDetector instance from detectClosedPath()
  37     private final ClosedPathDetector   cpDetector;
  38 
  39     // recycled PathConsumer2D instances from deltaTransformConsumer()
  40     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
  41     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
  42 
  43     // recycled PathConsumer2D instances from inverseDeltaTransformConsumer()
  44     private final DeltaScaleFilter     iv_DeltaScaleFilter     = new DeltaScaleFilter();
  45     private final DeltaTransformFilter iv_DeltaTransformFilter = new DeltaTransformFilter();
  46 
  47     // recycled PathTracer instances from tracer...() methods
  48     private final PathTracer tracerInput      = new PathTracer("[Input]");
  49     private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
  50     private final PathTracer tracerStroker    = new PathTracer("Stroker");
  51 
  52     TransformingPathConsumer2D(final RendererContext rdrCtx) {
  53         // used by RendererContext
  54         this.rdrCtx = rdrCtx;
  55         this.cpDetector = new ClosedPathDetector(rdrCtx);
  56     }
  57 
  58     public PathConsumer2D traceInput(PathConsumer2D out) {
  59         return tracerInput.init(out);
  60     }
  61 
  62     public PathConsumer2D traceClosedPathDetector(PathConsumer2D out) {
  63         return tracerCPDetector.init(out);
  64     }
  65 
  66     public PathConsumer2D traceStroker(PathConsumer2D out) {
  67         return tracerStroker.init(out);
  68     }
  69 
  70     public PathConsumer2D detectClosedPath(PathConsumer2D out)
  71     {
  72         return cpDetector.init(out);
  73     }
  74 
  75     public PathConsumer2D deltaTransformConsumer(PathConsumer2D out,
  76                                                  BaseTransform at,
  77                                                  final float rdrOffX,
  78                                                  final float rdrOffY)
  79     {
  80         if (at == null) {
  81             return out;
  82         }
  83         final float mxx = (float) at.getMxx();
  84         final float mxy = (float) at.getMxy();
  85         final float myx = (float) at.getMyx();
  86         final float myy = (float) at.getMyy();
  87 
  88         if (mxy == 0.0f && myx == 0.0f) {
  89             if (mxx == 1.0f && myy == 1.0f) {
  90                 return out;
  91             } else {
  92                 // Scale only
  93                 if (rdrCtx.doClip) {
  94                     // adjust clip rectangle (ymin, ymax, xmin, xmax):
  95                     adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY);
  96                     adjustClipScale(rdrCtx.clipRect, mxx, myy);
  97                 }
  98                 return dt_DeltaScaleFilter.init(out, mxx, myy);
  99             }
 100         } else {
 101             if (rdrCtx.doClip) {
 102                 // adjust clip rectangle (ymin, ymax, xmin, xmax):
 103                 adjustClipOffset(rdrCtx.clipRect, rdrOffX, rdrOffY);
 104                 adjustClipInverseDelta(rdrCtx.clipRect, mxx, mxy, myx, myy);
 105             }
 106             return dt_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
 107         }
 108     }
 109 
 110     private static void adjustClipOffset(final float[] clipRect,
 111                                          final float rdrOffX,
 112                                          final float rdrOffY)
 113     {
 114         clipRect[0] += rdrOffY;
 115         clipRect[1] += rdrOffY;
 116         clipRect[2] += rdrOffX;
 117         clipRect[3] += rdrOffX;
 118     }
 119 
 120     private static void adjustClipScale(final float[] clipRect,
 121                                         final float mxx, final float myy)
 122     {
 123         // Adjust the clipping rectangle (iv_DeltaScaleFilter):
 124         clipRect[0] /= myy;
 125         clipRect[1] /= myy;
 126         clipRect[2] /= mxx;
 127         clipRect[3] /= mxx;
 128     }
 129 
 130     private static void adjustClipInverseDelta(final float[] clipRect,
 131                                                final float mxx, final float mxy,
 132                                                final float myx, final float myy)
 133     {
 134         // Adjust the clipping rectangle (iv_DeltaTransformFilter):
 135         final float det = mxx * myy - mxy * myx;
 136         final float imxx =  myy / det;
 137         final float imxy = -mxy / det;
 138         final float imyx = -myx / det;
 139         final float imyy =  mxx / det;
 140 
 141         float xmin, xmax, ymin, ymax;
 142         float x, y;
 143         // xmin, ymin:
 144         x = clipRect[2] * imxx + clipRect[0] * imxy;
 145         y = clipRect[2] * imyx + clipRect[0] * imyy;
 146 
 147         xmin = xmax = x;
 148         ymin = ymax = y;
 149 
 150         // xmax, ymin:
 151         x = clipRect[3] * imxx + clipRect[0] * imxy;
 152         y = clipRect[3] * imyx + clipRect[0] * imyy;
 153 
 154         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 155         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 156 
 157         // xmin, ymax:
 158         x = clipRect[2] * imxx + clipRect[1] * imxy;
 159         y = clipRect[2] * imyx + clipRect[1] * imyy;
 160 
 161         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 162         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 163 
 164         // xmax, ymax:
 165         x = clipRect[3] * imxx + clipRect[1] * imxy;
 166         y = clipRect[3] * imyx + clipRect[1] * imyy;
 167 
 168         if (x < xmin) { xmin = x; } else if (x > xmax) { xmax = x; }
 169         if (y < ymin) { ymin = y; } else if (y > ymax) { ymax = y; }
 170 
 171         clipRect[0] = ymin;
 172         clipRect[1] = ymax;
 173         clipRect[2] = xmin;
 174         clipRect[3] = xmax;
 175     }
 176 
 177     public PathConsumer2D inverseDeltaTransformConsumer(PathConsumer2D out,
 178                                                         BaseTransform at)
 179     {
 180         if (at == null) {
 181             return out;
 182         }
 183         float mxx = (float) at.getMxx();
 184         float mxy = (float) at.getMxy();
 185         float myx = (float) at.getMyx();
 186         float myy = (float) at.getMyy();
 187 
 188         if (mxy == 0.0f && myx == 0.0f) {
 189             if (mxx == 1.0f && myy == 1.0f) {
 190                 return out;
 191             } else {
 192                 return iv_DeltaScaleFilter.init(out, 1.0f/mxx, 1.0f/myy);
 193             }
 194         } else {
 195             final float det = mxx * myy - mxy * myx;
 196             return iv_DeltaTransformFilter.init(out,
 197                                                 myy / det,
 198                                                -mxy / det,
 199                                                -myx / det,
 200                                                 mxx / det);
 201         }
 202     }
 203 
 204 
 205     static final class DeltaScaleFilter implements PathConsumer2D {
 206         private PathConsumer2D out;
 207         private float sx, sy;
 208 
 209         DeltaScaleFilter() {}
 210 
 211         DeltaScaleFilter init(PathConsumer2D out,
 212                               float mxx, float myy)
 213         {
 214             this.out = out;
 215             sx = mxx;
 216             sy = myy;
 217             return this; // fluent API
 218         }
 219 
 220         @Override
 221         public void moveTo(float x0, float y0) {
 222             out.moveTo(x0 * sx, y0 * sy);
 223         }
 224 
 225         @Override
 226         public void lineTo(float x1, float y1) {
 227             out.lineTo(x1 * sx, y1 * sy);
 228         }
 229 
 230         @Override
 231         public void quadTo(float x1, float y1,
 232                            float x2, float y2)
 233         {
 234             out.quadTo(x1 * sx, y1 * sy,
 235                        x2 * sx, y2 * sy);
 236         }
 237 
 238         @Override
 239         public void curveTo(float x1, float y1,
 240                             float x2, float y2,
 241                             float x3, float y3)
 242         {
 243             out.curveTo(x1 * sx, y1 * sy,
 244                         x2 * sx, y2 * sy,
 245                         x3 * sx, y3 * sy);
 246         }
 247 
 248         @Override
 249         public void closePath() {
 250             out.closePath();
 251         }
 252 
 253         @Override
 254         public void pathDone() {
 255             out.pathDone();
 256         }
 257     }
 258 
 259     static final class DeltaTransformFilter implements PathConsumer2D {
 260         private PathConsumer2D out;
 261         private float mxx, mxy, myx, myy;
 262 
 263         DeltaTransformFilter() {}
 264 
 265         DeltaTransformFilter init(PathConsumer2D out,
 266                                   float mxx, float mxy,
 267                                   float myx, float myy)
 268         {
 269             this.out = out;
 270             this.mxx = mxx;
 271             this.mxy = mxy;
 272             this.myx = myx;
 273             this.myy = myy;
 274             return this; // fluent API
 275         }
 276 
 277         @Override
 278         public void moveTo(float x0, float y0) {
 279             out.moveTo(x0 * mxx + y0 * mxy,
 280                        x0 * myx + y0 * myy);
 281         }
 282 
 283         @Override
 284         public void lineTo(float x1, float y1) {
 285             out.lineTo(x1 * mxx + y1 * mxy,
 286                        x1 * myx + y1 * myy);
 287         }
 288 
 289         @Override
 290         public void quadTo(float x1, float y1,
 291                            float x2, float y2)
 292         {
 293             out.quadTo(x1 * mxx + y1 * mxy,
 294                        x1 * myx + y1 * myy,
 295                        x2 * mxx + y2 * mxy,
 296                        x2 * myx + y2 * myy);
 297         }
 298 
 299         @Override
 300         public void curveTo(float x1, float y1,
 301                             float x2, float y2,
 302                             float x3, float y3)
 303         {
 304             out.curveTo(x1 * mxx + y1 * mxy,
 305                         x1 * myx + y1 * myy,
 306                         x2 * mxx + y2 * mxy,
 307                         x2 * myx + y2 * myy,
 308                         x3 * mxx + y3 * mxy,
 309                         x3 * myx + y3 * myy);
 310         }
 311 
 312         @Override
 313         public void closePath() {
 314             out.closePath();
 315         }
 316 
 317         @Override
 318         public void pathDone() {
 319             out.pathDone();
 320         }
 321     }
 322 
 323     static final class ClosedPathDetector implements PathConsumer2D {
 324 
 325         private final RendererContext rdrCtx;
 326         private final PolyStack stack;
 327 
 328         private PathConsumer2D out;
 329 
 330         ClosedPathDetector(final RendererContext rdrCtx) {
 331             this.rdrCtx = rdrCtx;
 332             this.stack = (rdrCtx.stats != null) ?
 333                 new PolyStack(rdrCtx,
 334                         rdrCtx.stats.stat_cpd_polystack_types,
 335                         rdrCtx.stats.stat_cpd_polystack_curves,
 336                         rdrCtx.stats.hist_cpd_polystack_curves,
 337                         rdrCtx.stats.stat_array_cpd_polystack_curves,
 338                         rdrCtx.stats.stat_array_cpd_polystack_types)
 339                 : new PolyStack(rdrCtx);
 340         }
 341 
 342         ClosedPathDetector init(PathConsumer2D out) {
 343             this.out = out;
 344             return this; // fluent API
 345         }
 346 
 347         /**
 348          * Disposes this instance:
 349          * clean up before reusing this instance
 350          */
 351         void dispose() {
 352             stack.dispose();
 353         }
 354 
 355         @Override
 356         public void pathDone() {
 357             // previous path is not closed:
 358             finish(false);
 359             out.pathDone();
 360 
 361             // TODO: fix possible leak if exception happened
 362             // Dispose this instance:
 363             dispose();
 364         }
 365 
 366         @Override
 367         public void closePath() {
 368             // path is closed
 369             finish(true);
 370             out.closePath();
 371         }
 372 
 373         @Override
 374         public void moveTo(float x0, float y0) {
 375             // previous path is not closed:
 376             finish(false);
 377             out.moveTo(x0, y0);
 378         }
 379 
 380         private void finish(final boolean closed) {
 381             rdrCtx.closedPath = closed;
 382             stack.pullAll(out);
 383         }
 384 
 385         @Override
 386         public void lineTo(float x1, float y1) {
 387             stack.pushLine(x1, y1);
 388         }
 389 
 390         @Override
 391         public void curveTo(float x3, float y3,
 392                             float x2, float y2,
 393                             float x1, float y1)
 394         {
 395             stack.pushCubic(x1, y1, x2, y2, x3, y3);
 396         }
 397 
 398         @Override
 399         public void quadTo(float x2, float y2, float x1, float y1) {
 400             stack.pushQuad(x1, y1, x2, y2);
 401         }
 402     }
 403 
 404     static final class PathTracer implements PathConsumer2D {
 405         private final String prefix;
 406         private PathConsumer2D out;
 407 
 408         PathTracer(String name) {
 409             this.prefix = name + ": ";
 410         }
 411 
 412         PathTracer init(PathConsumer2D out) {
 413             this.out = out;
 414             return this; // fluent API
 415         }
 416 
 417         @Override
 418         public void moveTo(float x0, float y0) {
 419             log("moveTo (" + x0 + ", " + y0 + ')');
 420             out.moveTo(x0, y0);
 421         }
 422 
 423         @Override
 424         public void lineTo(float x1, float y1) {
 425             log("lineTo (" + x1 + ", " + y1 + ')');
 426             out.lineTo(x1, y1);
 427         }
 428 
 429         @Override
 430         public void curveTo(float x1, float y1,
 431                             float x2, float y2,
 432                             float x3, float y3)
 433         {
 434             log("curveTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ") P3(" + x3 + ", " + y3 + ')');
 435             out.curveTo(x1, y1, x2, y2, x3, y3);
 436         }
 437 
 438         @Override
 439         public void quadTo(float x1, float y1, float x2, float y2) {
 440             log("quadTo P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2  + ')');
 441             out.quadTo(x1, y1, x2, y2);
 442         }
 443 
 444         @Override
 445         public void closePath() {
 446             log("closePath");
 447             out.closePath();
 448         }
 449 
 450         @Override
 451         public void pathDone() {
 452             log("pathDone");
 453             out.pathDone();
 454         }
 455 
 456         private void log(final String message) {
 457             System.out.println(prefix + message);
 458         }
 459     }
 460 }