1 /*
   2  * Copyright (c) 1997, 2015, 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 package javax.swing;
  26 
  27 import java.awt.*;
  28 import java.awt.image.*;
  29 import java.beans.ConstructorProperties;
  30 import java.beans.BeanProperty;
  31 import java.beans.Transient;
  32 import java.net.URL;
  33 
  34 import java.io.Serializable;
  35 import java.io.ObjectOutputStream;
  36 import java.io.ObjectInputStream;
  37 import java.io.IOException;
  38 
  39 import java.util.Locale;
  40 import javax.accessibility.*;
  41 
  42 import sun.awt.AppContext;
  43 import java.security.*;
  44 import sun.awt.AWTAccessor;
  45 
  46 /**
  47  * An implementation of the Icon interface that paints Icons
  48  * from Images. Images that are created from a URL, filename or byte array
  49  * are preloaded using MediaTracker to monitor the loaded state
  50  * of the image.
  51  *
  52  * <p>
  53  * For further information and examples of using image icons, see
  54  * <a href="https://docs.oracle.com/javase/tutorial/uiswing/components/icon.html">How to Use Icons</a>
  55  * in <em>The Java Tutorial.</em>
  56  *
  57  * <p>
  58  * <strong>Warning:</strong>
  59  * Serialized objects of this class will not be compatible with
  60  * future Swing releases. The current serialization support is
  61  * appropriate for short term storage or RMI between applications running
  62  * the same version of Swing.  As of 1.4, support for long term storage
  63  * of all JavaBeans&trade;
  64  * has been added to the <code>java.beans</code> package.
  65  * Please see {@link java.beans.XMLEncoder}.
  66  *
  67  * @author Jeff Dinkins
  68  * @author Lynn Monsanto
  69  * @since 1.2
  70  */
  71 @SuppressWarnings("serial") // Same-version serialization only
  72 public class ImageIcon implements Icon, Serializable, Accessible {
  73     /* Keep references to the filename and location so that
  74      * alternate persistence schemes have the option to archive
  75      * images symbolically rather than including the image data
  76      * in the archive.
  77      */
  78     private transient String filename;
  79     private transient URL location;
  80 
  81     transient Image image;
  82     transient int loadStatus = 0;
  83     ImageObserver imageObserver;
  84     String description = null;
  85 
  86     /**
  87      * Do not use this shared component, which is used to track image loading.
  88      * It is left for backward compatibility only.
  89      * @deprecated since 1.8
  90      */
  91     @Deprecated
  92     protected static final Component component;
  93 
  94     /**
  95      * Do not use this shared media tracker, which is used to load images.
  96      * It is left for backward compatibility only.
  97      * @deprecated since 1.8
  98      */
  99     @Deprecated
 100     protected static final MediaTracker tracker;
 101 
 102     static {
 103         component = AccessController.doPrivileged(new PrivilegedAction<Component>() {
 104             public Component run() {
 105                 try {
 106                     final Component component = createNoPermsComponent();
 107 
 108                     // 6482575 - clear the appContext field so as not to leak it
 109                     AWTAccessor.getComponentAccessor().
 110                             setAppContext(component, null);
 111 
 112                     return component;
 113                 } catch (Throwable e) {
 114                     // We don't care about component.
 115                     // So don't prevent class initialisation.
 116                     e.printStackTrace();
 117                     return null;
 118                 }
 119             }
 120         });
 121         tracker = new MediaTracker(component);
 122     }
 123 
 124     private static Component createNoPermsComponent() {
 125         // 7020198 - set acc field to no permissions and no subject
 126         // Note, will have appContext set.
 127         return AccessController.doPrivileged(
 128                 new PrivilegedAction<Component>() {
 129                     public Component run() {
 130                         return new Component() {
 131                         };
 132                     }
 133                 },
 134                 new AccessControlContext(new ProtectionDomain[]{
 135                         new ProtectionDomain(null, null)
 136                 })
 137         );
 138     }
 139 
 140     /**
 141      * Id used in loading images from MediaTracker.
 142      */
 143     private static int mediaTrackerID;
 144 
 145     private static final Object TRACKER_KEY = new StringBuilder("TRACKER_KEY");
 146 
 147     int width = -1;
 148     int height = -1;
 149 
 150     /**
 151      * Creates an ImageIcon from the specified file. The image will
 152      * be preloaded by using MediaTracker to monitor the loading state
 153      * of the image.
 154      * @param filename the name of the file containing the image
 155      * @param description a brief textual description of the image
 156      * @see #ImageIcon(String)
 157      */
 158     public ImageIcon(String filename, String description) {
 159         image = Toolkit.getDefaultToolkit().getImage(filename);
 160         if (image == null) {
 161             return;
 162         }
 163         this.filename = filename;
 164         this.description = description;
 165         loadImage(image);
 166     }
 167 
 168     /**
 169      * Creates an ImageIcon from the specified file. The image will
 170      * be preloaded by using MediaTracker to monitor the loading state
 171      * of the image. The specified String can be a file name or a
 172      * file path. When specifying a path, use the Internet-standard
 173      * forward-slash ("/") as a separator.
 174      * (The string is converted to an URL, so the forward-slash works
 175      * on all systems.)
 176      * For example, specify:
 177      * <pre>
 178      *    new ImageIcon("images/myImage.gif") </pre>
 179      * The description is initialized to the <code>filename</code> string.
 180      *
 181      * @param filename a String specifying a filename or path
 182      * @see #getDescription
 183      */
 184     @ConstructorProperties({"description"})
 185     public ImageIcon (String filename) {
 186         this(filename, filename);
 187     }
 188 
 189     /**
 190      * Creates an ImageIcon from the specified URL. The image will
 191      * be preloaded by using MediaTracker to monitor the loaded state
 192      * of the image.
 193      * @param location the URL for the image
 194      * @param description a brief textual description of the image
 195      * @see #ImageIcon(String)
 196      */
 197     public ImageIcon(URL location, String description) {
 198         image = Toolkit.getDefaultToolkit().getImage(location);
 199         if (image == null) {
 200             return;
 201         }
 202         this.location = location;
 203         this.description = description;
 204         loadImage(image);
 205     }
 206 
 207     /**
 208      * Creates an ImageIcon from the specified URL. The image will
 209      * be preloaded by using MediaTracker to monitor the loaded state
 210      * of the image.
 211      * The icon's description is initialized to be
 212      * a string representation of the URL.
 213      * @param location the URL for the image
 214      * @see #getDescription
 215      */
 216     public ImageIcon (URL location) {
 217         this(location, location.toExternalForm());
 218     }
 219 
 220     /**
 221      * Creates an ImageIcon from the image.
 222      * @param image the image
 223      * @param description a brief textual description of the image
 224      */
 225     public ImageIcon(Image image, String description) {
 226         this(image);
 227         this.description = description;
 228     }
 229 
 230     /**
 231      * Creates an ImageIcon from an image object.
 232      * If the image has a "comment" property that is a string,
 233      * then the string is used as the description of this icon.
 234      * @param image the image
 235      * @see #getDescription
 236      * @see java.awt.Image#getProperty
 237      */
 238     public ImageIcon (Image image) {
 239         this.image = image;
 240         Object o = image.getProperty("comment", imageObserver);
 241         if (o instanceof String) {
 242             description = (String) o;
 243         }
 244         loadImage(image);
 245     }
 246 
 247     /**
 248      * Creates an ImageIcon from an array of bytes which were
 249      * read from an image file containing a supported image format,
 250      * such as GIF, JPEG, or (as of 1.3) PNG.
 251      * Normally this array is created
 252      * by reading an image using Class.getResourceAsStream(), but
 253      * the byte array may also be statically stored in a class.
 254      *
 255      * @param  imageData an array of pixels in an image format supported
 256      *         by the AWT Toolkit, such as GIF, JPEG, or (as of 1.3) PNG
 257      * @param  description a brief textual description of the image
 258      * @see    java.awt.Toolkit#createImage
 259      */
 260     public ImageIcon (byte[] imageData, String description) {
 261         this.image = Toolkit.getDefaultToolkit().createImage(imageData);
 262         if (image == null) {
 263             return;
 264         }
 265         this.description = description;
 266         loadImage(image);
 267     }
 268 
 269     /**
 270      * Creates an ImageIcon from an array of bytes which were
 271      * read from an image file containing a supported image format,
 272      * such as GIF, JPEG, or (as of 1.3) PNG.
 273      * Normally this array is created
 274      * by reading an image using Class.getResourceAsStream(), but
 275      * the byte array may also be statically stored in a class.
 276      * If the resulting image has a "comment" property that is a string,
 277      * then the string is used as the description of this icon.
 278      *
 279      * @param  imageData an array of pixels in an image format supported by
 280      *             the AWT Toolkit, such as GIF, JPEG, or (as of 1.3) PNG
 281      * @see    java.awt.Toolkit#createImage
 282      * @see #getDescription
 283      * @see java.awt.Image#getProperty
 284      */
 285     public ImageIcon (byte[] imageData) {
 286         this.image = Toolkit.getDefaultToolkit().createImage(imageData);
 287         if (image == null) {
 288             return;
 289         }
 290         Object o = image.getProperty("comment", imageObserver);
 291         if (o instanceof String) {
 292             description = (String) o;
 293         }
 294         loadImage(image);
 295     }
 296 
 297     /**
 298      * Creates an uninitialized image icon.
 299      */
 300     public ImageIcon() {
 301     }
 302 
 303     /**
 304      * Loads the image, returning only when the image is loaded.
 305      * @param image the image
 306      */
 307     protected void loadImage(Image image) {
 308         MediaTracker mTracker = getTracker();
 309         synchronized(mTracker) {
 310             int id = getNextID();
 311 
 312             mTracker.addImage(image, id);
 313             try {
 314                 mTracker.waitForID(id, 0);
 315             } catch (InterruptedException e) {
 316                 System.out.println("INTERRUPTED while loading Image");
 317             }
 318             loadStatus = mTracker.statusID(id, false);
 319             mTracker.removeImage(image, id);
 320 
 321             width = image.getWidth(imageObserver);
 322             height = image.getHeight(imageObserver);
 323         }
 324     }
 325 
 326     /**
 327      * Returns an ID to use with the MediaTracker in loading an image.
 328      */
 329     private int getNextID() {
 330         synchronized(getTracker()) {
 331             return ++mediaTrackerID;
 332         }
 333     }
 334 
 335     /**
 336      * Returns the MediaTracker for the current AppContext, creating a new
 337      * MediaTracker if necessary.
 338      */
 339     private MediaTracker getTracker() {
 340         Object trackerObj;
 341         AppContext ac = AppContext.getAppContext();
 342         // Opt: Only synchronize if trackerObj comes back null?
 343         // If null, synchronize, re-check for null, and put new tracker
 344         synchronized(ac) {
 345             trackerObj = ac.get(TRACKER_KEY);
 346             if (trackerObj == null) {
 347                 Component comp = new Component() {};
 348                 trackerObj = new MediaTracker(comp);
 349                 ac.put(TRACKER_KEY, trackerObj);
 350             }
 351         }
 352         return (MediaTracker) trackerObj;
 353     }
 354 
 355     /**
 356      * Returns the status of the image loading operation.
 357      * @return the loading status as defined by java.awt.MediaTracker
 358      * @see java.awt.MediaTracker#ABORTED
 359      * @see java.awt.MediaTracker#ERRORED
 360      * @see java.awt.MediaTracker#COMPLETE
 361      */
 362     public int getImageLoadStatus() {
 363         return loadStatus;
 364     }
 365 
 366     /**
 367      * Returns this icon's <code>Image</code>.
 368      * @return the <code>Image</code> object for this <code>ImageIcon</code>
 369      */
 370     @Transient
 371     public Image getImage() {
 372         return image;
 373     }
 374 
 375     /**
 376      * Sets the image displayed by this icon.
 377      * @param image the image
 378      */
 379     public void setImage(Image image) {
 380         this.image = image;
 381         loadImage(image);
 382     }
 383 
 384     /**
 385      * Gets the description of the image.  This is meant to be a brief
 386      * textual description of the object.  For example, it might be
 387      * presented to a blind user to give an indication of the purpose
 388      * of the image.
 389      * The description may be null.
 390      *
 391      * @return a brief textual description of the image
 392      */
 393     public String getDescription() {
 394         return description;
 395     }
 396 
 397     /**
 398      * Sets the description of the image.  This is meant to be a brief
 399      * textual description of the object.  For example, it might be
 400      * presented to a blind user to give an indication of the purpose
 401      * of the image.
 402      * @param description a brief textual description of the image
 403      */
 404     public void setDescription(String description) {
 405         this.description = description;
 406     }
 407 
 408     /**
 409      * Paints the icon.
 410      * The top-left corner of the icon is drawn at
 411      * the point (<code>x</code>, <code>y</code>)
 412      * in the coordinate space of the graphics context <code>g</code>.
 413      * If this icon has no image observer,
 414      * this method uses the <code>c</code> component
 415      * as the observer.
 416      *
 417      * @param c the component to be used as the observer
 418      *          if this icon has no image observer
 419      * @param g the graphics context
 420      * @param x the X coordinate of the icon's top-left corner
 421      * @param y the Y coordinate of the icon's top-left corner
 422      */
 423     public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
 424         if(imageObserver == null) {
 425            g.drawImage(image, x, y, c);
 426         } else {
 427            g.drawImage(image, x, y, imageObserver);
 428         }
 429     }
 430 
 431     /**
 432      * Gets the width of the icon.
 433      *
 434      * @return the width in pixels of this icon
 435      */
 436     public int getIconWidth() {
 437         return width;
 438     }
 439 
 440     /**
 441      * Gets the height of the icon.
 442      *
 443      * @return the height in pixels of this icon
 444      */
 445     public int getIconHeight() {
 446         return height;
 447     }
 448 
 449     /**
 450      * Sets the image observer for the image.  Set this
 451      * property if the ImageIcon contains an animated GIF, so
 452      * the observer is notified to update its display.
 453      * For example:
 454      * <pre>
 455      *     icon = new ImageIcon(...)
 456      *     button.setIcon(icon);
 457      *     icon.setImageObserver(button);
 458      * </pre>
 459      *
 460      * @param observer the image observer
 461      */
 462     public void setImageObserver(ImageObserver observer) {
 463         imageObserver = observer;
 464     }
 465 
 466     /**
 467      * Returns the image observer for the image.
 468      *
 469      * @return the image observer, which may be null
 470      */
 471     @Transient
 472     public ImageObserver getImageObserver() {
 473         return imageObserver;
 474     }
 475 
 476     /**
 477      * Returns a string representation of this image.
 478      *
 479      * @return a string representing this image
 480      */
 481     public String toString() {
 482         if (description != null) {
 483             return description;
 484         }
 485         return super.toString();
 486     }
 487 
 488     private void readObject(ObjectInputStream s)
 489         throws ClassNotFoundException, IOException
 490     {
 491         ObjectInputStream.GetField f = s.readFields();
 492 
 493         imageObserver = (ImageObserver) f.get("imageObserver", null);
 494         description = (String) f.get("description", null);
 495         width = f.get("width", -1);
 496         height = f.get("height", -1);
 497         accessibleContext = (AccessibleImageIcon) f.get("accessibleContext", null);
 498 
 499         int w = s.readInt();
 500         int h = s.readInt();
 501         int[] pixels = (int[])(s.readObject());
 502 
 503         if (pixels == null && (w != -1 || h != -1)) {
 504             throw new IllegalStateException("Inconsistent width and height"
 505                     + " for null image [" + w + ", " + h + "]");
 506         }
 507 
 508         if (pixels != null && (w < 0 || h < 0)) {
 509             throw new IllegalStateException("Inconsistent width and height"
 510                     + " for image [" + w + ", " + h + "]");
 511         }
 512 
 513         if (w != getIconWidth() || h != getIconHeight()) {
 514             throw new IllegalStateException("Inconsistent width and height"
 515                     + " for image [" + w + ", " + h + "]");
 516         }
 517 
 518         if (pixels != null) {
 519             Toolkit tk = Toolkit.getDefaultToolkit();
 520             ColorModel cm = ColorModel.getRGBdefault();
 521             image = tk.createImage(new MemoryImageSource(w, h, cm, pixels, 0, w));
 522             loadImage(image);
 523         }
 524     }
 525 
 526 
 527     private void writeObject(ObjectOutputStream s)
 528         throws IOException
 529     {
 530         s.defaultWriteObject();
 531 
 532         int w = getIconWidth();
 533         int h = getIconHeight();
 534         int[] pixels = image != null? new int[w * h] : null;
 535 
 536         if (image != null) {
 537             try {
 538                 PixelGrabber pg = new PixelGrabber(image, 0, 0, w, h, pixels, 0, w);
 539                 pg.grabPixels();
 540                 if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
 541                     throw new IOException("failed to load image contents");
 542                 }
 543             }
 544             catch (InterruptedException e) {
 545                 throw new IOException("image load interrupted");
 546             }
 547         }
 548 
 549         s.writeInt(w);
 550         s.writeInt(h);
 551         s.writeObject(pixels);
 552     }
 553 
 554     /**
 555      * --- Accessibility Support ---
 556      */
 557 
 558     private AccessibleImageIcon accessibleContext = null;
 559 
 560     /**
 561      * Gets the AccessibleContext associated with this ImageIcon.
 562      * For image icons, the AccessibleContext takes the form of an
 563      * AccessibleImageIcon.
 564      * A new AccessibleImageIcon instance is created if necessary.
 565      *
 566      * @return an AccessibleImageIcon that serves as the
 567      *         AccessibleContext of this ImageIcon
 568      * @since 1.3
 569      */
 570     @BeanProperty(expert = true, description
 571             = "The AccessibleContext associated with this ImageIcon.")
 572     public AccessibleContext getAccessibleContext() {
 573         if (accessibleContext == null) {
 574             accessibleContext = new AccessibleImageIcon();
 575         }
 576         return accessibleContext;
 577     }
 578 
 579     /**
 580      * This class implements accessibility support for the
 581      * <code>ImageIcon</code> class.  It provides an implementation of the
 582      * Java Accessibility API appropriate to image icon user-interface
 583      * elements.
 584      * <p>
 585      * <strong>Warning:</strong>
 586      * Serialized objects of this class will not be compatible with
 587      * future Swing releases. The current serialization support is
 588      * appropriate for short term storage or RMI between applications running
 589      * the same version of Swing.  As of 1.4, support for long term storage
 590      * of all JavaBeans&trade;
 591      * has been added to the <code>java.beans</code> package.
 592      * Please see {@link java.beans.XMLEncoder}.
 593      * @since 1.3
 594      */
 595     @SuppressWarnings("serial") // Same-version serialization only
 596     protected class AccessibleImageIcon extends AccessibleContext
 597         implements AccessibleIcon, Serializable {
 598 
 599         /*
 600          * AccessibleContest implementation -----------------
 601          */
 602 
 603         /**
 604          * Gets the role of this object.
 605          *
 606          * @return an instance of AccessibleRole describing the role of the
 607          * object
 608          * @see AccessibleRole
 609          */
 610         public AccessibleRole getAccessibleRole() {
 611             return AccessibleRole.ICON;
 612         }
 613 
 614         /**
 615          * Gets the state of this object.
 616          *
 617          * @return an instance of AccessibleStateSet containing the current
 618          * state set of the object
 619          * @see AccessibleState
 620          */
 621         public AccessibleStateSet getAccessibleStateSet() {
 622             return null;
 623         }
 624 
 625         /**
 626          * Gets the Accessible parent of this object.  If the parent of this
 627          * object implements Accessible, this method should simply return
 628          * getParent().
 629          *
 630          * @return the Accessible parent of this object -- can be null if this
 631          * object does not have an Accessible parent
 632          */
 633         public Accessible getAccessibleParent() {
 634             return null;
 635         }
 636 
 637         /**
 638          * Gets the index of this object in its accessible parent.
 639          *
 640          * @return the index of this object in its parent; -1 if this
 641          * object does not have an accessible parent.
 642          * @see #getAccessibleParent
 643          */
 644         public int getAccessibleIndexInParent() {
 645             return -1;
 646         }
 647 
 648         /**
 649          * Returns the number of accessible children in the object.  If all
 650          * of the children of this object implement Accessible, than this
 651          * method should return the number of children of this object.
 652          *
 653          * @return the number of accessible children in the object.
 654          */
 655         public int getAccessibleChildrenCount() {
 656             return 0;
 657         }
 658 
 659         /**
 660          * Returns the nth Accessible child of the object.
 661          *
 662          * @param i zero-based index of child
 663          * @return the nth Accessible child of the object
 664          */
 665         public Accessible getAccessibleChild(int i) {
 666             return null;
 667         }
 668 
 669         /**
 670          * Returns the locale of this object.
 671          *
 672          * @return the locale of this object
 673          */
 674         public Locale getLocale() throws IllegalComponentStateException {
 675             return null;
 676         }
 677 
 678         /*
 679          * AccessibleIcon implementation -----------------
 680          */
 681 
 682         /**
 683          * Gets the description of the icon.  This is meant to be a brief
 684          * textual description of the object.  For example, it might be
 685          * presented to a blind user to give an indication of the purpose
 686          * of the icon.
 687          *
 688          * @return the description of the icon
 689          */
 690         public String getAccessibleIconDescription() {
 691             return ImageIcon.this.getDescription();
 692         }
 693 
 694         /**
 695          * Sets the description of the icon.  This is meant to be a brief
 696          * textual description of the object.  For example, it might be
 697          * presented to a blind user to give an indication of the purpose
 698          * of the icon.
 699          *
 700          * @param description the description of the icon
 701          */
 702         public void setAccessibleIconDescription(String description) {
 703             ImageIcon.this.setDescription(description);
 704         }
 705 
 706         /**
 707          * Gets the height of the icon.
 708          *
 709          * @return the height of the icon
 710          */
 711         public int getAccessibleIconHeight() {
 712             return ImageIcon.this.height;
 713         }
 714 
 715         /**
 716          * Gets the width of the icon.
 717          *
 718          * @return the width of the icon
 719          */
 720         public int getAccessibleIconWidth() {
 721             return ImageIcon.this.width;
 722         }
 723 
 724         private void readObject(ObjectInputStream s)
 725             throws ClassNotFoundException, IOException
 726         {
 727             s.defaultReadObject();
 728         }
 729 
 730         private void writeObject(ObjectOutputStream s)
 731             throws IOException
 732         {
 733             s.defaultWriteObject();
 734         }
 735     }  // AccessibleImageIcon
 736 }