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