/*
* 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.codename1.ui;
import com.codename1.ui.geom.Dimension;
import com.codename1.impl.CodenameOneImplementation;
import com.codename1.io.Log;
import com.codename1.io.Util;
import com.codename1.ui.util.ImageIO;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
/**
* <p>Abstracts the underlying platform images allowing us to treat them as a uniform
* object. </p>
*
* @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;
private String imageName;
/**
* 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 Codename One implementation
*/
protected Image(Object image) {
this.image = image;
animated = Display.impl.isAnimation(image);
}
/** Creates a new instance of ImageImpl */
Image(int[] imageArray, int w, int h) {
this(Display.impl.createImage(imageArray, w, h));
}
private HashMap<Dimension, Object> getScaleCache() {
if(scaleCache == null) {
HashMap<Dimension, Object> h = new HashMap<Dimension, Object>();
scaleCache = Display.getInstance().createSoftWeakRef(h);
return h;
}
HashMap<Dimension, Object> h = (HashMap<Dimension, Object>)Display.getInstance().extractHardRef(scaleCache);
if(h == null) {
h = new HashMap<Dimension, Object>();
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);
}
/**
* Async lock is the equivalent of a lock operation, however it uses the given image as
* the hard cache and performs the actual image loading asynchronously. On completion this
* method will invoke repaint on the main form if applicable.
*
* @param internal the image to show while the actual image loads.
*/
public void asyncLock(Image internal) {
}
/**
* 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() {
}
/**
* Returns true if the image is locked
* @return false by default
*/
public boolean isLocked() {
return false;
}
/**
* 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.impl.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.impl.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.impl.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;
}
/**
* <p>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, other channels are ignored.<br>
* The generated mask can be used with the apply mask method.<br>
* The sample below demonstrates the masking of an image based on a circle drawn on a mutable image:</p>
*
* <script src="https://gist.github.com/codenameone/b18c37dfcc7de752e0e6.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/graphics-image-masking.png" alt="Picture after the capture was complete and the resulted image was rounded. The background was set to red so the rounding effect will be more noticeable" />
*
* @return mask object that can be used with applyMask
*/
public Object createMask() {
int[] rgb = getRGBCached();
int rlen = rgb.length;
byte[] mask = new byte[rlen];
for(int iter = 0 ; iter < rlen ; iter++) {
mask[iter] = (byte)(rgb[iter] & 0xff);
}
return new IndexedImage(getWidth(), getHeight(), null, mask);
}
/**
* <p>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.</p>
* The sample below demonstrates the masking of an image based on a circle drawn on a mutable image:</p>
*
* <script src="https://gist.github.com/codenameone/b18c37dfcc7de752e0e6.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/graphics-image-masking.png" alt="Picture after the capture was complete and the resulted image was rounded. The background was set to red so the rounding effect will be more noticeable" />
*
* @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");
}
int mdlen = maskData.length;
for(int iter = 0 ; iter < mdlen ; iter++) {
int maskAlpha = maskData[iter] & 0xff;
maskAlpha = (maskAlpha << 24) & 0xff000000;
rgb[iter] = (rgb[iter] & 0xffffff) | maskAlpha;
}
return createImage(rgb, mWidth, mHeight);
}
/**
* 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. If the image is of a different size it will be scaled to mask size.
*
* @param mask mask object created by the createMask() method.
* @return image masked based on the given object
*/
public Image applyMaskAutoScale(Object mask) {
try {
int mWidth = ((IndexedImage)mask).getWidth();
int mHeight = ((IndexedImage)mask).getHeight();
if(mWidth != getWidth() || mHeight != getHeight()) {
return scaled(mWidth, mHeight).applyMask(mask);
}
return applyMask(mask);
} catch(Throwable t) {
Log.e(t);
}
return this;
}
/**
* 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.impl.createImage(arr, width, height));
i.opaque = opaque;
i.opaqueTested = opaqueTested;
return i;
}
/**
* Creates a mirror image for the given image which is useful for some RTL scenarios. Notice that this
* method isn't the most efficient way to perform this task and is designed for portability over efficiency.
* @return a mirrored image
*/
public Image mirror() {
int width = getWidth();
int height = getHeight();
int[] tmp = getRGB();
int[] arr = new int[width * height];
for(int x = 0 ; x < width ; x++) {
for(int y = 0 ; y < height ; y++) {
arr[x + y * width] = tmp[width - x - 1 + y * width];
}
}
Image i = new Image(Display.impl.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) {
CodenameOneImplementation i = Display.impl;
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.impl.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.impl.rotate(image, degrees));
}
return this;
} else {
return new Image(Display.impl.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
* @deprecated try to avoid using indexed images explicitly
*/
public static Image createIndexed(int width, int height, int[] palette, byte[] data) {
IndexedImage i = new IndexedImage(width, height, palette, data);
CodenameOneImplementation impl = Display.impl;
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.impl.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.impl.createImage(path));
}
}
/**
* creates an image from the given native image (e.g. MIDP image object)
*
* @param nativeImage
* @return newly created Codename One image object
* @deprecated this method is deprecated as a warning! Don't use this method unless you actually
* know what you are doing, if you are invoking this method without understanding the distinction
* between native image and Codename One image then you are using the wrong method.
*/
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.impl.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.impl.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.impl.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.impl.createImage(rgb, width, height));
}
}
/**
* <p>Creates a white opaque mutable image that may be manipulated using {@link #getGraphics()}.<br>
* The sample below shows this method being used to create a screenshot for sharing the image:</p>
*
* <script src="https://gist.github.com/codenameone/6bf5e68b329ae59a25e3.js"></script>
*
* <p>
* The sample below demonstrates the drawing of a mask image to create a round image effect
* </p>
* <script src="https://gist.github.com/codenameone/b18c37dfcc7de752e0e6.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/graphics-image-masking.png" alt="Picture after the capture was complete and the resulted image was rounded. The background was set to red so the rounding effect will be more noticeable" />
*
* @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.impl.isAlphaMutableImageSupported();
}
/**
* <p>Creates a mutable image that may be manipulated using {@link #getGraphics()}.<br>
* The sample below shows this method being used to create a screenshot for sharing the image:</p>
*
* <script src="https://gist.github.com/codenameone/6bf5e68b329ae59a25e3.js"></script>
*
* @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.impl.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.impl.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.impl.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.impl.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() {
Graphics g = new Graphics(Display.impl.getNativeGraphics(image));
return g;
}
/**
* 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.impl.getImageHeight(image);
}
}
return Display.impl.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.impl.getImageWidth(image);
}
}
return Display.impl.getImageHeight(image);
}
/**
* Callback invoked internally by Codename One 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 Codename One 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 Codename One 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.impl.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.impl.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 * image.getWidth(), 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), height);
} else {
return scaled(width, (int)(getHeight() * wRatio));
}
}
/**
* Scales the image while maintaining the aspect ratio to the larger size
* image
*
* @param width the given new image width
* @param height the given new image height
* @return the newly created image
*/
public Image scaledLargerRatio(int width, int height) {
float hRatio = ((float)height) / ((float)getHeight());
float wRatio = ((float)width) / ((float)getWidth());
if(hRatio > wRatio) {
return scaled(Math.round(getWidth() * hRatio), height);
} else {
return scaled(width, Math.round(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) {
return scaledImpl(width, height);
}
/**
* 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
*/
Image scaledImpl(int width, int height) {
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;
}
/**
* Resizes/crops the image so that its center fills the given dimensions. This is similar to {@link com.codename1.ui.plaf.Style#BACKGROUND_IMAGE_SCALED_FILL}
*
* @param width the width to fill
* @param height the height to fill
* @return a new image (or the same image if dimensions happen to match) filling the width/height
*/
public Image fill(int width, int height) {
if(getWidth() == width && getHeight() == height) {
return this;
}
Image nimage = scaledLargerRatio(width, height);
if(nimage.getWidth() > width) {
int diff = nimage.getWidth() - width;
nimage = nimage.subImage(diff / 2, 0, width, height, true);
} else {
if(nimage.getHeight() > height) {
int diff = nimage.getHeight() - height;
nimage = nimage.subImage(0, diff / 2, width, height, true);
}
}
return nimage;
}
/**
* Returns the platform specific image implementation, <strong>warning</strong> the
* implementation class can change between revisions of Codename One 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.impl.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.impl.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.impl.isOpaque(this, image);
opaqueTested = true;
}
return opaque;
}
/**
* The name of the image is set for some images mostly to ease the debugging of Codename One application
* @return the imageName
*/
public String getImageName() {
return imageName;
}
/**
* The name of the image is set for some images mostly to ease the debugging of Codename One application
* @param imageName the imageName to set
*/
public void setImageName(String imageName) {
this.imageName = imageName;
}
/**
* DO NOT CALL THIS METHOD UNLESS YOU KNOW WHAT YOU ARE DOING! Images dispose
* automatically for most cases except for very rare special cases.
* Images on devices usually holds a native memory, some platforms garbage
* collectors might fail to release the native and to fail with out of memory
* errors.
* Use this method to make sure the image will be released from memory, after
* calling this the image will become unusable.
*/
public void dispose(){
if(image != null) {
Display.impl.releaseImage(image);
}
image = null;
}
/**
* Rotates this image by 90 degrees while changing the ratio of the picture
* @param maintainOpacity whether the opacity in the image should be maintained
* @return a new image rotated by 90 degrees
*/
public Image rotate90Degrees(boolean maintainOpacity) {
return Display.impl.rotate90Degrees(this, maintainOpacity);
}
/**
* Rotates the image by 180 degrees
* @param maintainOpacity whether the opacity in the image should be maintained
* @return a new image rotated by 180 degrees
*/
public Image rotate180Degrees(boolean maintainOpacity) {
return Display.impl.rotate180Degrees(this, maintainOpacity);
}
/**
* Rotates the image by 270 degrees while changing the ratio of the picture
* @param maintainOpacity whether the opacity in the image should be maintained
* @return a new image rotated by 270 degrees
*/
public Image rotate270Degrees(boolean maintainOpacity) {
return Display.impl.rotate270Degrees(this, maintainOpacity);
}
/**
* Flips this image on the horizontal axis
* @param maintainOpacity whether the opacity in the image should be maintained
* @return a new image flipped
*/
public Image flipHorizontally(boolean maintainOpacity) {
return Display.impl.flipImageHorizontally(this, maintainOpacity);
}
/**
* Flips this image on the vertical axis
* @param maintainOpacity whether the opacity in the image should be maintained
* @return a new image flipped
*/
public Image flipVertically(boolean maintainOpacity) {
return Display.impl.flipImageVertically(this, maintainOpacity);
}
/**
* New label optimizations don't invoke drawImage and instead just pass the native image directly to
* the underlying renderer. This is problematic for some image types specifically timeline & FontImage
* and this method allows these classes to indicate that they need that legacy behavior of calling drawImage.
*
* @return true if a drawImage call is a required
*/
public boolean requiresDrawImage() {
return false;
}
}