Commits

Martin Karing committed eda01bb

Improved cursor loader.

The new cursor loader is now able to fix cursor images so the host system is able to work with them properly. It performs changes of the texture size and is able to fix alpha values. It works with cursors that are too small for the host system and with cursor too large. How ever the support for larger cursors is limited to avoid crashes. The cursor will likely look crappy.

Comments (0)

Files changed (1)

trunk/Slick/src/org/newdawn/slick/opengl/CursorLoader.java

 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 Kappa-One
+ * @author Martin Karing <nitram@illarion.org>
  */
 public class CursorLoader {
-	/** The single instnace of this loader to exist */
+	/** The single instance of this loader to exist */
 	private static CursorLoader single = new CursorLoader();
 
 	/**
-	 * Retrieve the single instance of this loader - convinient huh?
+	 * Retrieve the single instance of this loader - convenient huh?
 	 * 
 	 * @return The single instance of the 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
 	 * @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(String ref,int x,int y) throws IOException, LWJGLException {
+	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 buf = imageData.loadImage(ResourceLoader.getResourceAsStream(ref), true, true, null);
-		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);
-			buf.put(i+3, alpha);
-		}
-		
-		try {
-			int yspot = imageData.getHeight() - y - 1;
-			if (yspot < 0) {
-				yspot = 0;
-			}
-			
-			return new Cursor(imageData.getTexWidth(), imageData.getTexHeight(), x, yspot, 1, buf.asIntBuffer(), null);
-		} catch (Throwable e) {
-			Log.info("Chances are you cursor is too small for this platform");
-			throw new LWJGLException(e);
-		}
+
+        imageData.loadImage(ResourceLoader.getResourceAsStream(ref), true, true, null);
+        return getCursor(imageData, x, y);
 	}
 
-	
 	/**
 	 * Get a cursor based on a set of image data
 	 * 
 	 * @throws IOException Indicates a failure to load the image
 	 * @throws LWJGLException Indicates a failure to create the hardware cursor
 	 */
-	public Cursor getCursor(ByteBuffer buf,int x,int y,int width,int height) throws IOException, LWJGLException {
-		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);
-			buf.put(i+3, alpha);
-		}
-		
-		try {
-			int yspot = height - y - 1;
-			if (yspot < 0) {
-				yspot = 0;
-			}
-			return new Cursor(width,height, x, yspot, 1, buf.asIntBuffer(), null);
-		} catch (Throwable e) {
-			Log.info("Chances are you cursor is too small for this platform");
-			throw new LWJGLException(e);
-		}
+	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);
+        }
 	}
 	
 	/**
 	 * @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 {
-		ByteBuffer buf = imageData.getImageBufferData();
-		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);
-			buf.put(i+3, alpha);
-		}
-		
-		try {
-			int yspot = imageData.getHeight() - y - 1;
-			if (yspot < 0) {
-				yspot = 0;
-			}
-			return new Cursor(imageData.getTexWidth(), imageData.getTexHeight(), x, yspot, 1, buf.asIntBuffer(), null);
-		} catch (Throwable e) {
-			Log.info("Chances are you cursor is too small for this platform");
-			throw new LWJGLException(e);
-		}
+        return getCursor(imageData.getImageBufferData(), x, y, imageData.getTexWidth(), imageData.getTexHeight(),
+                imageData.getWidth(), imageData.getHeight());
 	}
 	
 	/**
 	 * @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();