modules/midp-lcdui/src/main/java/javax/microedition/lcdui/Image.java
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
// Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------
package javax.microedition.lcdui;
import cc.squirreljme.jvm.mle.constants.UIPixelFormat;
import cc.squirreljme.runtime.cldc.annotation.Api;
import cc.squirreljme.runtime.cldc.debug.Debugging;
import cc.squirreljme.runtime.lcdui.image.AccessibleImage;
import cc.squirreljme.runtime.lcdui.image.ImageReaderDispatcher;
import cc.squirreljme.runtime.lcdui.image.MIDPImageLoadHandler;
import cc.squirreljme.runtime.lcdui.mle.PencilGraphics;
import cc.squirreljme.runtime.midlet.ActiveMidlet;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Api
public class Image
extends AccessibleImage
{
/** The RGB image data. */
private final int[] _data;
/** Offset into the array. */
private final int _offset;
/** Image width. */
private final int _width;
/** Image height. */
private final int _height;
/** Is this image mutable? */
private final boolean _mutable;
/** Does this have an alpha channel? */
private final boolean _alpha;
Image()
{
throw Debugging.todo();
}
/**
* Initializes the image with the given settings.
*
* @param __data The image data, this is used directly.
* @param __w The image width.
* @param __h The image height.
* @param __mut If this image is mutable
* @param __alpha If this image has an alpha channel.
* @throws NullPointerException On null arguments.
* @since 2017/02/10
*/
Image(int[] __data, int __w, int __h, boolean __mut, boolean __alpha)
throws NullPointerException
{
this(__data, 0, __data.length, __w, __h, __mut, __alpha);
}
/**
* Initializes the image with the given settings.
*
* @param __data The image data, this is used directly.
* @param __o The offset into the buffer.
* @param __l The length of the buffer.
* @param __w The image width.
* @param __h The image height.
* @param __mut If this image is mutable
* @param __alpha If this image has an alpha channel.
* @throws IndexOutOfBoundsException If the image buffer is out of bounds.
* @throws NullPointerException On null arguments.
* @since 2022/06/28
*/
Image(int[] __data, int __o, int __l, int __w, int __h, boolean __mut,
boolean __alpha)
throws IndexOutOfBoundsException, NullPointerException
{
// Check
if (__data == null)
throw new NullPointerException("NARG");
if (__o < 0 || __l < 0 || (__o + __l) > __data.length ||
(__w * __h) > __l)
throw new IndexOutOfBoundsException("IOOB");
// Set
this._data = __data;
this._offset = __o;
this._width = __w;
this._height = __h;
this._mutable = __mut && !this.isAnimated() && !this.isScalable();
this._alpha = __alpha;
// If no alpha, set upper channel to full opaqueness
if (!__alpha)
for (int i = 0, n = __data.length; i < n; i++)
__data[i] |= 0xFF_000000;
}
@Api
public final void getARGB16(short[] __data, int __off, int __scanlen,
int __x, int __y, int __w, int __h)
{
throw Debugging.todo();
}
/**
* This obtains the graphics interface which is used to draw on top of
* a mutable image.
*
* It defaults to:
* The clipping region covers the entire image.
* The color is a fully opaque black.
* The blending mode is {@link Graphics#SRC_OVER}.
* The stroke is {@link Graphics#SOLID}.
* The font is the default font.
* The coordinate origin is the top-left corner.
*
* The blending mode may only be changed to {@link Graphics#SRC} if the
* image has an alpha channel.
*
* @return A new graphics drawer.
* @throws IllegalStateException If the image is not mutable.
* @since 2017/02/10
*/
@Api
public final Graphics getGraphics()
throws IllegalStateException
{
/* {@squirreljme.error EB28 Cannot get mutable graphic operations for
an immutable image.} */
if (!this.isMutable())
throw new IllegalStateException("EB28");
// Create hardware accelerated graphics where possible
return PencilGraphics.hardwareGraphics(
(this._alpha ? UIPixelFormat.INT_RGBA8888 :
UIPixelFormat.INT_RGB888),
this._width, this._height,
this._data, this._offset, null,
0, 0, this._width, this._height);
}
/**
* Returns the image height.
*
* @return The height of the image.
* @since 2017/02/10
*/
@Api
public final int getHeight()
{
return this._height;
}
/**
* Copies RGB image data from the source image.
*
* The source image data must be within the bounds of the image.
*
* All written pixels will have an alpha value regardless if the image has
* an alpha channel or not. In the case the image has no alpha channel then
* all read pixels will have a value of {@code 0xFF} as their alpha
* channel.
*
* @param __b The destination array.
* @param __o The offset into the array.
* @param __sl The scanline length of the destination array, this value may
* be negative to indicate that pixels are placed in reverse order.
* @param __x The source X position.
* @param __y The source Y position.
* @param __w The width to copy, if this is zero nothing is copied.
* @param __h The height to copy, if this is zero nothing is copied.
* @throws ArrayIndexOutOfBoundsException If writing to the destination
* buffer would result in a write that exceeds the bounds of the array.
* @throws IllegalArgumentException If the source X or Y position is
* negative; If the source region exceeds the image bounds; If the absolute
* value of the scanline length is lower than the width.
* @throws NullPointerException On null arguments.
* @since 2017/02/11
*/
@Api
public final void getRGB(int[] __b, int __o, int __sl, int __x, int __y,
int __w, int __h)
throws ArrayIndexOutOfBoundsException, IllegalArgumentException,
NullPointerException
{
// Do nothing
if (__w <= 0 || __h <= 0)
return;
// Scalable images must be rasterized first
if (this.isScalable())
throw Debugging.todo();
// Check
if (__b == null)
throw new NullPointerException("NARG");
/* {@squirreljme.error EB29 The source coordinates are negative.} */
if (__x < 0 || __y < 0)
throw new IllegalArgumentException("EB29");
/* {@squirreljme.error EB2a The absolute value of the scanline length
exceeds the read width.} */
int scanLen = Math.abs(__sl);
if (scanLen < __w)
throw new IllegalArgumentException("EB2a");
/* {@squirreljme.error EB2b Reading of RGB data would exceed the bounds
out the output array.} */
int areaPix = __w * __h;
int areaScan = __sl * __h;
if (__o < 0 || (__o + areaScan) > __b.length || (__o + areaScan) < 0)
throw new ArrayIndexOutOfBoundsException("EB2b");
/* {@squirreljme.error EB2c The area to read exceeds the bounds of the
image.} */
int ex = __x + __w;
int ey = __y + __h;
int iw = this._width;
int ih = this._height;
if (ex > iw || ey > ih)
throw new IllegalArgumentException(String.format(
"EB2c (%d, %d) <> (%d, %d)", ex, ey, iw, ih));
// If the alpha channel is not used then all RGB data is forced to
// be fully opaque
boolean alpha = this._alpha;
int opaqueMask = (this._alpha ? 0 : 0xFF_000000);
// Read image data
int[] data = this._data;
int dataOffset = this._offset;
for (int sy = __y, wy = 0; sy < ey; sy++, wy++)
{
// Calculate offsets
int srcoff = dataOffset + (iw * sy) + __x;
int dstoff = __o + (wy * __sl);
// Copy data, arraycopy is much faster of an operation!
/*for (int sx = __x; sx < ex; sx++)
__b[dstoff++] = data[srcoff++] | opqmask;*/
System.arraycopy(data, srcoff,
__b, dstoff, ex - __x);
// If not using alpha, then force all pixels to have the given
// alpha mask
if (!alpha)
for (int sx = __x; sx < ex; sx++)
__b[dstoff++] |= opaqueMask;
}
}
@Api
public final void getRGB16(short[] __data, int __off, int __scanlen,
int __x, int __y, int __w, int __h)
{
throw Debugging.todo();
}
/**
* Returns the image width.
*
* @return The width of the image.
* @since 2017/02/10
*/
@Api
public final int getWidth()
{
return this._width;
}
/**
* Returns {@code true} if this image has an alpha channel.
*
* @return {@code true} if this image has an alpha channel.
* @since 2017/02/10
*/
@Api
public final boolean hasAlpha()
{
return this._alpha;
}
/**
* Returns {@code true} if this image is animated.
*
* @return {@code true} if this image is animated.
* @since 2017/02/10
*/
@Api
public final boolean isAnimated()
{
return (this instanceof AnimatedImage);
}
/**
* Returns {@code true} if this image is mutable.
*
* @return {@code true} if this image is mutable.
* @since 2017/02/10
*/
@Api
public final boolean isMutable()
{
return this._mutable && !this.isAnimated() && !this.isScalable();
}
/**
* Returns {@code true} if this image is scalable.
*
* @return {@code true} if this image is scalable.
* @since 2017/02/10
*/
@Api
public final boolean isScalable()
{
return (this instanceof ScalableImage);
}
/**
* {@inheritDoc}
* @since 2022/01/26
*/
@Override
public final int squirreljmeDirectOffset()
{
if (this.squirreljmeIsDirect())
return this._offset;
return Integer.MIN_VALUE;
}
/**
* {@inheritDoc}
* @since 2022/01/26
*/
@Override
public final int[] squirreljmeDirectRGBInt()
{
if (this.squirreljmeIsDirect())
return this._data;
return null;
}
/**
* {@inheritDoc}
* @since 2022/01/26
*/
@Override
public final int squirreljmeDirectScanLen()
{
if (this.squirreljmeIsDirect())
return this._width;
return Integer.MIN_VALUE;
}
/**
* {@inheritDoc}
* @since 2022/01/26
*/
@Override
public final boolean squirreljmeIsDirect()
{
return !(this.isScalable() || this.isAnimated());
}
/**
* Loads the specified image from the specified byte array.
*
* @param __b The array to read from.
* @param __o The offset into the array.
* @param __l The length of the array.
* @return The loaded image.
* @throws ArrayIndexOutOfBoundsException If the offset and/or length are
* negative or exceed the array bounds.
* @throws IllegalArgumentException If the image could not be read.
* @throws NullPointerException On null arguments.
* @since 2017/02/28
*/
@Api
public static Image createImage(byte[] __b, int __o, int __l)
throws ArrayIndexOutOfBoundsException, IllegalArgumentException,
NullPointerException
{
// Check
if (__b == null)
throw new NullPointerException("NARG");
// Could fail
try
{
return Image.createImage(new ByteArrayInputStream(__b, __o, __l));
}
/* {@squirreljme.error EB2d Could not load the image data.} */
catch (IOException e)
{
throw new IllegalArgumentException("EB2d", e);
}
}
/**
* Same as {@code createImage(__w, __h, false, 0x00FFFFFF)}.
*
* @param __w The width of the image.
* @param __h The height of the image.
* @return The created image.
* @throws IllegalArgumentException If the requested image size has a zero
* or negative dimension.
* @since 2017/02/11
*/
@Api
public static Image createImage(int __w, int __h)
throws IllegalArgumentException
{
return Image.createImage(__w, __h, false, 0x00FFFFFF);
}
/**
* Creates a mutable image that may or may not have an alpha channel.
*
* @param __w The width of the image.
* @param __h The height of the image.
* @param __alpha Whether it has an alpha channel.
* @param __c The initial color to fill.
* @return The created image.
* @throws IllegalArgumentException If the requested image size has a zero
* or negative dimension.
* @since 2017/02/11
*/
@Api
public static Image createImage(int __w, int __h, boolean __alpha, int __c)
throws IllegalArgumentException
{
/* {@squirreljme.error EB2e Zero or negative image size requested.} */
if (__w <= 0 || __h <= 0)
throw new IllegalArgumentException("EB2e");
// Setup buffer
int area = __w * __h;
int[] data = new int[area];
// Fill with color, unless we asked for zero then nothing needs to
// actually be done
if (__c != 0)
for (int i = 0; i < area; i++)
data[i] = __c;
// Create
return new Image(data, __w, __h, true, __alpha);
}
/**
* Loads the image from the specified input stream.
*
* @param __is The stream to read image data from.
* @return The parsed image.
* @throws IOException If the image could not be read.
* @throws NullPointerException On null arguments.
* @since 2017/02/28
*/
@Api
public static Image createImage(InputStream __is)
throws IOException, NullPointerException
{
// Check
if (__is == null)
throw new NullPointerException("NARG");
// Parse the image
__ImageFactory__ factory = new __ImageFactory__();
return new ImageReaderDispatcher<Image>(
new MIDPImageLoadHandler(factory)).parse(__is);
}
/**
* This loads the specified resource as an image.
*
* @param __s The string to load the resource for.
* @return The created image.
* @throws IOException If the resource does not exist or the image data
* could not be decoded.
* @throws NullPointerException On null arguments.
* @since 2017/02/28
*/
@Api
public static Image createImage(String __s)
throws IOException, NullPointerException
{
// Check
if (__s == null)
throw new NullPointerException("NARG");
// Try loading it
try (InputStream is = ActiveMidlet.get().getClass().
getResourceAsStream(__s))
{
/* {@squirreljme.error EB2f The specified resource does not
exist. (The resource name)} */
if (is == null)
throw new IOException(String.format("EB2f %s", __s));
return Image.createImage(is);
}
}
/**
* Creates an immutable image which is an exact copy of the other image. If
* the other image is scalable it will be rasterized with whatever
* dimensions the other image has. If the specified image is immutable then
* it will be returned.
*
* @param __i The image to copy.
* @return The resulting copy.
* @throws NullPointerException On null arguments.
* @since 2019/06/24
*/
@Api
public static Image createImage(Image __i)
throws NullPointerException
{
if (__i == null)
throw new NullPointerException("NARG");
// Needs to be rendered
if (__i instanceof ScalableImage)
throw Debugging.todo();
// Same otherwise
else if (!__i._mutable)
return __i;
// Copy and make this immutable
return new Image(__i._data.clone(), __i._width, __i._height,
false, __i._alpha);
}
@Api
public static Image createImage(Image __i, int __x, int __y,
int __w, int __h, int __trans)
{
return Image.createImage(__i, __x, __y, __w, __h, __trans,
__i.getWidth(), __i.getHeight());
}
@Api
public static Image createImage(Image __i, int __x, int __y, int __w,
int __h, int __trans, int __iw, int __ih)
{
throw Debugging.todo();
}
/**
* Creates an immutable image from the specified ARGB pixel array.
*
* @param __rgb The ARGB or RGB image data to use as the image data.
* @param __w The width of the image.
* @param __h The height of the image.
* @param __alpha If {@code true} then the alpha is processed, otherwise
* all pixels are treated as fully opaque.
* @return The created image.
* @throws IllegalArgumentException If the width or height is negative.
* @throws IndexOutOfBoundsException If the input array is too small.
* @throws NullPointerException On null arguments.
* @since 2017/02/10
*/
@Api
public static Image createRGBImage(int[] __rgb, int __w, int __h, boolean
__alpha)
throws IllegalArgumentException, IndexOutOfBoundsException,
NullPointerException
{
// Check
if (__rgb == null)
throw new NullPointerException("NARG");
/* {@squirreljme.error EB2g Invalid image size. (The width;
The height)} */
int area = __w * __h;
if (__w <= 0 || __h <= 0 || area <= 0)
throw new IllegalArgumentException(String.format("EB2g %d %d",
__w, __h));
/* {@squirreljme.error EB2h The input integer buffer is shorter than
the specified image area.} */
int rgbLen = __rgb.length;
if (rgbLen < area)
throw new IndexOutOfBoundsException(String.format(
"EB2h %d < %d", rgbLen, area));
// Use a cloned copy of the pixel data?
if (rgbLen == area)
__rgb = __rgb.clone();
// Otherwise initialize a new one
else
{
int[] copy = new int[area];
System.arraycopy(__rgb, 0,
copy, 0, area);
__rgb = copy;
}
// If there is no alpha channel, force all of it to be opaque
if (!__alpha)
for (int i = 0; i < area; i++)
__rgb[i] |= 0xFF000000;
// Setup image
return new Image(__rgb, __w, __h, false, __alpha);
}
}