/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores * CA 94065 USA or visit www.oracle.com if you need additional information or * have any questions. */ package com.sun.lwuit; import com.sun.lwuit.geom.Dimension; import com.sun.lwuit.impl.LWUITImplementation; import java.io.IOException; import java.io.InputStream; import java.util.Hashtable; /** * Abstracts the underlying platform images allowing us to treat them as a uniform * object. * * @author Chen Fishbein */ public class Image { private Object rgbCache; private Object image; int transform; private boolean opaqueTested = false; private boolean opaque; private Object scaleCache; private boolean animated; private long imageTime = -1; private String svgBaseURL; private byte[] svgData; /** * Subclasses may use this and point to an underlying native image which might be * null for a case of an image that doesn't use native drawing * * @param image native image object passed to the LWUIT implementation */ protected Image(Object image) { this.image = image; animated = Display.getInstance().getImplementation().isAnimation(image); } /** Creates a new instance of ImageImpl */ Image(int[] imageArray, int w, int h) { this(Display.getInstance().getImplementation().createImage(imageArray, w, h)); } private Hashtable getScaleCache() { if(scaleCache == null) { Hashtable h = new Hashtable(); scaleCache = Display.getInstance().createSoftWeakRef(h); return h; } Hashtable h = (Hashtable)Display.getInstance().extractHardRef(scaleCache); if(h == null) { h = new Hashtable(); scaleCache = Display.getInstance().createSoftWeakRef(h); } return h; } /** * Returns a cached scaled image * * @param size the size of the cached image * @return cached image */ Image getCachedImage(Dimension size) { Object w = getScaleCache().get(size); return (Image)Display.getInstance().extractHardRef(w); } /** * Returns a cached scaled image * * @param size the size of the cached image * @return cached image */ void cacheImage(Dimension size, Image i) { Object w = Display.getInstance().createSoftWeakRef(i); getScaleCache().put(size, w); } /** * This callback indicates that a component pointing at this image is initialized, this allows * an image to make performance sensitive considerations e.g. an encoded image * might choose to cache itself in RAM. * This method may be invoked multiple times. */ public void lock() { } /** * This callback indicates that a component pointing at this image is now deinitilized * This method may be invoked multiple times. */ public void unlock() { } void setImage(Object image) { this.image = image; } void setOpaque(boolean opaque) { this.opaque = opaque; opaqueTested = true; } /** * Indicates whether the underlying platform supports creating an SVG Image * * @return true if the method create SVG image would return a valid image object * from an SVG Input stream */ public static boolean isSVGSupported() { return Display.getInstance().getImplementation().isSVGSupported(); } /** * Returns a platform specific DOM object that can be manipulated by the user * to change the SVG Image * * @return Platform dependent object, when JSR 226 is supported an SVGSVGElement might * be returned. */ public Object getSVGDocument() { return Display.getInstance().getImplementation().getSVGDocument(image); } /** * Creates an SVG Image from the given byte array data and the base URL, this method * will throw an exception if SVG is unsupported. * * @param baseURL URL which is used to resolve relative references within the SVG file * @param animated indicates if the SVG features an animation * @param data the conten of the SVG file * @return an image object that can be used as any other image object. * @throws IOException if resource lookup fail SVG is unsupported */ public static Image createSVG(String baseURL, boolean animated, byte[] data) throws IOException { Image i = new Image(Display.getInstance().getImplementation().createSVGImage(baseURL, data)); i.animated = animated; i.svgBaseURL = baseURL; i.svgData = data; return i; } /** * Indicates if this image represents an SVG file or a bitmap file * * @return true if this is an SVG file */ public boolean isSVG() { return svgData != null; } /** * Creates a mask from the given image, a mask can be used to apply an arbitrary * alpha channel to any image. A mask is derived from the blue channel (LSB) of * the given image. * The generated mask can be used with the apply mask method. * * @return mask object that can be used with applyMask */ public Object createMask() { int[] rgb = getRGBCached(); byte[] mask = new byte[rgb.length]; for(int iter = 0 ; iter < rgb.length ; iter++) { mask[iter] = (byte)(rgb[iter] & 0xff); } return new IndexedImage(getWidth(), getHeight(), null, mask); } /** * Applies the given alpha mask onto this image and returns the resulting image * see the createMask method for indication on how to convert an image into an alpha * mask. * * @param mask mask object created by the createMask() method. * @param x starting x where to apply the mask * @param y starting y where to apply the mask * @return image masked based on the given object */ public Image applyMask(Object mask, int x, int y) { int[] rgb = getRGB(); byte[] maskData = ((IndexedImage)mask).getImageDataByte(); int mWidth = ((IndexedImage)mask).getWidth(); int mHeight = ((IndexedImage)mask).getHeight(); int imgWidth = getWidth(); int aWidth = imgWidth - x; int aHeight = getHeight() - y; if(aWidth > mWidth) { aWidth = mWidth; } if(aHeight > mHeight) { aHeight = mHeight; } for(int xPos = 0 ; xPos < aWidth ; xPos++) { for(int yPos = 0 ; yPos < aHeight ; yPos++) { int aX = x + xPos; int aY = y + yPos; int imagePos = aX + aY * imgWidth; int maskAlpha = maskData[aX + aY * mWidth] & 0xff; maskAlpha = (maskAlpha << 24) & 0xff000000; rgb[imagePos] = (rgb[imagePos] & 0xffffff) | maskAlpha; } } return createImage(rgb, imgWidth, getHeight()); } /** * Applies the given alpha mask onto this image and returns the resulting image * see the createMask method for indication on how to convert an image into an alpha * mask. * * @param mask mask object created by the createMask() method. * @return image masked based on the given object * @throws IllegalArgumentException if the image size doesn't match the mask size */ public Image applyMask(Object mask) { int[] rgb = getRGB(); byte[] maskData = ((IndexedImage)mask).getImageDataByte(); int mWidth = ((IndexedImage)mask).getWidth(); int mHeight = ((IndexedImage)mask).getHeight(); if(mWidth != getWidth() || mHeight != getHeight()) { throw new IllegalArgumentException("Mask and image sizes don't match"); } for(int iter = 0 ; iter < maskData.length ; iter++) { int maskAlpha = maskData[iter] & 0xff; maskAlpha = (maskAlpha << 24) & 0xff000000; rgb[iter] = (rgb[iter] & 0xffffff) | maskAlpha; } return createImage(rgb, mWidth, mHeight); } /** * Extracts a subimage from the given image allowing us to breakdown a single large image * into multiple smaller images in RAM, this actually creates a standalone version * of the image for use. * * @param x the x offset from the image * @param y the y offset from the image * @param width the width of internal images * @param height the height of internal images * @param processAlpha whether alpha should be processed as well as part of the cutting * @return An array of all the possible images that can be created from the source */ public Image subImage(int x, int y, int width, int height, boolean processAlpha) { // we use the getRGB API rather than the mutable image API to allow translucency to // be maintained in the newly created image int[] arr = new int[width * height]; getRGB(arr, 0, x, y, width, height); Image i = new Image(Display.getInstance().getImplementation().createImage(arr, width, height)); i.opaque = opaque; i.opaqueTested = opaqueTested; return i; } /** * Returns an instance of this image rotated by the given number of degrees. By default 90 degree * angle divisions are supported, anything else is implementation dependent. This method assumes * a square image. Notice that it is inefficient in the current implementation to rotate to * non-square angles, * <p>E.g. rotating an image to 45, 90 and 135 degrees is inefficient. Use rotatate to 45, 90 * and then rotate the 45 to another 90 degrees to achieve the same effect with less memory. * * @param degrees A degree in right angle must be larger than 0 and up to 359 degrees * @return new image instance with the closest possible rotation */ public Image rotate(int degrees) { LWUITImplementation i = Display.getInstance().getImplementation(); if(i.isRotationDrawingSupported()) { if(degrees >= 90) { int newTransform = 0; if(transform != 0) { newTransform = (transform + degrees) % 360; } else { newTransform = degrees % 360; } degrees %= 90; newTransform -= degrees; if(degrees != 0) { Image newImage = new Image(Display.getInstance().getImplementation().rotate(image, degrees)); newImage.transform = newTransform; return newImage; } else { Image newImage = new Image(image); newImage.transform = newTransform; return newImage; } } if(degrees != 0) { return new Image(Display.getInstance().getImplementation().rotate(image, degrees)); } return this; } else { return new Image(Display.getInstance().getImplementation().rotate(image, degrees)); } } /** * Creates an indexed image with byte data this method may return a native indexed image rather than * an instance of the IndexedImage class * * @param width image width * @param height image height * @param palette the color palette to use with the byte data * @param data byte data containing palette offsets to map to ARGB colors */ public static Image createIndexed(int width, int height, int[] palette, byte[] data) { IndexedImage i = new IndexedImage(width, height, palette, data); LWUITImplementation impl = Display.getInstance().getImplementation(); if(impl.isNativeIndexed()) { return new Image(impl.createNativeIndexed(i)); } return i; } /** * Creates a new image instance with the alpha channel of opaque/translucent * pixels within the image using the new alpha value. Transparent (alpha == 0) * pixels remain transparent. All other pixels will have the new alpha value. * * @param alpha New value for the entire alpha channel * @return Translucent/Opaque image based on the alpha value and the pixels of * this image */ public Image modifyAlpha(byte alpha) { int w = getWidth(); int h = getHeight(); int size = w * h; int[] arr = getRGB(); int alphaInt = (((int)alpha) << 24) & 0xff000000; for(int iter = 0 ; iter < size ; iter++) { int currentAlpha = (arr[iter] >> 24) & 0xff; if(currentAlpha != 0) { arr[iter] = (arr[iter] & 0xffffff) | alphaInt; } } Image i = new Image(arr, w, h); i.opaqueTested = true; i.opaque = false; return i; } /** * Creates a new image instance with the alpha channel of opaque * pixels within the image using the new alpha value. Transparent (alpha == 0) * pixels remain transparent. Semi translucent pixels will be multiplied by the * ratio difference and their translucency reduced appropriately. * * @param alpha New value for the entire alpha channel * @return Translucent/Opaque image based on the alpha value and the pixels of * this image */ public Image modifyAlphaWithTranslucency(byte alpha) { int w = getWidth(); int h = getHeight(); int size = w * h; int[] arr = getRGB(); int alphaInt = (((int)alpha) << 24) & 0xff000000; float alphaRatio = (alpha & 0xff); alphaRatio = (alpha & 0xff) / 255.0f; for(int iter = 0 ; iter < size ; iter++) { int currentAlpha = (arr[iter] >> 24) & 0xff; if(currentAlpha != 0) { if(currentAlpha == 0xff) { arr[iter] = (arr[iter] & 0xffffff) | alphaInt; } else { int relative = (int)(currentAlpha * alphaRatio); relative = (relative << 24) & 0xff000000; arr[iter] = (arr[iter] & 0xffffff) | relative; } } } Image i = new Image(arr, w, h); i.opaqueTested = true; i.opaque = false; return i; } /** * Creates a new image instance with the alpha channel of opaque/translucent * pixels within the image using the new alpha value. Transparent (alpha == 0) * pixels remain transparent. All other pixels will have the new alpha value. * * @param alpha New value for the entire alpha channel * @param removeColor pixels matching this color are made transparent (alpha channel ignored) * @return Translucent/Opaque image based on the alpha value and the pixels of * this image */ public Image modifyAlpha(byte alpha, int removeColor) { removeColor = removeColor & 0xffffff; int w = getWidth(); int h = getHeight(); int size = w * h; int[] arr = new int[size]; getRGB(arr, 0, 0, 0, w, h); int alphaInt = (((int)alpha) << 24) & 0xff000000; for(int iter = 0 ; iter < size ; iter++) { if((arr[iter] & 0xff000000) != 0) { arr[iter] = (arr[iter] & 0xffffff) | alphaInt; if(removeColor == (0xffffff & arr[iter])) { arr[iter] = 0; } } } Image i = new Image(arr, w, h); i.opaqueTested = true; i.opaque = false; return i; } /** * creates an image from the given path based on MIDP's createImage(path) * * @param path * @throws java.io.IOException * @return newly created image object */ public static Image createImage(String path) throws IOException { try { return new Image(Display.getInstance().getImplementation().createImage(path)); } catch(OutOfMemoryError err) { // Images have a major bug on many phones where they sometimes throw // an OOM with no reason. A system.gc followed by the same call over // solves the problem. This has something to do with the fact that // there is no Image.dispose method in existance. System.gc();System.gc(); return new Image(Display.getInstance().getImplementation().createImage(path)); } } /** * creates an image from the given native image (e.g. MIDP image object) * * @param nativeImage * @return newly created LWUIT image object */ public static Image createImage(Object nativeImage) { return new Image(nativeImage); } /** * creates an image from an InputStream * * @param stream a given InputStream * @throws java.io.IOException * @return the newly created image */ public static Image createImage(InputStream stream) throws IOException { try { return new Image(Display.getInstance().getImplementation().createImage(stream)); } catch(OutOfMemoryError err) { // Images have a major bug on many phones where they sometimes throw // an OOM with no reason. A system.gc followed by the same call over // solves the problem. This has something to do with the fact that // there is no Image.dispose method in existance. System.gc();System.gc(); return new Image(Display.getInstance().getImplementation().createImage(stream)); } } /** * creates an image from an RGB image * * @param rgb the RGB image array data * @param width the image width * @param height the image height * @return an image from an RGB image */ public static Image createImage(int[] rgb, int width, int height) { try { Image i = new Image(Display.getInstance().getImplementation().createImage(rgb, width, height)); return i; } catch(OutOfMemoryError err) { // Images have a major bug on many phones where they sometimes throw // an OOM with no reason. A system.gc followed by the same call over // solves the problem. This has something to do with the fact that // there is no Image.dispose method in existance. System.gc();System.gc(); return new Image(Display.getInstance().getImplementation().createImage(rgb, width, height)); } } /** * Creates a mutable image that may be manipulated using getGraphics * * @param width the image width * @param height the image height * @return an image in a given width and height dimension */ public static Image createImage(int width, int height) { return createImage(width, height, 0xffffffff); } /** * Returns true if mutable images support alpha transparency * * @return true if mutable images support alpha in their fillColor argument */ public static boolean isAlphaMutableImageSupported() { return Display.getInstance().getImplementation().isAlphaMutableImageSupported(); } /** * Creates a mutable image that may be manipulated using getGraphics * * @param width the image width * @param height the image height * @param fillColor the color with which the image should be initially filled * @return an image in a given width and height dimension */ public static Image createImage(int width, int height, int fillColor) { try { return new Image(Display.getInstance().getImplementation().createMutableImage(width, height, fillColor)); } catch(OutOfMemoryError err) { // Images have a major bug on many phones where they sometimes throw // an OOM with no reason. A system.gc followed by the same call over // solves the problem. This has something to do with the fact that // there is no Image.dispose method in existance. System.gc();System.gc(); return new Image(Display.getInstance().getImplementation().createMutableImage(width, height, fillColor)); } } /** * creates an image from a given byte array data * * @param bytes the array of image data in a supported image format * @param offset the offset of the start of the data in the array * @param len the length of the data in the array * @return the newly created image */ public static Image createImage(byte[] bytes,int offset,int len) { try { Object o = Display.getInstance().getImplementation().createImage(bytes, offset, len); if(o == null) { throw new IllegalArgumentException("create image failed for the given image data of length: " + len); } return new Image(o); } catch(OutOfMemoryError err) { // Images have a major bug on many phones where they sometimes throw // an OOM with no reason. A system.gc followed by the same call over // solves the problem. This has something to do with the fact that // there is no Image.dispose method in existance. System.gc();System.gc(); return new Image(Display.getInstance().getImplementation().createImage(bytes, offset, len)); } } /** * If this is a mutable image a graphics object allowing us to draw on it * is returned. * * @return Graphics object allowing us to manipulate the content of a mutable image */ public Graphics getGraphics() { return new Graphics(Display.getInstance().getImplementation().getNativeGraphics(image)); } /** * Returns the width of the image * * @return the width of the image */ public int getWidth() { if(transform != 0) { if(transform == 90 || transform == 270) { return Display.getInstance().getImplementation().getImageHeight(image); } } return Display.getInstance().getImplementation().getImageWidth(image); } /** * Returns the height of the image * * @return the height of the image */ public int getHeight() { if(transform != 0) { if(transform == 90 || transform == 270) { return Display.getInstance().getImplementation().getImageWidth(image); } } return Display.getInstance().getImplementation().getImageHeight(image); } /** * Callback invoked internally by LWUIT to draw the image/frame onto the display. * Image subclasses can override this method to perform drawing of custom image types. * * @param g the graphics object * @param nativeGraphics the underlying native graphics which might be essential for some image types * @param x the x coordinate * @param y the y coordinate */ protected void drawImage(Graphics g, Object nativeGraphics, int x, int y) { g.drawImage(image, x, y, transform); } /** * Callback invoked internally by LWUIT to draw the image/frame onto the display. * Image subclasses can override this method to perform drawing of custom image types. * * @param g the graphics object * @param nativeGraphics the underlying native graphics which might be essential for some image types * @param x the x coordinate * @param y the y coordinate * @param w the width to occupy * @param h the height to occupy */ protected void drawImage(Graphics g, Object nativeGraphics, int x, int y, int w, int h) { g.drawImageWH(image, x, y, w, h); } /** * Callback invoked internally by LWUIT to draw a portion of the image onto the display. * Image subclasses can override this method to perform drawing of custom image types. * * @param g the graphics object * @param nativeGraphics the underlying native graphics which might be essential for some image types * @param x the x coordinate * @param y the y coordinate * @param imageX location within the image to draw * @param imageY location within the image to draw * @param imageWidth size of the location within the image to draw * @param imageHeight size of the location within the image to draw */ void drawImageArea(Graphics g, Object nativeGraphics, int x, int y, int imageX, int imageY, int imageWidth, int imageHeight) { Display.getInstance().getImplementation().drawImageArea(nativeGraphics, image, x, y, imageX, imageY, imageWidth, imageHeight); } /** * Obtains ARGB pixel data from the specified region of this image and * stores it in the provided array of integers. Each pixel value is * stored in 0xAARRGGBB format, where the high-order byte contains the * alpha channel and the remaining bytes contain color components for red, * green and blue, respectively. The alpha channel specifies the opacity of * the pixel, where a value of 0x00 represents a pixel that is fully * transparent and a value of 0xFF represents a fully opaque pixel. * The rgb information contained within the image, this method ignors * rotation and mirroring in some/most situations and cannot be * used in such cases. * * @param rgbData an array of integers in which the ARGB pixel data is * stored * @param offset the index into the array where the first ARGB value is * stored * @param scanlength the relative offset in the array between * corresponding pixels in consecutive rows of the region * @param x the x-coordinate of the upper left corner of the region * @param y the y-coordinate of the upper left corner of the region * @param width the width of the region * @param height the height of the region */ void getRGB(int[] rgbData, int offset, int x, int y, int width, int height){ Display.getInstance().getImplementation().getRGB(image, rgbData, offset, x, y, width, height); } /** * Extracts data from this image into the given RGBImage * * @param image RGBImage that would receive pixel data * @param destX x location within RGBImage into which the data will * be written * @param destY y location within RGBImage into which the data will * be written * @param x location within the source image * @param y location within the source image * @param width size of the image to extract from the source image * @param height size of the image to extract from the source image */ public void toRGB(RGBImage image, int destX, int destY, int x, int y, int width, int height){ getRGB(image.getRGB(), destX * destY, x, y, width, height); } /** * Returns the content of this image as a newly created ARGB array. * * @return new array instance containing the ARGB data within this image */ public int[] getRGB() { return getRGBImpl(); } /** * Returns the content of this image as a newly created ARGB array or a cached * instance if possible. Note that cached instances may be garbage collected. * * @return array instance containing the ARGB data within this image */ public int[] getRGBCached() { int[] r = getRGBCache(); if(r == null) { r = getRGBImpl(); rgbCache = Display.getInstance().createSoftWeakRef(r); } return r; } int[] getRGBCache() { if(rgbCache != null) { int[] rgb = (int[])Display.getInstance().extractHardRef(rgbCache); return rgb; } return null; } int[] getRGBImpl() { int width = getWidth(); int height = getHeight(); int[] rgbData = new int[width * height]; getRGB(rgbData, 0, 0, 0, width, height); return rgbData; } /** * Scales the image to the given width while updating the height based on the * aspect ratio of the width * * @param width the given new image width * @return the newly created image */ public Image scaledWidth(int width) { float ratio = ((float)width) / ((float)getWidth()); return scaled(width, Math.max(1, (int)(getHeight() * ratio))); } /** * Scales the image to the given height while updating the width based on the * aspect ratio of the height * * @param height the given new image height * @return the newly created image */ public Image scaledHeight(int height) { float ratio = ((float)height) / ((float)getHeight()); return scaled(Math.max(1, (int)(getWidth() * ratio)), height); } /** * Scales the image while maintaining the aspect ratio to the smaller size * image * * @param width the given new image width * @param height the given new image height * @return the newly created image */ public Image scaledSmallerRatio(int width, int height) { float hRatio = ((float)height) / ((float)getHeight()); float wRatio = ((float)width) / ((float)getWidth()); if(hRatio < wRatio) { return scaled((int)(getWidth() * hRatio), (int)(getHeight() * hRatio)); } else { return scaled((int)(getWidth() * wRatio), (int)(getHeight() * wRatio)); } } /** * Returns a scaled version of this image image using the given width and height, * this is a fast algorithm that preserves translucent information. * The method accepts -1 to preserve aspect ratio in the given axis. * * @param width width for the scaling * @param height height of the scaled image * @return new image instance scaled to the given height and width */ public Image scaled(int width, int height) { if(width == getWidth() && height == getHeight()) { return this; } if(width == -1) { return scaledHeight(height); } if(height == -1) { return scaledWidth(width); } Dimension d = new Dimension(width, height); Image i = getCachedImage(d); if(i != null) { return i; } if(svgData != null){ try { i = createSVG(svgBaseURL, animated, svgData); } catch (IOException ex) { i = new Image(this.image); } }else{ i = new Image(this.image); } i.scaleCache = scaleCache; i.scale(width, height); i.transform = this.transform; i.animated = animated; i.svgBaseURL = svgBaseURL; i.svgData = svgData; cacheImage(new Dimension(width, height), i); return i; } /** * Returns the platform specific image implementation, <strong>warning</strong> the * implementation class can change between revisions of LWUIT and platforms. * * @return platform specific native implementation for this image object */ public Object getImage() { return image; } /** * Scale the image to the given width and height, this is a fast algorithm * that preserves translucent information * * @param width width for the scaling * @param height height of the scaled image * * @deprecated scale should return an image rather than modify the image in place * use scaled(int, int) instead */ public void scale(int width, int height) { image = Display.getInstance().getImplementation().scale(image, width, height); }//resize image boolean scaleArray(int srcWidth, int srcHeight, int height, int width, int[] currentArray, int[] destinationArray) { // Horizontal Resize int yRatio = (srcHeight << 16) / height; int xRatio = (srcWidth << 16) / width; int xPos = xRatio / 2; int yPos = yRatio / 2; // if there is more than 16bit color there is no point in using mutable // images since they won't save any memory boolean testOpaque = Display.getInstance().numColors() <= 65536 && (!opaqueTested); boolean currentOpaque = true; for (int y = 0; y < height; y++) { int srcY = yPos >> 16; getRGB(currentArray, 0, 0, srcY, srcWidth, 1); for (int x = 0; x < width; x++) { int srcX = xPos >> 16; int destPixel = x + y * width; if ((destPixel >= 0 && destPixel < destinationArray.length) && (srcX < currentArray.length)) { destinationArray[destPixel] = currentArray[srcX]; // if all the pixels have an opaque alpha channel then the image is opaque currentOpaque = testOpaque && currentOpaque && (currentArray[srcX] & 0xff000000) == 0xff000000; } xPos += xRatio; } yPos += yRatio; xPos = xRatio / 2; } if(testOpaque) { this.opaque = currentOpaque; } return opaque; } /** * Returns true if this is an animated image * * @return true if this image represents an animation */ public boolean isAnimation() { return animated; } /** * @inheritDoc */ public boolean animate() { if(imageTime == -1) { imageTime = System.currentTimeMillis(); } boolean val = Display.getInstance().getImplementation().animateImage(image, imageTime); imageTime = System.currentTimeMillis(); return val; } /** * Indicates whether this image is opaque or not * * @return true if the image is completely opqaque which allows for some heavy optimizations */ public boolean isOpaque() { if(!opaqueTested) { opaque = Display.getInstance().getImplementation().isOpaque(this, image); opaqueTested = true; } return opaque; } }