Well the loader does not seperate between cur and ico formats, wich is fine for the image part, as both are specified identically,
however there are 4 bytes that have a different meaning in cur and containg the hotspot ICO_(file_format)
So I added a case for loading hotspots from cur files (note for ani format the hotspot kinda works, this is only for .cur)
This is not the nicest patch, but then the whole class is kinda chaotic, and it gets the job done, and does not make stuff worse, so I would like seeing this getting merged into the trunk (As Iâm most probably not the only person needing cursors with a custom hotspot).
Following a pseudo dif of the CursorLoader.java
[java]
/*
- Copyright © 2009-2012 jMonkeyEngine
- All rights reserved.
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
-
- Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
- Neither the name of âjMonkeyEngineâ nor the names of its contributors
- may be used to endorse or promote products derived from this software
- without specific prior written permission.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- âAS ISâ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.cursors.plugins;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.IntBuffer;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetLoader;
import com.jme3.util.BufferUtils;
import com.jme3.util.LittleEndien;
/**
*
-
@author MadJack
-
@creation Jun 5, 2012 9:45:58 AM
*/
public class CursorLoader implements AssetLoader {private boolean isIco;
private boolean isAni;/**
- Loads and return a cursor file of one of the following format: .ani, .cur and .ico.
- @param info
-
The {@link AssetInfo} describing the cursor file.
- @return A JmeCursor representation of the LWJGLâs Cursor.
- @throws IOException
-
if the file is not found.
*/
@Override
public JmeCursor load(final AssetInfo info) throws IOException {this.isIco = false; this.isAni = false; this.isIco = info.getKey().getExtension().equals("ico"); if (!this.isIco) { this.isIco = info.getKey().getExtension().equals("cur"); if (!this.isIco) { this.isAni = info.getKey().getExtension().equals("ani"); } } if (!this.isAni && !this.isIco) { throw new IllegalArgumentException("Cursors supported are .ico, .cur or .ani"); } InputStream in = null; try { in = info.openStream();
â return this.loadCursor(in);
+++ return this.loadCursor(in, info);
} finally {
if (in != null) {
in.close();
}
}
}
â private JmeCursor loadCursor(final InputStream inStream) throws IOException {
+++ private JmeCursor loadCursor(final InputStream inStream, final AssetInfo info) throws IOException {
byte[] icoimages = new byte[0]; // new byte [0] facilitates read()
if (this.isAni) {
final CursorImageData ciDat = new CursorImageData();
int numIcons = 0;
int jiffy = 0;
// not using those but keeping references for now.
int steps = 0;
int width = 0;
int height = 0;
int flag = 0; // we don't use that.
int[] rate = null;
int[] animSeq = null;
ArrayList<byte[]> icons;
final DataInput leIn = new LittleEndien(inStream);
final int riff = leIn.readInt();
if (riff == 0x46464952) { // RIFF
// read next int (file length), discarding it, we don't need that.
leIn.readInt();
int nextInt = 0;
nextInt = this.getNext(leIn);
if (nextInt == 0x4e4f4341) {
// We have ACON, we do nothing
// System.out.println("We have ACON. Next!");
nextInt = this.getNext(leIn);
while (nextInt >= 0) {
if (nextInt == 0x68696e61) {
// System.out.println("we have 'anih' header");
leIn.skipBytes(8); // internal struct length (always 36)
numIcons = leIn.readInt();
steps = leIn.readInt(); // number of blits for ani cycles
width = leIn.readInt();
height = leIn.readInt();
leIn.skipBytes(8);
jiffy = leIn.readInt();
flag = leIn.readInt();
nextInt = leIn.readInt();
} else if (nextInt == 0x65746172) { // found a 'rate' of animation
// System.out.println("we have 'rate'.");
// Fill rate here.
// Rate is synchronous with frames.
final int length = leIn.readInt();
rate = new int[length / 4];
for (int i = 0; i < length / 4; i++) {
rate[i] = leIn.readInt();
}
nextInt = leIn.readInt();
} else if (nextInt == 0x20716573) { // found a 'seq ' of animation
// System.out.println("we have 'seq '.");
// Fill animation sequence here
final int length = leIn.readInt();
animSeq = new int[length / 4];
for (int i = 0; i < length / 4; i++) {
animSeq[i] = leIn.readInt();
}
nextInt = leIn.readInt();
} else if (nextInt == 0x5453494c) { // Found a LIST
// System.out.println("we have 'LIST'.");
final int length = leIn.readInt();
nextInt = leIn.readInt();
if (nextInt == 0x4f464e49) { // Got an INFO, skip its length
// this part consist of Author, title, etc
leIn.skipBytes(length - 4);
// System.out.println(" Discarding INFO (skipped = " + skipped + ")");
nextInt = leIn.readInt();
} else if (nextInt == 0x6d617266) { // found a 'fram' for animation
// System.out.println("we have 'fram'.");
if (leIn.readInt() == 0x6e6f6369) { // we have 'icon'
// We have an icon and from this point on
// the rest is only icons.
final int icoLength = leIn.readInt();
ciDat.numImages = numIcons;
icons = new ArrayList<byte[]>(numIcons);
for (int i = 0; i < numIcons; i++) {
if (i > 0) {
// skip 'icon' header and length as they are
// known already and won't change.
leIn.skipBytes(8);
}
final byte[] data = new byte[icoLength];
((InputStream) leIn).read(data, 0, icoLength);
// in case the header didn't have width or height
// get it from first image.
if (width == 0 || height == 0 && i == 1) {
width = data[6];
height = data[7];
}
icons.add(data);
}
// at this point we have the icons, rates (either
// through jiffy or rate array, the sequence (if
// applicable) and the ani header info.
// Put things together.
ciDat.assembleCursor(icons, rate, animSeq, jiffy, steps, width, height);
ciDat.completeCursor();
nextInt = leIn.readInt();
// if for some reason there's JUNK (nextInt > -1)
// bail out.
nextInt = nextInt > -1 ? -1 : nextInt;
}
}
}
}
}
return this.setJmeCursor(ciDat);
} else if (riff == 0x58464952) {
throw new IllegalArgumentException("Big-Endian RIFX is not supported. Sorry.");
} else {
throw new IllegalArgumentException("Unknown format.");
}
} else if (this.isIco) {
final DataInputStream in = new DataInputStream(inStream);
int bytesToRead;
while ((bytesToRead = in.available()) != 0) {
final byte[] icoimage2 = new byte[icoimages.length + bytesToRead];
System.arraycopy(icoimages, 0, icoimage2, 0, icoimages.length);
in.read(icoimage2, icoimages.length, bytesToRead);
icoimages = icoimage2;
}
}
final BufferedImage bi[] = this.parseICOImage(icoimages);
final CursorImageData cid = new CursorImageData(bi, 0, 0, 0, 0);
// only cur does store hotspots NOTE: untested for extremly large images
+++ if (info.getKey().getExtension().equals(âcurâ)) {
+++ final int FDE_OFFSET = 6; // first directory entry offset
+++ int hotspotX = icoimages[FDE_OFFSET + 4];
+++ hotspotX = hotspotX + icoimages[FDE_OFFSET + 5] * 255;
+++ int hotspotY = icoimages[FDE_OFFSET + 6];
+++ hotspotY = hotspotY + icoimages[FDE_OFFSET + 7] * 255;
+++ cid.xHotSpot = hotspotX;
+++ cid.yHotSpot = bi[0].getHeight() - 1 - hotspotY;
+++ }
cid.completeCursor();
return this.setJmeCursor(cid);
}
private JmeCursor setJmeCursor(final CursorImageData cid) {
final JmeCursor jmeCursor = new JmeCursor();
// set cursor's params.
jmeCursor.setWidth(cid.width);
jmeCursor.setHeight(cid.height);
jmeCursor.setxHotSpot(cid.xHotSpot);
jmeCursor.setyHotSpot(cid.yHotSpot);
jmeCursor.setNumImages(cid.numImages);
jmeCursor.setImagesDelay(cid.imgDelay);
jmeCursor.setImagesData(cid.data);
// System.out.println("Width = " + cid.width);
// System.out.println("Height = " + cid.height);
// System.out.println("HSx = " + cid.xHotSpot);
// System.out.println("HSy = " + cid.yHotSpot);
// System.out.println("# img = " + cid.numImages);
return jmeCursor;
}
private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException {
/*
* Most of this is original code by Jeff Friesen at http://www.informit.com/articles/article.aspx?p=1186882&seqNum=3
*/
BufferedImage[] bi;
// Check resource type field.
final int FDE_OFFSET = 6; // first directory entry offset
final int DE_LENGTH = 16; // directory entry length
final int BMIH_LENGTH = 40; // BITMAPINFOHEADER length
if (icoimage[2] != 1 && icoimage[2] != 2 || icoimage[3] != 0) {
throw new IllegalArgumentException("Bad data in ICO/CUR file. ImageType has to be either 1 or 2.");
}
int numImages = this.ubyte(icoimage[5]);
numImages <<= 8;
numImages |= icoimage[4];
bi = new BufferedImage[numImages];
final int[] colorCount = new int[numImages];
for (int i = 0; i < numImages; i++) {
int width = this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH]);
int height = this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 1]);
colorCount[i] = this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 2]);
int bytesInRes = this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 11]);
bytesInRes <<= 8;
bytesInRes |= this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 10]);
bytesInRes <<= 8;
bytesInRes |= this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 9]);
bytesInRes <<= 8;
bytesInRes |= this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 8]);
int imageOffset = this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 15]);
imageOffset <<= 8;
imageOffset |= this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 14]);
imageOffset <<= 8;
imageOffset |= this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 13]);
imageOffset <<= 8;
imageOffset |= this.ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 12]);
if (icoimage[imageOffset] == 40 && icoimage[imageOffset + 1] == 0 && icoimage[imageOffset + 2] == 0
&& icoimage[imageOffset + 3] == 0) {
// BITMAPINFOHEADER detected
int _width = this.ubyte(icoimage[imageOffset + 7]);
_width <<= 8;
_width |= this.ubyte(icoimage[imageOffset + 6]);
_width <<= 8;
_width |= this.ubyte(icoimage[imageOffset + 5]);
_width <<= 8;
_width |= this.ubyte(icoimage[imageOffset + 4]);
// If width is 0 (for 256 pixels or higher), _width contains
// actual width.
if (width == 0) {
width = _width;
}
int _height = this.ubyte(icoimage[imageOffset + 11]);
_height <<= 8;
_height |= this.ubyte(icoimage[imageOffset + 10]);
_height <<= 8;
_height |= this.ubyte(icoimage[imageOffset + 9]);
_height <<= 8;
_height |= this.ubyte(icoimage[imageOffset + 8]);
// If height is 0 (for 256 pixels or higher), _height contains
// actual height times 2.
if (height == 0) {
height = _height >> 1; // Divide by 2.
}
int planes = this.ubyte(icoimage[imageOffset + 13]);
planes <<= 8;
planes |= this.ubyte(icoimage[imageOffset + 12]);
int bitCount = this.ubyte(icoimage[imageOffset + 15]);
bitCount <<= 8;
bitCount |= this.ubyte(icoimage[imageOffset + 14]);
// If colorCount [i] is 0, the number of colors is determined
// from the planes and bitCount values. For example, the number
// of colors is 256 when planes is 1 and bitCount is 8. Leave
// colorCount [i] set to 0 when planes is 1 and bitCount is 32.
if (colorCount[i] == 0) {
if (planes == 1) {
if (bitCount == 1) {
colorCount[i] = 2;
} else if (bitCount == 4) {
colorCount[i] = 16;
} else if (bitCount == 8) {
colorCount[i] = 256;
} else if (bitCount != 32) {
colorCount[i] = (int) Math.pow(2, bitCount);
}
} else {
colorCount[i] = (int) Math.pow(2, bitCount * planes);
}
}
bi[i] = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
// Parse image to image buffer.
final int colorTableOffset = imageOffset + BMIH_LENGTH;
if (colorCount[i] == 2) {
final int xorImageOffset = colorTableOffset + 2 * 4;
final int scanlineBytes = this.calcScanlineBytes(width, 1);
final int andImageOffset = xorImageOffset + scanlineBytes * height;
final int[] masks = { 128, 64, 32, 16, 8, 4, 2, 1 };
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int index;
if ((this.ubyte(icoimage[xorImageOffset + row * scanlineBytes + col / 8]) & masks[col % 8]) != 0) {
index = 1;
} else {
index = 0;
}
int rgb = 0;
rgb |= this.ubyte(icoimage[colorTableOffset + index * 4 + 2]);
rgb <<= 8;
rgb |= this.ubyte(icoimage[colorTableOffset + index * 4 + 1]);
rgb <<= 8;
rgb |= this.ubyte(icoimage[colorTableOffset + index * 4]);
if ((this.ubyte(icoimage[andImageOffset + row * scanlineBytes + col / 8]) & masks[col % 8]) != 0) {
bi[i].setRGB(col, height - 1 - row, rgb);
} else {
bi[i].setRGB(col, height - 1 - row, 0xff000000 | rgb);
}
}
}
} else if (colorCount[i] == 16) {
final int xorImageOffset = colorTableOffset + 16 * 4;
final int scanlineBytes = this.calcScanlineBytes(width, 4);
final int andImageOffset = xorImageOffset + scanlineBytes * height;
final int[] masks = { 128, 64, 32, 16, 8, 4, 2, 1 };
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int index;
if ((col & 1) == 0) // even
{
index = this.ubyte(icoimage[xorImageOffset + row * scanlineBytes + col / 2]);
index >>= 4;
} else {
index = this.ubyte(icoimage[xorImageOffset + row * scanlineBytes + col / 2]) & 15;
}
int rgb = 0;
rgb |= this.ubyte(icoimage[colorTableOffset + index * 4 + 2]);
rgb <<= 8;
rgb |= this.ubyte(icoimage[colorTableOffset + index * 4 + 1]);
rgb <<= 8;
rgb |= this.ubyte(icoimage[colorTableOffset + index * 4]);
if ((this
.ubyte(icoimage[andImageOffset + row * this.calcScanlineBytes(width, 1) + col / 8]) & masks[col % 8]) != 0) {
bi[i].setRGB(col, height - 1 - row, rgb);
} else {
bi[i].setRGB(col, height - 1 - row, 0xff000000 | rgb);
}
}
}
} else if (colorCount[i] == 256) {
final int xorImageOffset = colorTableOffset + 256 * 4;
final int scanlineBytes = this.calcScanlineBytes(width, 8);
final int andImageOffset = xorImageOffset + scanlineBytes * height;
final int[] masks = { 128, 64, 32, 16, 8, 4, 2, 1 };
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int index;
index = this.ubyte(icoimage[xorImageOffset + row * scanlineBytes + col]);
int rgb = 0;
rgb |= this.ubyte(icoimage[colorTableOffset + index * 4 + 2]);
rgb <<= 8;
rgb |= this.ubyte(icoimage[colorTableOffset + index * 4 + 1]);
rgb <<= 8;
rgb |= this.ubyte(icoimage[colorTableOffset + index * 4]);
if ((this
.ubyte(icoimage[andImageOffset + row * this.calcScanlineBytes(width, 1) + col / 8]) & masks[col % 8]) != 0) {
bi[i].setRGB(col, height - 1 - row, rgb);
} else {
bi[i].setRGB(col, height - 1 - row, 0xff000000 | rgb);
}
}
}
} else if (colorCount[i] == 0) {
final int scanlineBytes = this.calcScanlineBytes(width, 32);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int rgb = this.ubyte(icoimage[colorTableOffset + row * scanlineBytes + col * 4 + 3]);
rgb <<= 8;
rgb |= this.ubyte(icoimage[colorTableOffset + row * scanlineBytes + col * 4 + 2]);
rgb <<= 8;
rgb |= this.ubyte(icoimage[colorTableOffset + row * scanlineBytes + col * 4 + 1]);
rgb <<= 8;
rgb |= this.ubyte(icoimage[colorTableOffset + row * scanlineBytes + col * 4]);
bi[i].setRGB(col, height - 1 - row, rgb);
}
}
}
} else if (this.ubyte(icoimage[imageOffset]) == 0x89 && icoimage[imageOffset + 1] == 0x50
&& icoimage[imageOffset + 2] == 0x4e && icoimage[imageOffset + 3] == 0x47
&& icoimage[imageOffset + 4] == 0x0d && icoimage[imageOffset + 5] == 0x0a
&& icoimage[imageOffset + 6] == 0x1a && icoimage[imageOffset + 7] == 0x0a) {
// PNG detected
ByteArrayInputStream bais;
bais = new ByteArrayInputStream(icoimage, imageOffset, bytesInRes);
bi[i] = ImageIO.read(bais);
} else {
throw new IllegalArgumentException("Bad data in ICO/CUR file. BITMAPINFOHEADER or PNG " + "expected");
}
}
icoimage = null; // This array can now be garbage collected.
return bi;
}
private int ubyte(final byte b) {
return b < 0 ? 256 + b : b; // Convert byte to unsigned byte.
}
private int calcScanlineBytes(final int width, final int bitCount) {
// Calculate minimum number of double-words required to store width
// pixels where each pixel occupies bitCount bits. XOR and AND bitmaps
// are stored such that each scanline is aligned on a double-word
// boundary.
return (width * bitCount + 31) / 32 * 4;
}
private int getNext(final DataInput in) throws IOException {
return in.readInt();
}
private class CursorImageData {
int width;
int height;
int xHotSpot;
int yHotSpot;
int numImages;
IntBuffer imgDelay;
IntBuffer data;
public CursorImageData() {
}
CursorImageData(final BufferedImage[] bi, final int delay, int hsX, int hsY, final int curType) {
// cursor type
// 0 - Undefined (an array of images inside an ICO)
// 1 - ICO
// 2 - CUR
IntBuffer singleCursor = null;
final ArrayList<IntBuffer> cursors = new ArrayList<IntBuffer>();
int bwidth = 0;
int bheight = 0;
boolean multIcons = false;
// make the cursor image
for (int i = 0; i < bi.length; i++) {
BufferedImage img = bi[i];
bwidth = img.getWidth();
bheight = img.getHeight();
if (curType == 1) {
hsX = 0;
hsY = bheight - 1;
} else if (curType == 2) {
if (hsY == 0) {
// make sure we flip if 0
hsY = bheight - 1;
}
} else {
// We force to choose 32x32 icon from
// the array of icons in that ICO file.
if (bwidth != 32 && bheight != 32) {
multIcons = true;
continue;
} else {
if (img.getType() != 2) {
continue;
} else {
// force hotspot
hsY = bheight - 1;
}
}
}
// We flip our image because .ICO and .CUR will always be reversed.
final AffineTransform trans = AffineTransform.getScaleInstance(1, -1);
trans.translate(0, -img.getHeight(null));
final AffineTransformOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_BILINEAR);
img = op.filter(img, null);
singleCursor = BufferUtils.createIntBuffer(img.getWidth() * img.getHeight());
final DataBufferInt dataIntBuf = (DataBufferInt) img.getData().getDataBuffer();
singleCursor = IntBuffer.wrap(dataIntBuf.getData());
cursors.add(singleCursor);
}
int count;
if (multIcons) {
bwidth = 32;
bheight = 32;
count = 1;
} else {
count = cursors.size();
}
// put the image in the IntBuffer
this.data = BufferUtils.createIntBuffer(bwidth * bheight);
this.imgDelay = BufferUtils.createIntBuffer(bi.length);
for (int i = 0; i < count; i++) {
this.data.put(cursors.get(i));
if (delay > 0) {
this.imgDelay.put(delay);
}
}
this.width = bwidth;
this.height = bheight;
this.xHotSpot = hsX;
this.yHotSpot = hsY;
this.numImages = count;
this.data.rewind();
if (this.imgDelay != null) {
this.imgDelay.rewind();
}
}
private void addFrame(final byte[] imgData, int rate, final int jiffy, final int width, final int height,
final int numSeq) throws IOException {
final BufferedImage bi[] = CursorLoader.this.parseICOImage(imgData);
int hotspotx = 0;
int hotspoty = 0;
final int type = imgData[2] | imgData[3];
if (type == 2) {
// CUR type, hotspot might be stored.
hotspotx = imgData[10] | imgData[11];
hotspoty = imgData[12] | imgData[13];
} else if (type == 1) {
// ICO type, hotspot not stored. Put at 0, height - 1
// because it's flipped.
hotspotx = 0;
hotspoty = height - 1;
}
// System.out.println("Image type = " + (type == 1 ? "CUR" : "ICO"));
if (rate == 0) {
rate = jiffy;
}
CursorImageData cid = new CursorImageData(bi, rate, hotspotx, hotspoty, type);
if (width == 0) {
this.width = cid.width;
} else {
this.width = width;
}
if (height == 0) {
this.height = cid.height;
} else {
this.height = height;
}
if (this.data == null) {
if (numSeq > this.numImages) {
this.data = BufferUtils.createIntBuffer(this.width * this.height * numSeq);
} else {
this.data = BufferUtils.createIntBuffer(this.width * this.height * this.numImages);
}
this.data.put(cid.data);
} else {
this.data.put(cid.data);
}
if (this.imgDelay == null && (this.numImages > 1 || numSeq > 1)) {
if (numSeq > this.numImages) {
this.imgDelay = BufferUtils.createIntBuffer(numSeq);
} else {
this.imgDelay = BufferUtils.createIntBuffer(this.numImages);
}
this.imgDelay.put(cid.imgDelay);
} else if (imgData != null) {
this.imgDelay.put(cid.imgDelay);
}
this.xHotSpot = cid.xHotSpot;
this.yHotSpot = cid.yHotSpot;
cid = null;
}
void assembleCursor(final ArrayList<byte[]> icons, final int[] rate, final int[] animSeq, final int jiffy,
final int steps, final int width, final int height) throws IOException {
// Jiffy multiplicator for LWJGL's delay, which is in milisecond.
final int MULT = 17;
this.numImages = icons.size();
int frRate = 0;
byte[] frame = new byte[0];
// if we have an animation sequence we use that
// since the sequence can be larger than the number
// of images in the ani if it reuses one or more of those
// images.
if (animSeq != null && animSeq.length > 0) {
for (int i = 0; i < animSeq.length; i++) {
if (rate != null) {
frRate = rate[i] * MULT;
} else {
frRate = jiffy * MULT;
}
// the frame # is the one in the animation sequence
frame = icons.get(animSeq[i]);
this.addFrame(frame, frRate, jiffy, width, height, animSeq.length);
// System.out.println("delay of " + frRate);
}
} else {
for (int i = 0; i < icons.size(); i++) {
frame = icons.get(i);
if (rate == null) {
frRate = jiffy * MULT;
} else {
frRate = rate[i] * MULT;
}
this.addFrame(frame, frRate, jiffy, width, height, 0);
// System.out.println("delay of " + frRate);
}
}
}
/**
* Called to rewind the buffers after filling them.
*/
void completeCursor() {
if (this.numImages == 1) {
this.imgDelay = null;
} else {
this.imgDelay.rewind();
}
this.data.rewind();
}
}
}
[/java]
And last but not least a simple TestCase, so it is easily verifiable
[java]
package com.jme3x.jfx;
import org.lwjgl.input.Mouse;
import com.jme3.app.SimpleApplication;
import com.jme3.cursors.plugins.JmeCursor;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
public class CursorHotspotTest extends SimpleApplication {
private Geometry hotspotDebug;
public static void main(final String[] args) {
new CursorHotspotTest().start();
}
@Override
public void simpleInitApp() {
this.flyCam.setEnabled(false);
final JmeCursor loaded = (JmeCursor) this.assetManager.loadAsset("com/jme3x/jfx/cursor/proton/aero_cross.cur");
this.inputManager.setMouseCursor(loaded);
final Quad hotspot = new Quad(2, 2);
this.hotspotDebug = new Geometry("gurke", hotspot);
final Material hotspotMaterial = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
hotspotMaterial.setColor("Color", ColorRGBA.Cyan);
this.hotspotDebug.setMaterial(hotspotMaterial);
this.guiNode.attachChild(this.hotspotDebug);
this.hotspotDebug.setLocalTranslation(500, 500, 0);
}
@Override
public void simpleUpdate(final float tpf) {
this.hotspotDebug.setLocalTranslation(Mouse.getX(), Mouse.getY(), 0);
}
}
[/java]