1 /*
   2  * Copyright (c) 2011, 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 javafx.scene.transform;
  27 
  28 import javafx.beans.property.DoubleProperty;
  29 import javafx.beans.property.DoublePropertyBase;
  30 
  31 import com.sun.javafx.geom.transform.Affine3D;
  32 import com.sun.javafx.geom.transform.BaseTransform;
  33 import javafx.geometry.Point2D;
  34 import javafx.geometry.Point3D;
  35 
  36 
  37 /**
  38  * This class represents an {@code Affine} object that shears coordinates
  39  * by the specified multipliers. The matrix representing the shearing transformation
  40  * around a pivot point {@code (pivotX, pivotY)} with multiplication factors {@code x}
  41  * and {@code y} is as follows:
  42  * <pre>
  43  *              [   1   x   0   -x*pivotY   ]
  44  *              [   y   1   0   -y*pivotX   ]
  45  *              [   0   0   1       0       ]
  46  * </pre>
  47  *
  48  * <p>
  49  * For example:
  50  * <pre>{@code
  51  * Text text = new Text("Using Shear for pseudo-italic font");
  52  * text.setX(20);
  53  * text.setY(50);
  54  * text.setFont(new Font(20));
  55  *
  56  * text.getTransforms().add(new Shear(-0.35, 0));
  57  * }</pre>
  58  *
  59  * @since JavaFX 2.0
  60  */
  61 public class Shear extends Transform {
  62 
  63     /**
  64      * Creates a default Shear (identity).
  65      */
  66     public Shear() {
  67     }
  68 
  69     /**
  70      * Creates a new instance of Shear.
  71      * The pivot point is set to (0,0)
  72      * @param x the multiplier by which coordinates are shifted in the direction
  73      * of the positive X axis as a factor of their Y coordinate
  74      * @param y the multiplier by which coordinates are shifted in the direction
  75      * of the positive Y axis as a factor of their X coordinate
  76      */
  77     public Shear(double x, double y) {
  78         setX(x);
  79         setY(y);
  80     }
  81 
  82     /**
  83      * Creates a new instance of Shear with pivot.
  84      * @param x the multiplier by which coordinates are shifted in the direction
  85      * of the positive X axis as a factor of their Y coordinate
  86      * @param y the multiplier by which coordinates are shifted in the direction
  87      * of the positive Y axis as a factor of their X coordinate
  88      * @param pivotX the X coordinate of the shear pivot point
  89      * @param pivotY the Y coordinate of the shear pivot point
  90      */
  91     public Shear(double x, double y, double pivotX, double pivotY) {
  92         setX(x);
  93         setY(y);
  94         setPivotX(pivotX);
  95         setPivotY(pivotY);
  96     }
  97 
  98     /**
  99      * Defines the multiplier by which coordinates are shifted in the direction
 100      * of the positive X axis as a factor of their Y coordinate. Typical values
 101      * are in the range -1 to 1, exclusive.
 102      *
 103      * @defaultValue 0.0
 104      */
 105     private DoubleProperty x;
 106 
 107 
 108     public final void setX(double value) {
 109         xProperty().set(value);
 110     }
 111 
 112     public final double getX() {
 113         return x == null ? 0.0 : x.get();
 114     }
 115 
 116     public final DoubleProperty xProperty() {
 117         if (x == null) {
 118             x = new DoublePropertyBase() {
 119 
 120                 @Override
 121                 public void invalidated() {
 122                     transformChanged();
 123                 }
 124 
 125                 @Override
 126                 public Object getBean() {
 127                     return Shear.this;
 128                 }
 129 
 130                 @Override
 131                 public String getName() {
 132                     return "x";
 133                 }
 134             };
 135         }
 136         return x;
 137     }
 138 
 139     /**
 140      * Defines the multiplier by which coordinates are shifted in the direction
 141      * of the positive Y axis as a factor of their X coordinate. Typical values
 142      * are in the range -1 to 1, exclusive.
 143      *
 144      * @defaultValue 0.0
 145      */
 146     private DoubleProperty y;
 147 
 148 
 149     public final void setY(double value) {
 150         yProperty().set(value);
 151     }
 152 
 153     public final double getY() {
 154         return y == null ? 0.0 : y.get();
 155     }
 156 
 157     public final DoubleProperty yProperty() {
 158         if (y == null) {
 159             y = new DoublePropertyBase() {
 160 
 161                 @Override
 162                 public void invalidated() {
 163                     transformChanged();
 164                 }
 165 
 166                 @Override
 167                 public Object getBean() {
 168                     return Shear.this;
 169                 }
 170 
 171                 @Override
 172                 public String getName() {
 173                     return "y";
 174                 }
 175             };
 176         }
 177         return y;
 178     }
 179 
 180     /**
 181      * Defines the X coordinate of the shear pivot point.
 182      *
 183      * @defaultValue 0.0
 184      */
 185     private DoubleProperty pivotX;
 186 
 187 
 188     public final void setPivotX(double value) {
 189         pivotXProperty().set(value);
 190     }
 191 
 192     public final double getPivotX() {
 193         return pivotX == null ? 0.0 : pivotX.get();
 194     }
 195 
 196     public final DoubleProperty pivotXProperty() {
 197         if (pivotX == null) {
 198             pivotX = new DoublePropertyBase() {
 199 
 200                 @Override
 201                 public void invalidated() {
 202                     transformChanged();
 203                 }
 204 
 205                 @Override
 206                 public Object getBean() {
 207                     return Shear.this;
 208                 }
 209 
 210                 @Override
 211                 public String getName() {
 212                     return "pivotX";
 213                 }
 214             };
 215         }
 216         return pivotX;
 217     }
 218 
 219     /**
 220      * Defines the Y coordinate of the shear pivot point.
 221      *
 222      * @defaultValue 0.0
 223      */
 224     private DoubleProperty pivotY;
 225 
 226 
 227     public final void setPivotY(double value) {
 228         pivotYProperty().set(value);
 229     }
 230 
 231     public final double getPivotY() {
 232         return pivotY == null ? 0.0 : pivotY.get();
 233     }
 234 
 235     public final DoubleProperty pivotYProperty() {
 236         if (pivotY == null) {
 237             pivotY = new DoublePropertyBase() {
 238 
 239                 @Override
 240                 public void invalidated() {
 241                     transformChanged();
 242                 }
 243 
 244                 @Override
 245                 public Object getBean() {
 246                     return Shear.this;
 247                 }
 248 
 249                 @Override
 250                 public String getName() {
 251                     return "pivotY";
 252                 }
 253             };
 254         }
 255         return pivotY;
 256     }
 257 
 258     /* *************************************************************************
 259      *                                                                         *
 260      *                         Element getters                                 *
 261      *                                                                         *
 262      **************************************************************************/
 263 
 264     @Override
 265     public double getMxy() {
 266         return getX();
 267     }
 268 
 269     @Override
 270     public double getMyx() {
 271         return getY();
 272     }
 273 
 274     @Override
 275     public double getTx() {
 276         return -getX() * getPivotY();
 277     }
 278 
 279     @Override
 280     public double getTy() {
 281         return -getY() * getPivotX();
 282     }
 283 
 284     /* *************************************************************************
 285      *                                                                         *
 286      *                           State getters                                 *
 287      *                                                                         *
 288      **************************************************************************/
 289 
 290     @Override
 291     boolean computeIs2D() {
 292         return true;
 293     }
 294 
 295     @Override
 296     boolean computeIsIdentity() {
 297         return getX() == 0.0 && getY() == 0.0;
 298     }
 299 
 300     /* *************************************************************************
 301      *                                                                         *
 302      *                           Array getters                                 *
 303      *                                                                         *
 304      **************************************************************************/
 305 
 306     @Override
 307     void fill2DArray(double[] array) {
 308         final double sx = getX();
 309         final double sy = getY();
 310 
 311         array[0] = 1.0;
 312         array[1] = sx;
 313         array[2] = -sx * getPivotY();
 314         array[3] = sy;
 315         array[4] = 1.0;
 316         array[5] = -sy * getPivotX();
 317     }
 318 
 319     @Override
 320     void fill3DArray(double[] array) {
 321         final double sx = getX();
 322         final double sy = getY();
 323 
 324         array[0] = 1.0;
 325         array[1] = sx;
 326         array[2] = 0.0;
 327         array[3] = -sx * getPivotY();
 328         array[4] = sy;
 329         array[5] = 1.0;
 330         array[6] = 0.0;
 331         array[7] = -sy * getPivotX();
 332         array[8] = 0.0;
 333         array[9] = 0.0;
 334         array[10] = 1.0;
 335         array[11] = 0.0;
 336     }
 337 
 338     /* *************************************************************************
 339      *                                                                         *
 340      *                         Transform creators                              *
 341      *                                                                         *
 342      **************************************************************************/
 343 
 344     @Override
 345     public Transform createConcatenation(Transform transform) {
 346 
 347         if (transform instanceof Affine) {
 348             Affine a = (Affine) transform.clone();
 349             a.prepend(this);
 350             return a;
 351         }
 352 
 353         final double sx = getX();
 354         final double sy = getY();
 355 
 356         final double txx = transform.getMxx();
 357         final double txy = transform.getMxy();
 358         final double txz = transform.getMxz();
 359         final double ttx = transform.getTx();
 360         final double tyx = transform.getMyx();
 361         final double tyy = transform.getMyy();
 362         final double tyz = transform.getMyz();
 363         final double tty = transform.getTy();
 364         return new Affine(
 365                 txx + sx * tyx,
 366                 txy + sx * tyy,
 367                 txz + sx * tyz,
 368                 ttx + sx * tty - sx * getPivotY(),
 369                 sy * txx + tyx,
 370                 sy * txy + tyy,
 371                 sy * txz + tyz,
 372                 sy * ttx + tty - sy * getPivotX(),
 373                 transform.getMzx(),
 374                 transform.getMzy(),
 375                 transform.getMzz(),
 376                 transform.getTz());
 377     }
 378 
 379     @Override
 380     public Transform createInverse() {
 381         final double sx = getX();
 382         final double sy = getY();
 383 
 384         if (sy == 0.0) {
 385             return new Shear(-sx, 0.0, 0.0, getPivotY());
 386         }
 387 
 388         if (sx == 0.0) {
 389             return new Shear(0.0, -sy, getPivotX(), 0.0);
 390         }
 391 
 392         final double px = getPivotX();
 393         final double py = getPivotY();
 394         final double coef = 1.0 / (1.0 - sx * sy);
 395 
 396         return new Affine(
 397                 coef,       -sx * coef,         0, sx * (py - sy * px) * coef,
 398                 -sy * coef, 1 + sx * sy * coef, 0, sy * px + sy * (sx * sy * px - sx * py) * coef,
 399                 0,          0,                  1, 0);
 400     }
 401 
 402     @Override
 403     public Shear clone() {
 404         return new Shear(getX(), getY(), getPivotX(), getPivotY());
 405     }
 406 
 407     /* *************************************************************************
 408      *                                                                         *
 409      *                     Transform, Inverse Transform                        *
 410      *                                                                         *
 411      **************************************************************************/
 412 
 413     @Override
 414     public Point2D transform(double x, double y) {
 415         final double mxy = getX();
 416         final double myx = getY();
 417 
 418         return new Point2D(
 419             x + mxy * y - mxy * getPivotY(),
 420             myx * x + y - myx * getPivotX());
 421     }
 422 
 423     @Override
 424     public Point3D transform(double x, double y, double z) {
 425         final double mxy = getX();
 426         final double myx = getY();
 427 
 428         return new Point3D(
 429             x + mxy * y - mxy * getPivotY(),
 430             myx * x + y - myx * getPivotX(),
 431             z);
 432     }
 433 
 434     @Override
 435     void transform2DPointsImpl(double[] srcPts, int srcOff,
 436             double[] dstPts, int dstOff, int numPts) {
 437         final double xy = getX();
 438         final double yx = getY();
 439         final double px = getPivotX();
 440         final double py = getPivotY();
 441 
 442         while (--numPts >= 0) {
 443             final double x = srcPts[srcOff++];
 444             final double y = srcPts[srcOff++];
 445 
 446             dstPts[dstOff++] = x + xy * y - xy * py;
 447             dstPts[dstOff++] = yx * x + y - yx * px;
 448         }
 449     }
 450 
 451     @Override
 452     void transform3DPointsImpl(double[] srcPts, int srcOff,
 453             double[] dstPts, int dstOff, int numPts) {
 454         final double xy = getX();
 455         final double yx = getY();
 456         final double px = getPivotX();
 457         final double py = getPivotY();
 458 
 459         while (--numPts >= 0) {
 460             final double x = srcPts[srcOff++];
 461             final double y = srcPts[srcOff++];
 462 
 463             dstPts[dstOff++] = x + xy * y - xy * py;
 464             dstPts[dstOff++] = yx * x + y - yx * px;
 465             dstPts[dstOff++] = srcPts[srcOff++];
 466         }
 467     }
 468 
 469     @Override
 470     public Point2D deltaTransform(double x, double y) {
 471 
 472         return new Point2D(
 473             x + getX() * y,
 474             getY() * x + y);
 475     }
 476 
 477     @Override
 478     public Point3D deltaTransform(double x, double y, double z) {
 479         return new Point3D(
 480             x + getX() * y,
 481             getY() * x + y,
 482             z);
 483     }
 484 
 485 
 486     @Override
 487     public Point2D inverseTransform(double x, double y)
 488             throws NonInvertibleTransformException {
 489         final double sx = getX();
 490         final double sy = getY();
 491 
 492         if (sy == 0.0) {
 493             final double mxy = -getX();
 494 
 495             return new Point2D(
 496                 x + mxy * y - mxy * getPivotY(),
 497                 y);
 498         }
 499 
 500         if (sx == 0.0) {
 501             final double myx = -getY();
 502 
 503             return new Point2D(
 504                 x,
 505                 myx * x + y - myx * getPivotX());
 506         }
 507 
 508         return super.inverseTransform(x, y);
 509     }
 510 
 511     @Override
 512     public Point3D inverseTransform(double x, double y, double z)
 513             throws NonInvertibleTransformException {
 514         final double sx = getX();
 515         final double sy = getY();
 516 
 517         if (sy == 0.0) {
 518             final double mxy = -getX();
 519 
 520             return new Point3D(
 521                 x + mxy * y - mxy * getPivotY(),
 522                 y,
 523                 z);
 524         }
 525 
 526         if (sx == 0.0) {
 527             final double myx = -getY();
 528 
 529             return new Point3D(
 530                 x,
 531                 myx * x + y - myx * getPivotX(),
 532                 z);
 533         }
 534 
 535         return super.inverseTransform(x, y, z);
 536     }
 537 
 538     @Override
 539     void inverseTransform2DPointsImpl(double[] srcPts, int srcOff,
 540             double[] dstPts, int dstOff, int numPts)
 541             throws NonInvertibleTransformException {
 542 
 543         final double px = getPivotX();
 544         final double py = getPivotY();
 545 
 546         final double sx = getX();
 547         final double sy = getY();
 548 
 549         if (sy == 0.0) {
 550             final double xy = -sx;
 551 
 552             while (--numPts >= 0) {
 553                 final double x = srcPts[srcOff++];
 554                 final double y = srcPts[srcOff++];
 555 
 556                 dstPts[dstOff++] = x + xy * y - xy * py;
 557                 dstPts[dstOff++] = y;
 558             }
 559             return;
 560         }
 561 
 562         if (sx == 0.0) {
 563             final double yx = -sy;
 564 
 565             while (--numPts >= 0) {
 566                 final double x = srcPts[srcOff++];
 567                 final double y = srcPts[srcOff++];
 568 
 569                 dstPts[dstOff++] = x;
 570                 dstPts[dstOff++] = yx * x + y - yx * px;
 571             }
 572             return;
 573         }
 574 
 575         super.inverseTransform2DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
 576     }
 577 
 578     @Override
 579     void inverseTransform3DPointsImpl(double[] srcPts, int srcOff,
 580             double[] dstPts, int dstOff, int numPts)
 581             throws NonInvertibleTransformException{
 582 
 583         final double px = getPivotX();
 584         final double py = getPivotY();
 585 
 586         final double sx = getX();
 587         final double sy = getY();
 588 
 589         if (sy == 0.0) {
 590             final double xy = -sx;
 591 
 592             while (--numPts >= 0) {
 593                 final double x = srcPts[srcOff++];
 594                 final double y = srcPts[srcOff++];
 595 
 596                 dstPts[dstOff++] = x + xy * y - xy * py;
 597                 dstPts[dstOff++] = y;
 598                 dstPts[dstOff++] = srcPts[srcOff++];
 599             }
 600             return;
 601         }
 602 
 603         if (sx == 0.0) {
 604             final double yx = -sy;
 605 
 606             while (--numPts >= 0) {
 607                 final double x = srcPts[srcOff++];
 608                 final double y = srcPts[srcOff++];
 609 
 610                 dstPts[dstOff++] = x;
 611                 dstPts[dstOff++] = yx * x + y - yx * px;
 612                 dstPts[dstOff++] = srcPts[srcOff++];
 613             }
 614             return;
 615         }
 616 
 617         super.inverseTransform3DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
 618     }
 619 
 620     @Override
 621     public Point2D inverseDeltaTransform(double x, double y)
 622             throws NonInvertibleTransformException {
 623         final double sx = getX();
 624         final double sy = getY();
 625 
 626         if (sy == 0.0) {
 627             return new Point2D(
 628                 x - getX() * y,
 629                 y);
 630         }
 631 
 632         if (sx == 0.0) {
 633             return new Point2D(
 634                 x,
 635                 -getY() * x + y);
 636         }
 637 
 638         return super.inverseDeltaTransform(x, y);
 639     }
 640 
 641     @Override
 642     public Point3D inverseDeltaTransform(double x, double y, double z)
 643             throws NonInvertibleTransformException {
 644         final double sx = getX();
 645         final double sy = getY();
 646 
 647         if (sy == 0.0) {
 648             return new Point3D(
 649                 x - getX() * y,
 650                 y,
 651                 z);
 652         }
 653 
 654         if (sx == 0.0) {
 655             return new Point3D(
 656                 x,
 657                 -getY() * x + y,
 658                 z);
 659         }
 660 
 661         return super.inverseDeltaTransform(x, y, z);
 662     }
 663 
 664     /* *************************************************************************
 665      *                                                                         *
 666      *                               Other API                                 *
 667      *                                                                         *
 668      **************************************************************************/
 669 
 670     /**
 671      * Returns a string representation of this {@code Shear} object.
 672      * @return a string representation of this {@code Shear} object.
 673      */
 674     @Override
 675     public String toString() {
 676         final StringBuilder sb = new StringBuilder("Shear [");
 677 
 678         sb.append("x=").append(getX());
 679         sb.append(", y=").append(getY());
 680         sb.append(", pivotX=").append(getPivotX());
 681         sb.append(", pivotY=").append(getPivotY());
 682 
 683         return sb.append("]").toString();
 684     }
 685 
 686     /* *************************************************************************
 687      *                                                                         *
 688      *                    Internal implementation stuff                        *
 689      *                                                                         *
 690      **************************************************************************/
 691 
 692     @Override
 693     void apply(final Affine3D trans) {
 694         if (getPivotX() != 0 || getPivotY() != 0) {
 695             trans.translate(getPivotX(), getPivotY());
 696             trans.shear(getX(), getY());
 697             trans.translate(-getPivotX(), -getPivotY());
 698         } else {
 699             trans.shear(getX(), getY());
 700         }
 701     }
 702 
 703     @Override
 704     BaseTransform derive(final BaseTransform trans) {
 705         return trans.deriveWithConcatenation(
 706                 1.0, getY(),
 707                 getX(), 1.0,
 708                 getTx(), getTy());
 709     }
 710 
 711     @Override
 712     void validate() {
 713         getX(); getPivotX();
 714         getY(); getPivotY();
 715     }
 716 
 717     @Override
 718     void appendTo(Affine a) {
 719         a.appendShear(getX(), getY(), getPivotX(), getPivotY());
 720     }
 721 
 722     @Override
 723     void prependTo(Affine a) {
 724         a.prependShear(getX(), getY(), getPivotX(), getPivotY());
 725     }
 726 }