1 /* 2 * Copyright (c) 2008, 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 javafx.scene.image; 27 28 import com.sun.javafx.beans.event.AbstractNotifyListener; 29 import com.sun.javafx.css.StyleManager; 30 import javafx.css.converter.URLConverter; 31 import com.sun.javafx.geom.BaseBounds; 32 import com.sun.javafx.geom.transform.BaseTransform; 33 import com.sun.javafx.scene.DirtyBits; 34 import com.sun.javafx.scene.ImageViewHelper; 35 import com.sun.javafx.scene.NodeHelper; 36 import com.sun.javafx.sg.prism.NGImageView; 37 import com.sun.javafx.sg.prism.NGNode; 38 import com.sun.javafx.tk.Toolkit; 39 import javafx.beans.DefaultProperty; 40 import javafx.beans.Observable; 41 import javafx.beans.property.*; 42 import javafx.css.CssMetaData; 43 import javafx.css.Styleable; 44 import javafx.css.StyleableProperty; 45 import javafx.css.StyleableStringProperty; 46 import javafx.geometry.NodeOrientation; 47 import javafx.geometry.Rectangle2D; 48 import javafx.scene.AccessibleRole; 49 import javafx.scene.Node; 50 import java.util.ArrayList; 51 import java.util.Collections; 52 import java.util.List; 53 54 /** 55 * The {@code ImageView} is a {@code Node} used for painting images loaded with 56 * {@link Image} class. 57 * 58 * <p> 59 * This class allows resizing the displayed image (with or without preserving 60 * the original aspect ratio) and specifying a viewport into the source image 61 * for restricting the pixels displayed by this {@code ImageView}. 62 * </p> 63 * 64 * 65 * <p> 66 * Example code for displaying images 67 * </p> 68 * 69 * <pre><code> 70 * import javafx.application.Application; 71 * import javafx.geometry.Rectangle2D; 72 * import javafx.scene.Group; 73 * import javafx.scene.Scene; 74 * import javafx.scene.image.Image; 75 * import javafx.scene.image.ImageView; 76 * import javafx.scene.layout.HBox; 77 * import javafx.scene.paint.Color; 78 * import javafx.stage.Stage; 79 * 80 * public class HelloImageView extends Application { 81 * 82 * {@literal @Override} public void start(Stage stage) { 83 * // load the image 84 * Image image = new Image("flower.png"); 85 * 86 * // simple displays ImageView the image as is 87 * ImageView iv1 = new ImageView(); 88 * iv1.setImage(image); 89 * 90 * // resizes the image to have width of 100 while preserving the ratio and using 91 * // higher quality filtering method; this ImageView is also cached to 92 * // improve performance 93 * ImageView iv2 = new ImageView(); 94 * iv2.setImage(image); 95 * iv2.setFitWidth(100); 96 * iv2.setPreserveRatio(true); 97 * iv2.setSmooth(true); 98 * iv2.setCache(true); 99 * 100 * // defines a viewport into the source image (achieving a "zoom" effect) and 101 * // displays it rotated 102 * ImageView iv3 = new ImageView(); 103 * iv3.setImage(image); 104 * Rectangle2D viewportRect = new Rectangle2D(40, 35, 110, 110); 105 * iv3.setViewport(viewportRect); 106 * iv3.setRotate(90); 107 * 108 * Group root = new Group(); 109 * Scene scene = new Scene(root); 110 * scene.setFill(Color.BLACK); 111 * HBox box = new HBox(); 112 * box.getChildren().add(iv1); 113 * box.getChildren().add(iv2); 114 * box.getChildren().add(iv3); 115 * root.getChildren().add(box); 116 * 117 * stage.setTitle("ImageView"); 118 * stage.setWidth(415); 119 * stage.setHeight(200); 120 * stage.setScene(scene); 121 * stage.sizeToScene(); 122 * stage.show(); 123 * } 124 * 125 * public static void main(String[] args) { 126 * Application.launch(args); 127 * } 128 * } 129 * </code></pre> 130 * <p> 131 * The code above produces the following: 132 * </p> 133 * <p> 134 * <img src="doc-files/imageview.png" alt="A visual rendering of the ImageView example"> 135 * </p> 136 * @since JavaFX 2.0 137 */ 138 @DefaultProperty("image") 139 public class ImageView extends Node { 140 static { 141 // This is used by classes in different packages to get access to 142 // private and package private methods. 143 ImageViewHelper.setImageViewAccessor(new ImageViewHelper.ImageViewAccessor() { 144 @Override 145 public NGNode doCreatePeer(Node node) { 146 return ((ImageView) node).doCreatePeer(); 147 } 148 149 @Override 150 public void doUpdatePeer(Node node) { 151 ((ImageView) node).doUpdatePeer(); 152 } 153 154 @Override 155 public BaseBounds doComputeGeomBounds(Node node, 156 BaseBounds bounds, BaseTransform tx) { 157 return ((ImageView) node).doComputeGeomBounds(bounds, tx); 158 } 159 160 @Override 161 public boolean doComputeContains(Node node, double localX, double localY) { 162 return ((ImageView) node).doComputeContains(localX, localY); 163 } 164 }); 165 } 166 167 { 168 // To initialize the class helper at the begining each constructor of this class 169 ImageViewHelper.initHelper(this); 170 } 171 /** 172 * Allocates a new ImageView object. 173 */ 174 public ImageView() { 175 getStyleClass().add(DEFAULT_STYLE_CLASS); 176 setAccessibleRole(AccessibleRole.IMAGE_VIEW); 177 setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); 178 } 179 180 /** 181 * Allocates a new ImageView object with image loaded from the specified 182 * URL. 183 * <p> 184 * The {@code new ImageView(url)} has the same effect as 185 * {@code new ImageView(new Image(url))}. 186 * </p> 187 * 188 * @param url the string representing the URL from which to load the image 189 * @throws NullPointerException if URL is null 190 * @throws IllegalArgumentException if URL is invalid or unsupported 191 * @since JavaFX 2.1 192 */ 193 public ImageView(String url) { 194 this(new Image(url)); 195 } 196 197 /** 198 * Allocates a new ImageView object using the given image. 199 * 200 * @param image Image that this ImageView uses 201 */ 202 public ImageView(Image image) { 203 getStyleClass().add(DEFAULT_STYLE_CLASS); 204 setAccessibleRole(AccessibleRole.IMAGE_VIEW); 205 setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); 206 setImage(image); 207 } 208 209 /** 210 * The {@link Image} to be painted by this {@code ImageView}. 211 * 212 * @defaultValue null 213 */ 214 private ObjectProperty<Image> image; 215 216 public final void setImage(Image value) { 217 imageProperty().set(value); 218 } 219 public final Image getImage() { 220 return image == null ? null : image.get(); 221 } 222 223 private Image oldImage; 224 public final ObjectProperty<Image> imageProperty() { 225 if (image == null) { 226 image = new ObjectPropertyBase<Image>() { 227 228 private boolean needsListeners = false; 229 230 @Override 231 public void invalidated() { 232 Image _image = get(); 233 boolean dimensionChanged = _image == null || oldImage == null || 234 (oldImage.getWidth() != _image.getWidth() || 235 oldImage.getHeight() != _image.getHeight()); 236 237 if (needsListeners) { 238 Toolkit.getImageAccessor().getImageProperty(oldImage). 239 removeListener(platformImageChangeListener.getWeakListener()); 240 } 241 242 needsListeners = _image != null && (_image.isAnimation() || _image.getProgress() < 1); 243 oldImage = _image; 244 245 if (needsListeners) { 246 Toolkit.getImageAccessor().getImageProperty(_image). 247 addListener(platformImageChangeListener.getWeakListener()); 248 } 249 if (dimensionChanged) { 250 invalidateWidthHeight(); 251 NodeHelper.geomChanged(ImageView.this); 252 } 253 NodeHelper.markDirty(ImageView.this, DirtyBits.NODE_CONTENTS); 254 } 255 256 @Override 257 public Object getBean() { 258 return ImageView.this; 259 } 260 261 @Override 262 public String getName() { 263 return "image"; 264 } 265 }; 266 } 267 return image; 268 } 269 270 private StringProperty imageUrl = null; 271 /** 272 * The imageUrl property is set from CSS and then the image property is 273 * set from the invalidated method. This ensures that the same image isn't 274 * reloaded. 275 */ 276 private StringProperty imageUrlProperty() { 277 if (imageUrl == null) { 278 imageUrl = new StyleableStringProperty() { 279 280 @Override 281 protected void invalidated() { 282 283 final String imageUrl = get(); 284 if (imageUrl != null) { 285 setImage(StyleManager.getInstance().getCachedImage(imageUrl)); 286 } else { 287 setImage(null); 288 } 289 } 290 291 @Override 292 public Object getBean() { 293 return ImageView.this; 294 } 295 296 @Override 297 public String getName() { 298 return "imageUrl"; 299 } 300 301 @Override 302 public CssMetaData<ImageView,String> getCssMetaData() { 303 return StyleableProperties.IMAGE; 304 } 305 306 }; 307 } 308 return imageUrl; 309 } 310 311 private final AbstractNotifyListener platformImageChangeListener = 312 new AbstractNotifyListener() { 313 @Override 314 public void invalidated(Observable valueModel) { 315 invalidateWidthHeight(); 316 NodeHelper.markDirty(ImageView.this, DirtyBits.NODE_CONTENTS); 317 NodeHelper.geomChanged(ImageView.this); 318 } 319 }; 320 /** 321 * The current x coordinate of the {@code ImageView} origin. 322 * 323 * @defaultValue 0 324 */ 325 private DoubleProperty x; 326 327 328 public final void setX(double value) { 329 xProperty().set(value); 330 } 331 332 public final double getX() { 333 return x == null ? 0.0 : x.get(); 334 } 335 336 public final DoubleProperty xProperty() { 337 if (x == null) { 338 x = new DoublePropertyBase() { 339 340 @Override 341 protected void invalidated() { 342 NodeHelper.markDirty(ImageView.this, DirtyBits.NODE_GEOMETRY); 343 NodeHelper.geomChanged(ImageView.this); 344 } 345 346 @Override 347 public Object getBean() { 348 return ImageView.this; 349 } 350 351 @Override 352 public String getName() { 353 return "x"; 354 } 355 }; 356 } 357 return x; 358 } 359 360 /** 361 * The current y coordinate of the {@code ImageView} origin. 362 * 363 * @defaultValue 0 364 */ 365 private DoubleProperty y; 366 367 368 public final void setY(double value) { 369 yProperty().set(value); 370 } 371 372 public final double getY() { 373 return y == null ? 0.0 : y.get(); 374 } 375 376 public final DoubleProperty yProperty() { 377 if (y == null) { 378 y = new DoublePropertyBase() { 379 380 @Override 381 protected void invalidated() { 382 NodeHelper.markDirty(ImageView.this, DirtyBits.NODE_GEOMETRY); 383 NodeHelper.geomChanged(ImageView.this); 384 } 385 386 @Override 387 public Object getBean() { 388 return ImageView.this; 389 } 390 391 @Override 392 public String getName() { 393 return "y"; 394 } 395 }; 396 } 397 return y; 398 } 399 400 /** 401 * The width of the bounding box within which the source image is resized as 402 * necessary to fit. If set to a value <= 0, then the intrinsic width of the 403 * image will be used as the {@code fitWidth}. 404 * <p> 405 * See {@link #preserveRatioProperty() preserveRatio} for information on interaction between image 406 * view's {@code fitWidth}, {@code fitHeight} and {@code preserveRatio} 407 * attributes. 408 * 409 * @defaultValue 0 410 */ 411 private DoubleProperty fitWidth; 412 413 414 public final void setFitWidth(double value) { 415 fitWidthProperty().set(value); 416 } 417 418 public final double getFitWidth() { 419 return fitWidth == null ? 0.0 : fitWidth.get(); 420 } 421 422 public final DoubleProperty fitWidthProperty() { 423 if (fitWidth == null) { 424 fitWidth = new DoublePropertyBase() { 425 426 @Override 427 protected void invalidated() { 428 invalidateWidthHeight(); 429 NodeHelper.markDirty(ImageView.this, DirtyBits.NODE_VIEWPORT); 430 NodeHelper.geomChanged(ImageView.this); 431 } 432 433 @Override 434 public Object getBean() { 435 return ImageView.this; 436 } 437 438 @Override 439 public String getName() { 440 return "fitWidth"; 441 } 442 }; 443 } 444 return fitWidth; 445 } 446 447 /** 448 * The height of the bounding box within which the source image is resized 449 * as necessary to fit. If set to a value <= 0, then the intrinsic height of 450 * the image will be used as the {@code fitHeight}. 451 * <p> 452 * See {@link #preserveRatioProperty() preserveRatio} for information on interaction between image 453 * view's {@code fitWidth}, {@code fitHeight} and {@code preserveRatio} 454 * attributes. 455 * </p> 456 * 457 * @defaultValue 0 458 */ 459 private DoubleProperty fitHeight; 460 461 462 public final void setFitHeight(double value) { 463 fitHeightProperty().set(value); 464 } 465 466 public final double getFitHeight() { 467 return fitHeight == null ? 0.0 : fitHeight.get(); 468 } 469 470 public final DoubleProperty fitHeightProperty() { 471 if (fitHeight == null) { 472 fitHeight = new DoublePropertyBase() { 473 474 @Override 475 protected void invalidated() { 476 invalidateWidthHeight(); 477 NodeHelper.markDirty(ImageView.this, DirtyBits.NODE_VIEWPORT); 478 NodeHelper.geomChanged(ImageView.this); 479 } 480 481 @Override 482 public Object getBean() { 483 return ImageView.this; 484 } 485 486 @Override 487 public String getName() { 488 return "fitHeight"; 489 } 490 }; 491 } 492 return fitHeight; 493 } 494 495 /** 496 * Indicates whether to preserve the aspect ratio of the source image when 497 * scaling to fit the image within the fitting bounding box. 498 * <p> 499 * If set to {@code true}, it affects the dimensions of this 500 * {@code ImageView} in the following way 501 * <ul> 502 * <li>If only {@code fitWidth} is set, height is scaled to preserve ratio 503 * <li>If only {@code fitHeight} is set, width is scaled to preserve ratio 504 * <li>If both are set, they both may be scaled to get the best fit in a 505 * width by height rectangle while preserving the original aspect ratio 506 * </ul> 507 * 508 * If unset or set to {@code false}, it affects the dimensions of this 509 * {@code ImageView} in the following way 510 * <ul> 511 * <li>If only {@code fitWidth} is set, image's view width is scaled to 512 * match and height is unchanged; 513 * <li>If only {@code fitHeight} is set, image's view height is scaled to 514 * match and height is unchanged; 515 * <li>If both are set, the image view is scaled to match both. 516 * </ul> 517 * 518 * Note that the dimensions of this node as reported by the node's bounds 519 * will be equal to the size of the scaled image and is guaranteed to be 520 * contained within {@code fitWidth x fitHeight} bonding box. 521 * 522 * @defaultValue false 523 */ 524 private BooleanProperty preserveRatio; 525 526 527 public final void setPreserveRatio(boolean value) { 528 preserveRatioProperty().set(value); 529 } 530 531 public final boolean isPreserveRatio() { 532 return preserveRatio == null ? false : preserveRatio.get(); 533 } 534 535 public final BooleanProperty preserveRatioProperty() { 536 if (preserveRatio == null) { 537 preserveRatio = new BooleanPropertyBase() { 538 539 @Override 540 protected void invalidated() { 541 invalidateWidthHeight(); 542 NodeHelper.markDirty(ImageView.this, DirtyBits.NODE_VIEWPORT); 543 NodeHelper.geomChanged(ImageView.this); 544 } 545 546 @Override 547 public Object getBean() { 548 return ImageView.this; 549 } 550 551 @Override 552 public String getName() { 553 return "preserveRatio"; 554 } 555 }; 556 } 557 return preserveRatio; 558 } 559 560 /** 561 * Indicates whether to use a better quality filtering algorithm or a faster 562 * one when transforming or scaling the source image to fit within the 563 * bounding box provided by {@code fitWidth} and {@code fitHeight}. 564 * 565 * <p> 566 * If set to {@code true} a better quality filtering will be used, if set to 567 * {@code false} a faster but lesser quality filtering will be used. 568 * </p> 569 * 570 * <p> 571 * The default value depends on platform configuration. 572 * </p> 573 * 574 * @defaultValue platform-dependent 575 */ 576 private BooleanProperty smooth; 577 578 579 public final void setSmooth(boolean value) { 580 smoothProperty().set(value); 581 } 582 583 public final boolean isSmooth() { 584 return smooth == null ? SMOOTH_DEFAULT : smooth.get(); 585 } 586 587 public final BooleanProperty smoothProperty() { 588 if (smooth == null) { 589 smooth = new BooleanPropertyBase(SMOOTH_DEFAULT) { 590 591 @Override 592 protected void invalidated() { 593 NodeHelper.markDirty(ImageView.this, DirtyBits.NODE_SMOOTH); 594 } 595 596 @Override 597 public Object getBean() { 598 return ImageView.this; 599 } 600 601 @Override 602 public String getName() { 603 return "smooth"; 604 } 605 }; 606 } 607 return smooth; 608 } 609 610 /** 611 * Platform-dependent default value of the {@link #smoothProperty() smooth} property. 612 */ 613 public static final boolean SMOOTH_DEFAULT = Toolkit.getToolkit() 614 .getDefaultImageSmooth(); 615 /** 616 * The rectangular viewport into the image. The viewport is specified in the 617 * coordinates of the image, prior to scaling or any other transformations. 618 * 619 * <p> 620 * If {@code viewport} is {@code null}, the entire image is displayed. If 621 * {@code viewport} is non-{@code null}, only the portion of the image which 622 * falls within the viewport will be displayed. If the image does not fully 623 * cover the viewport then any remaining area of the viewport will be empty. 624 * </p> 625 * 626 * @defaultValue null 627 */ 628 private ObjectProperty<Rectangle2D> viewport; 629 630 631 public final void setViewport(Rectangle2D value) { 632 viewportProperty().set(value); 633 } 634 635 public final Rectangle2D getViewport() { 636 return viewport == null ? null : viewport.get(); 637 } 638 639 public final ObjectProperty<Rectangle2D> viewportProperty() { 640 if (viewport == null) { 641 viewport = new ObjectPropertyBase<Rectangle2D>() { 642 643 @Override 644 protected void invalidated() { 645 invalidateWidthHeight(); 646 NodeHelper.markDirty(ImageView.this, DirtyBits.NODE_VIEWPORT); 647 NodeHelper.geomChanged(ImageView.this); 648 } 649 650 @Override 651 public Object getBean() { 652 return ImageView.this; 653 } 654 655 @Override 656 public String getName() { 657 return "viewport"; 658 } 659 }; 660 } 661 return viewport; 662 } 663 664 // Need to track changes to image width and image height and recompute 665 // bounds when changed. 666 // imageWidth = bind image.width on replace { 667 // NodeHelper.geomChanged(ImageView.this); 668 // } 669 // 670 // imageHeight = bind image.height on replace { 671 // NodeHelper.geomChanged(ImageView.this); 672 // } 673 674 private double destWidth, destHeight; 675 676 /* 677 * Note: This method MUST only be called via its accessor method. 678 */ 679 private NGNode doCreatePeer() { 680 return new NGImageView(); 681 } 682 683 /* 684 * Note: This method MUST only be called via its accessor method. 685 */ 686 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { 687 recomputeWidthHeight(); 688 689 bounds = bounds.deriveWithNewBounds((float)getX(), (float)getY(), 0.0f, 690 (float)(getX() + destWidth), (float)(getY() + destHeight), 0.0f); 691 bounds = tx.transform(bounds, bounds); 692 return bounds; 693 } 694 695 private boolean validWH; 696 697 private void invalidateWidthHeight() { 698 validWH = false; 699 } 700 701 private void recomputeWidthHeight() { 702 if (validWH) { 703 return; 704 } 705 Image localImage = getImage(); 706 Rectangle2D localViewport = getViewport(); 707 708 double w = 0; 709 double h = 0; 710 if (localViewport != null && localViewport.getWidth() > 0 && localViewport.getHeight() > 0) { 711 w = localViewport.getWidth(); 712 h = localViewport.getHeight(); 713 } else if (localImage != null) { 714 w = localImage.getWidth(); 715 h = localImage.getHeight(); 716 } 717 718 double localFitWidth = getFitWidth(); 719 double localFitHeight = getFitHeight(); 720 721 if (isPreserveRatio() && w > 0 && h > 0 && (localFitWidth > 0 || localFitHeight > 0)) { 722 if (localFitWidth <= 0 || (localFitHeight > 0 && localFitWidth * h > localFitHeight * w)) { 723 w = w * localFitHeight / h; 724 h = localFitHeight; 725 } else { 726 h = h * localFitWidth / w; 727 w = localFitWidth; 728 } 729 } else { 730 if (localFitWidth > 0f) { 731 w = localFitWidth; 732 } 733 if (localFitHeight > 0f) { 734 h = localFitHeight; 735 } 736 } 737 738 // Store these values for use later in doComputeContains() to support 739 // Node.contains(). 740 destWidth = w; 741 destHeight = h; 742 743 validWH = true; 744 } 745 746 /* 747 * Note: This method MUST only be called via its accessor method. 748 */ 749 private boolean doComputeContains(double localX, double localY) { 750 if (getImage() == null) { 751 return false; 752 } 753 754 recomputeWidthHeight(); 755 // Local Note bounds contain test is already done by the caller. 756 // (Node.contains()). 757 758 double dx = localX - getX(); 759 double dy = localY - getY(); 760 761 Image localImage = getImage(); 762 double srcWidth = localImage.getWidth(); 763 double srcHeight = localImage.getHeight(); 764 double viewWidth = srcWidth; 765 double viewHeight = srcHeight; 766 double vw = 0; 767 double vh = 0; 768 double vminx = 0; 769 double vminy = 0; 770 Rectangle2D localViewport = getViewport(); 771 if (localViewport != null) { 772 vw = localViewport.getWidth(); 773 vh = localViewport.getHeight(); 774 vminx = localViewport.getMinX(); 775 vminy = localViewport.getMinY(); 776 } 777 778 if (vw > 0 && vh > 0) { 779 viewWidth = vw; 780 viewHeight = vh; 781 } 782 783 // desWidth Note and destHeight are computed by NodeHelper.computeGeomBounds() 784 // via a call from Node.contains() before calling 785 // doComputeContains(). 786 // Transform into image's coordinate system. 787 dx = vminx + dx * viewWidth / destWidth; 788 dy = vminy + dy * viewHeight / destHeight; 789 // test whether it's inside the original image AND inside of viewport 790 // (viewport may stick out from the image bounds) 791 if (dx < 0.0 || dy < 0.0 || dx >= srcWidth || dy >= srcHeight || 792 dx < vminx || dy < vminy || 793 dx >= vminx + viewWidth || dy >= vminy + viewHeight) { 794 return false; 795 } 796 // Do alpha test on the picked pixel. 797 return Toolkit.getToolkit().imageContains( 798 Toolkit.getImageAccessor().getPlatformImage(localImage), (float)dx, (float)dy); 799 } 800 801 /*************************************************************************** 802 * * Stylesheet Handling * * 803 **************************************************************************/ 804 805 private static final String DEFAULT_STYLE_CLASS = "image-view"; 806 807 /* 808 * Super-lazy instantiation pattern from Bill Pugh. 809 */ 810 private static class StyleableProperties { 811 // TODO 812 // "preserve-ratio","smooth","viewport","fit-width","fit-height" 813 private static final CssMetaData<ImageView, String> IMAGE = 814 new CssMetaData<ImageView,String>("-fx-image", 815 URLConverter.getInstance()) { 816 817 @Override 818 public boolean isSettable(ImageView n) { 819 // Note that we care about the image, not imageUrl 820 return n.image == null || !n.image.isBound(); 821 } 822 823 @Override 824 public StyleableProperty<String> getStyleableProperty(ImageView n) { 825 return (StyleableProperty<String>)n.imageUrlProperty(); 826 } 827 }; 828 829 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 830 static { 831 final List<CssMetaData<? extends Styleable, ?>> styleables = 832 new ArrayList<CssMetaData<? extends Styleable, ?>>(Node.getClassCssMetaData()); 833 styleables.add(IMAGE); 834 STYLEABLES = Collections.unmodifiableList(styleables); 835 } 836 } 837 838 /** 839 * @return The CssMetaData associated with this class, which may include the 840 * CssMetaData of its superclasses. 841 * @since JavaFX 8.0 842 */ 843 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 844 return StyleableProperties.STYLEABLES; 845 } 846 847 /** 848 * {@inheritDoc} 849 * @return the CssMetaData associated with this class, which may include the 850 * CssMetaData of its super classes. 851 * @since JavaFX 8.0 852 */ 853 @Override 854 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 855 return getClassCssMetaData(); 856 } 857 858 void updateViewport() { 859 recomputeWidthHeight(); 860 if (getImage() == null || Toolkit.getImageAccessor().getPlatformImage(getImage()) == null) { 861 return; 862 } 863 864 Rectangle2D localViewport = getViewport(); 865 final NGImageView peer = NodeHelper.getPeer(this); 866 if (localViewport != null) { 867 peer.setViewport((float)localViewport.getMinX(), (float)localViewport.getMinY(), 868 (float)localViewport.getWidth(), (float)localViewport.getHeight(), 869 (float)destWidth, (float)destHeight); 870 } else { 871 peer.setViewport(0, 0, 0, 0, (float)destWidth, (float)destHeight); 872 } 873 } 874 875 /* 876 * Note: This method MUST only be called via its accessor method. 877 */ 878 private void doUpdatePeer() { 879 final NGImageView peer = NodeHelper.getPeer(this); 880 if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) { 881 peer.setX((float)getX()); 882 peer.setY((float)getY()); 883 } 884 if (NodeHelper.isDirty(this, DirtyBits.NODE_SMOOTH)) { 885 peer.setSmooth(isSmooth()); 886 } 887 if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) { 888 peer.setImage(getImage() != null 889 ? Toolkit.getImageAccessor().getPlatformImage(getImage()) : null); 890 } 891 // The NG part expects this to be called when image changes 892 if (NodeHelper.isDirty(this, DirtyBits.NODE_VIEWPORT) || NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) { 893 updateViewport(); 894 } 895 } 896 }