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