package org.newdawn.slick.opengl; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import org.lwjgl.BufferUtils; import org.lwjgl.LWJGLException; import org.lwjgl.input.Cursor; import org.newdawn.slick.util.Log; import org.newdawn.slick.util.ResourceLoader; import static org.lwjgl.input.Cursor.CURSOR_8_BIT_ALPHA; import static org.lwjgl.input.Cursor.CURSOR_ONE_BIT_TRANSPARENCY; /** * A utility to load cursors (thanks go to Kappa for the animated cursor * loader) * * @author Kevin Glass * @author Kappa-One * @author Martin Karing <nitram@illarion.org> */ public class CursorLoader { /** The single instance of this loader to exist */ private static CursorLoader single = new CursorLoader(); /** * Retrieve the single instance of this loader - convenient huh? * * @return The single instance of the cursor loader */ public static CursorLoader get() { return single; } /** * Create a new cursor loader */ private CursorLoader() { } /** * The transparency threshold. */ private float transparencyThreshold = 0.8f; /** * Get the current transparency threshold value. * * @return the transparency threshold * @see #setTransparencyThreshold(float) */ public float getTransparencyThreshold() { return transparencyThreshold; } /** * Set the threshold value for the conversation between eight and one bit alpha. In case the opacity of a pixel is * greater then the value applied here (1.0 fully opaque, 0.0 fully transparent) the pixel is altered as needed in * case the host system does not support 8bit alpha cursors. * * @param value the threshold value * @throws IllegalArgumentException in case the value is less then 0 or greater then 1 */ public void setTransparencyThreshold(final float value) { if (value < 0.f || value > 1.f) { throw new IllegalArgumentException("Value is outside of valid range."); } transparencyThreshold = value; } /** * Apply the threshold value to the alpha value. This is needed to display cursors with 8bit alpha properly on * systems that support only one bit alpha. * * @param alpha the real alpha value * @return the one bit alpha value */ private byte applyThreshold(final byte alpha) { int value = alpha; if (value < 0) { value = 256 + value; } if (value > 256 * transparencyThreshold) { return (byte) -1; } else { return (byte) 0; } } /** * Get a cursor based on a image reference on the classpath * * @param ref The reference to the image to be loaded * @param x The x-coordinate of the cursor hotspot (left -> right) * @param y The y-coordinate of the cursor hotspot (bottom -> top) * @return The create cursor * @throws IOException Indicates a failure to load the image * @throws LWJGLException Indicates a failure to create the hardware cursor * @throws IllegalArgumentException in case the width or the height is greater then the image width or height or in * case height or width is 0 or less. */ public Cursor getCursor(final String ref, final int x, final int y) throws IOException, LWJGLException { LoadableImageData imageData = null; imageData = ImageDataFactory.getImageDataFor(ref); imageData.configureEdging(false); ByteBuffer buff = imageData.loadImage(ResourceLoader.getResourceAsStream(ref), true, true, null); return getCursor(buff, x, y, imageData.getWidth(), imageData.getHeight()); } /** * Get a cursor based on a set of image data * * @param buf The image data (stored in RGBA) to load the cursor from * @param x The x-coordinate of the cursor hotspot (left -> right) * @param y The y-coordinate of the cursor hotspot (bottom -> top) * @param width The width of the image data provided * @param height The height of the image data provided * @return The create cursor * @throws IOException Indicates a failure to load the image * @throws LWJGLException Indicates a failure to create the hardware cursor */ public Cursor getCursor(final ByteBuffer buf, final int x, final int y, final int width, final int height) throws IOException, LWJGLException { return getCursor(buf, x, y, width, height, width, height); } /** * Get a cursor based on a set of image data * * @param buf The image data (stored in RGBA) to load the cursor from * @param x The x-coordinate of the cursor hotspot (left -> right) * @param y The y-coordinate of the cursor hotspot (bottom -> top) * @param width The width of the image data provided * @param height The height of the image data provided * @param imageWidth The width of the actual image, the pixels outside of this width are considered blank * @param imageHeight The height of the actual image, the pixels outside of this height are considered blank * @return The create cursor * @throws IOException Indicates a failure to load the image * @throws LWJGLException Indicates a failure to create the hardware cursor * @throws IllegalArgumentException in case the width or the height is greater then the image width or height or in * case height or width is 0 or less. */ public Cursor getCursor(ByteBuffer buf, int x, int y, int width, int height, int imageWidth, int imageHeight) throws IOException, LWJGLException { if (height < imageHeight) { throw new IllegalArgumentException("The image height can't be larger then the actual texture size."); } if (width < imageWidth) { throw new IllegalArgumentException("The image width can't be larger then the actual texture size."); } if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) { throw new IllegalArgumentException("Zero is a illegal value for height and width values"); } final int capabilities = Cursor.getCapabilities(); final boolean transparencySupport = (capabilities & CURSOR_ONE_BIT_TRANSPARENCY) != 0; final boolean fullTransparencySupport = (capabilities & CURSOR_8_BIT_ALPHA) != 0; if (!transparencySupport) { Log.info("Your system does not support cursors with transparency. The mouse cursor may look messy."); } for (int i=0;i<buf.limit();i+=4) { byte red = buf.get(i); byte green = buf.get(i+1); byte blue = buf.get(i+2); byte alpha = buf.get(i+3); buf.put(i+2, red); buf.put(i+1, green); buf.put(i, blue); if (fullTransparencySupport) { buf.put(i+3, alpha); } else if (transparencySupport) { buf.put(i+3, applyThreshold(alpha)); } else { buf.put(i+3, (byte) -1); } } final int maxSize = Cursor.getMaxCursorSize(); final int minSize = Cursor.getMinCursorSize(); int cursorTextureHeight = height; int cursorTextureWidth = width; int ySpot = imageHeight - y - 1; int xSpot = x; if (ySpot < 0) { ySpot = 0; } if ((cursorTextureHeight > maxSize) || (cursorTextureWidth > maxSize)) { final int targetHeight = Math.min(maxSize, cursorTextureHeight); final int targetWidth = Math.min(maxSize, cursorTextureWidth); ySpot -= imageHeight - targetHeight; xSpot -= imageWidth - targetWidth; final byte pixelBuffer[] = new byte[4]; ByteBuffer tempBuffer = BufferUtils.createByteBuffer(targetHeight * targetWidth * 4); BufferUtils.zeroBuffer(tempBuffer); for (int tempX = 0; tempX < targetHeight; tempX++) { for (int tempY = 0; tempY < targetWidth; tempY++) { buf.position((tempX + tempY * cursorTextureWidth) * 4); buf.get(pixelBuffer); tempBuffer.position((tempX + tempY * targetWidth) * 4); tempBuffer.put(pixelBuffer); } } cursorTextureHeight = targetHeight; cursorTextureWidth = targetWidth; buf = tempBuffer; } if ((cursorTextureHeight < minSize) || (cursorTextureWidth < minSize)) { final int targetHeight = Math.max(minSize, cursorTextureHeight); final int targetWidth = Math.max(minSize, cursorTextureWidth); final byte pixelBuffer[] = new byte[4]; ByteBuffer tempBuffer = BufferUtils.createByteBuffer(targetHeight * targetWidth * 4); BufferUtils.zeroBuffer(tempBuffer); for (int tempX = 0; tempX < imageWidth; tempX++) { for (int tempY = 0; tempY < imageHeight; tempY++) { buf.position((tempX + tempY * cursorTextureWidth) * 4); buf.get(pixelBuffer); tempBuffer.position((tempX + tempY * targetWidth) * 4); tempBuffer.put(pixelBuffer); } } cursorTextureHeight = targetHeight; cursorTextureWidth = targetWidth; buf = tempBuffer; } try { buf.position(0); return new Cursor(cursorTextureWidth, cursorTextureHeight, xSpot, ySpot, 1, buf.asIntBuffer(), null); } catch (Throwable e) { Log.info("Chances are you cursor is too small for this platform"); throw new LWJGLException(e); } } /** * Get a cursor based on a set of image data * * @param imageData The data from which the cursor can read it's contents * @param x The x-coordinate of the cursor hotspot (left -> right) * @param y The y-coordinate of the cursor hotspot (bottom -> top) * @return The create cursor * @throws IOException Indicates a failure to load the image * @throws LWJGLException Indicates a failure to create the hardware cursor * @throws IllegalArgumentException in case the width or the height is greater then the image width or height or in * case height or width is 0 or less. */ public Cursor getCursor(ImageData imageData,int x,int y) throws IOException, LWJGLException { return getCursor(imageData.getImageBufferData(), x, y, imageData.getTexWidth(), imageData.getTexHeight(), imageData.getWidth(), imageData.getHeight()); } /** * Get a cursor based on a image reference on the classpath. The image * is assumed to be a set/strip of cursor animation frames running from top to * bottom. * * @param ref The reference to the image to be loaded * @param x The x-coordinate of the cursor hotspot (left -> right) * @param y The y-coordinate of the cursor hotspot (bottom -> top) * @param width The x width of the cursor * @param height The y height of the cursor * @param cursorDelays image delays between changing frames in animation * * @return The created cursor * @throws IOException Indicates a failure to load the image * @throws LWJGLException Indicates a failure to create the hardware cursor * @throws IllegalArgumentException in case the width or the height is greater then the image width or height or in * case height or width is 0 or less. */ public Cursor getAnimatedCursor(String ref,int x,int y, int width, int height, int[] cursorDelays) throws IOException, LWJGLException { IntBuffer cursorDelaysBuffer = ByteBuffer.allocateDirect(cursorDelays.length*4).order(ByteOrder.nativeOrder()).asIntBuffer(); for (int i=0;i<cursorDelays.length;i++) { cursorDelaysBuffer.put(cursorDelays[i]); } cursorDelaysBuffer.flip(); LoadableImageData imageData = new TGAImageData(); ByteBuffer buf = imageData.loadImage(ResourceLoader.getResourceAsStream(ref), false, null); return new Cursor(width, height, x, y, cursorDelays.length, buf.asIntBuffer(), cursorDelaysBuffer); } }