All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.util.TextureAtlasElement Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.util;

import com.jogamp.opengl.util.texture.TextureCoords;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.render.DrawContext;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.*;
import java.beans.*;
import java.net.URL;

/**
 * Represents a texture defined by a sub-image within a {@link TextureAtlas}.
 * 

* TextureAtlasElement performs lazy retrieval and loading of its image source into its texture atlas. This loads the * image source and adds it to the atlas only when the {@link #load(gov.nasa.worldwind.render.DrawContext)} method is * called. If the image source is a {@link BufferedImage} it is added to the atlas immediately when load is * called. If the image source is a local file or a remote stream (URL), retrieval and loading is performed on a * separate thread from the EDT. * * @author dcollins * @version $Id: TextureAtlasElement.java 1171 2013-02-11 21:45:02Z dcollins $ */ public class TextureAtlasElement implements Disposable { /** Indicates the texture atlas this element belongs to. Specified during construction. */ protected TextureAtlas atlas; /** Indicates the original image source associated with this element. Specified during construction. */ protected Object imageSource; /** * The BufferedImage created as the image source is read. This intermediate field is necessary because the image * source is read on a non-EDT thread, but changes to the texture atlas must be performed on the EDT. This is set to * null once the image is loaded into the texture atlas. This field is volatile in order * to synchronize atomic access among threads. This field is not used if the image source is * BufferedImage. */ protected volatile BufferedImage image; /** * Indicates that image initialization failed. This element should not be used if true. Initially * false. */ protected boolean imageInitializationFailed; /** * The object to notify when the image is eventually loaded in memory. This is either the current layer or the layer * list at the time the image source is requested. The latter is used when the image source is requested during * ordered rendering mode, in which case the current layer is null. This set to null once * the image is loaded into the texture atlas. */ protected PropertyChangeListener listener; /** * Creates a new texture atlas element with the specified atlas and image source. * * @param atlas the texture atlas this element belongs to. * @param imageSource a general image source. The source type may be one of the following:

  • a {@link * URL}
  • an {@link java.io.InputStream}
  • a {@link java.io.File}
  • a {@link * String} containing a valid URL description or a file or resource name available on the * classpath.
* * @throws IllegalArgumentException if either the atlas or the image source is null. */ public TextureAtlasElement(TextureAtlas atlas, Object imageSource) { if (atlas == null) { String msg = Logging.getMessage("nullValue.AtlasIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (WWUtil.isEmpty(imageSource)) { String msg = Logging.getMessage("nullValue.ImageSource"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.atlas = atlas; this.imageSource = imageSource; } /** * Indicates the texture atlas this element belongs to. * * @return this element's texture atlas. */ public TextureAtlas getTextureAtlas() { return this.atlas; } /** * Indicates the original image source associated with this element. * * @return this element's image source. */ public Object getImageSource() { return this.imageSource; } /** * Indicates whether this element's image source is a BufferedImage. * * @return true if this element's image source is a BufferedImage, and false otherwise. */ protected boolean isBufferedImageSource() { return this.getImageSource() instanceof BufferedImage; } /** * Indicates the image created as the image source is read. * * @return this element's image. * * @see #setImage(java.awt.image.BufferedImage) */ protected BufferedImage getImage() { return this.image; } /** * Specifies the image created as the image source is read. This intermediate field is necessary because the image * source is read on a non-EDT thread, but changes to the texture atlas must be performed on the EDT. This field is * volatile in order to synchronize atomic access among threads. * * @param image this element's image. */ protected void setImage(BufferedImage image) { this.image = image; } /** * Returns the image dimensions associated with this texture atlas element. Always call load before * calling this method to ensure that the element is loaded into its texture atlas. * * @return the image dimensions associated with this texture atlas element, or null if this texture * atlas element has not yet loaded or has failed to load. * * @see #load(gov.nasa.worldwind.render.DrawContext) */ public Dimension getSize() { return this.getTextureAtlas().getSize(this.getImageSource()); } /** * Returns the OpenGL texture coordinates associated this texture atlas element. Always call load * before calling this method to ensure that the element is loaded into its texture atlas. *

* The returned texture coordinates can change any time an element is added or removed from this element's texture * atlas, and therefore should not be cached unless the caller has explicit knowledge of when this element's texture * atlas has changed. * * @return the OpenGL texture coordinates corresponding this texture atlas element, or null if this * texture atlas element has not yet loaded or has failed to load. * * @see #load(gov.nasa.worldwind.render.DrawContext) */ public TextureCoords getTexCoords() { return this.getTextureAtlas().getTexCoords(this.getImageSource()); } /** * Indicates whether this element's image failed to load. This element should not be used if true. * * @return true if this element's image failed to load, and false otherwise. */ public boolean isImageInitializationFailed() { return this.imageInitializationFailed; } /** * Loads this element's image and adds it to the texture atlas. If the image is not yet loaded this initiates image * source retrieval in a separate thread. This does nothing if the texture atlas already contains this element, or * if this element's image failed to load in an earlier attempt. * * @param dc the current draw context. Used to generate a repaint event when the image source retrieval completes. * * @return true if this element's image is successfully loaded and added to the texture atlas, * otherwise false. */ public boolean load(DrawContext dc) { if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (this.isImageInitializationFailed()) return false; // The atlas already contains an entry for this element then just return true. if (this.getTextureAtlas().contains(this.getImageSource())) return true; // The atlas does not contain an entry for this element. Issue a request for this element's image if it does not // exist, or load it into the atlas if it does. In this case we return true if this element was successfully // loaded into the atlas, and false otherwise. return this.requestImage(dc); } /** * Removes this element's image from its texture atlas and disposes of this element's image resource. This does * nothing if this element's image has not yet been loaded. */ public void dispose() { if (this.getTextureAtlas().contains(this.getImageSource())) this.getTextureAtlas().remove(this.getImageSource()); this.setImage(null); } /** * Indicates whether another texture atlas element is equivalent to this one. This tests equality using the image * source of each element. * * @param o the object to test. * * @return true if the specified object is a TextureAtlasElement and its image source is equivalent to * this element's image source, otherwise false. */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || this.getClass() != o.getClass()) return false; TextureAtlasElement that = (TextureAtlasElement) o; return this.imageSource != null ? this.imageSource.equals(that.imageSource) : that.imageSource == null; } /** Returns the hash code for this texture atlas element's image source. */ @Override public int hashCode() { return this.imageSource != null ? this.imageSource.hashCode() : 0; } /** Returns the string representation of this texture atlas element's image source. */ @Override public String toString() { return this.imageSource != null ? this.imageSource.toString() : null; } /** * Requests that this element's image source be loaded into its texture atlas. If the image source is a * BufferedImage, this immediately loads it into the texture atlas and returns true. Otherwise, this * initiates the retrieval of this element's image source in a separate thread and returns false. Once * the image source is retrieved, a subsequent invocation of this method loads it into the texture atlas and returns * true. * * @param dc the current draw context. Used to generate a repaint event when the image source retrieval completes. * * @return true if this element's image is loaded into the texture atlas, and false * otherwise. */ protected boolean requestImage(DrawContext dc) { // If the image source is already a buffered image, assign it to this element's image and let the subsequent // logic in this method take care of adding it to the atlas. if (this.isBufferedImageSource()) this.setImage((BufferedImage) this.getImageSource()); if (this.getImage() != null && !this.getTextureAtlas().contains(this.getImageSource())) return this.addAtlasImage(); if (WorldWind.getTaskService().isFull()) return false; Runnable task = this.createRequestTask(); if (WorldWind.getTaskService().contains(task)) return false; // Use either the current layer or the layer list as the listener to notify when the request completes. The // latter is used when the image source is requested during ordered rendering mode, and the current layer is // null. this.listener = dc.getCurrentLayer() != null ? dc.getCurrentLayer() : dc.getLayers(); WorldWind.getTaskService().addTask(task); return false; } /** * Adds this element's image source into its texture atlas. This throws an exception if this element's image source * is not loaded and stored in the image property. * * @return true if this element's image is successfully added to the texture atlas, and * false otherwise. */ protected boolean addAtlasImage() { if (this.getImage() == null) { String msg = Logging.getMessage("nullValue.ImageIsNull"); Logging.logger().severe(msg); throw new IllegalStateException(msg); } try { // Place this element's image in the atlas, then release our reference to the image. this.getTextureAtlas().add(this.getImageSource(), this.getImage()); this.setImage(null); } catch (Exception e) { String msg = Logging.getMessage("TextureAtlas.ExceptionAddingImage", this.getImageSource().toString()); Logging.logger().log(java.util.logging.Level.SEVERE, msg, e); this.imageInitializationFailed = true; return false; } return true; } /** * Returns an object that implements the Runnable interface, and who's run method retrieves and loads * this element's image source. * * @return a new request task that retrieves and loads this element's image source. */ protected Runnable createRequestTask() { return new RequestTask(this); } /** * Loads this element's image source into its image property. If the image source is a remote resource, * this initiates a request for it and returns null. * * @return true if the image source has been loaded successfully, and false otherwise. */ protected boolean loadImage() { URL fileUrl = WorldWind.getDataFileStore().requestFile(this.getImageSource().toString()); if (fileUrl != null) { BufferedImage image = this.readImage(fileUrl); if (image != null) this.setImage(image); } return this.getImage() != null; } /** * Reads and returns the specified image URL as a BufferedImage. * * @param fileUrl the image URL to read. * * @return the image URL as a BufferedImage, or null if the image could not be read. */ protected BufferedImage readImage(URL fileUrl) { try { return ImageIO.read(fileUrl); } catch (Exception e) { String msg = Logging.getMessage("generic.ExceptionAttemptingToReadImageFile", this.getImageSource().toString()); Logging.logger().log(java.util.logging.Level.SEVERE, msg, e); this.imageInitializationFailed = true; return null; } } /** * Notifies this texture atlas element's listener that image loading has completed. This does nothing if this * texture atlas element has no listener. */ protected void notifyImageLoaded() { if (this.listener != null) { this.listener.propertyChange(new PropertyChangeEvent(this, AVKey.IMAGE, null, this)); this.listener = null; // Forget the listener to avoid dangling references. } } /** * RequestTask is an implementation of the Runnable interface who's run method retrieves and loads this * element's image source. */ protected static class RequestTask implements Runnable { /** The texture atlas element associated with this request task. Specified during construction. */ protected TextureAtlasElement elem; /** * Constructs a new request task with the specified texture atlas element, but otherwise does nothing. Calling * the new request tasks run method causes this to retrieve and load the specified element's image * source. * * @param elem the texture atlas element who's image source is retrieved and loaded. * * @throws IllegalArgumentException if the element is null. */ protected RequestTask(TextureAtlasElement elem) { if (elem == null) { String message = Logging.getMessage("nullValue.ElementIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.elem = elem; } /** * Retrieves and loads the image source from this request task's texture atlas element, and notifies the element * when the load completes. This does nothing if the current thread has been interrupted. */ public void run() { if (Thread.currentThread().isInterrupted()) return; // The task was cancelled because it's a duplicate or for some other reason. if (this.elem.loadImage()) this.elem.notifyImageLoaded(); } /** * Indicates whether another request task is equivalent to this one. This tests equality using the texture atlas * element source of each task. * * @param o the object to test. * * @return true if the specified object is a RequestTask, and its texture atlas element is * equivalent to this task's texture atlas element. */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || this.getClass() != o.getClass()) return false; RequestTask that = (RequestTask) o; return this.elem.equals(that.elem); } /** Returns the hash code for this request task's texture atlas element. */ @Override public int hashCode() { return this.elem.hashCode(); } /** Returns the string representation of this request task's texture atlas element. */ @Override public String toString() { return this.elem.toString(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy