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