The way to do this is by calling inputManager.setMouseCursor("path/to/cursor/in/assets/cursor.ani"). NOTE: Supported formats are "RIFF-wrapped" .ani, .cur and .ico. Those can be found everywhere on the web and are the majority of icons found. If the file format is unrecognized the loader will crash with an appropriate message (hopefully). git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9484 75d07b2b-3a1a-0410-a2c5-0572b91ccdca3.0
parent
4e419a854c
commit
5ac9953f62
@ -0,0 +1,672 @@ |
|||||||
|
package com.jme3.cursors.plugins; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetInfo; |
||||||
|
import com.jme3.asset.AssetKey; |
||||||
|
import com.jme3.asset.AssetLoader; |
||||||
|
import com.jme3.util.BufferUtils; |
||||||
|
import com.jme3.util.LittleEndien; |
||||||
|
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; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @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. |
||||||
|
*/ |
||||||
|
public JmeCursor load(AssetInfo info) throws IOException { |
||||||
|
|
||||||
|
isIco = ((AssetKey) info.getKey()).getExtension().equals("ico"); |
||||||
|
if (!isIco) { |
||||||
|
isIco = ((AssetKey) info.getKey()).getExtension().equals("cur"); |
||||||
|
if (!isIco) { |
||||||
|
isAni = ((AssetKey) info.getKey()).getExtension().equals("ani"); |
||||||
|
} |
||||||
|
} |
||||||
|
if (!isAni && !isIco) { |
||||||
|
throw new IllegalArgumentException("Cursors supported are .ico, .cur or .ani"); |
||||||
|
} |
||||||
|
|
||||||
|
InputStream in = null; |
||||||
|
try { |
||||||
|
in = info.openStream(); |
||||||
|
return loadCursor(in); |
||||||
|
} finally { |
||||||
|
if (in != null) { |
||||||
|
in.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private JmeCursor loadCursor(InputStream inStream) throws IOException { |
||||||
|
|
||||||
|
byte[] icoimages = new byte[0]; // new byte [0] facilitates read()
|
||||||
|
|
||||||
|
if (isAni) { |
||||||
|
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; |
||||||
|
|
||||||
|
DataInput leIn = new LittleEndien(inStream); |
||||||
|
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 = getNext(leIn); |
||||||
|
if (nextInt == 0x4e4f4341) { |
||||||
|
// We have ACON, we do nothing
|
||||||
|
// System.out.println("We have ACON. Next!");
|
||||||
|
nextInt = 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.
|
||||||
|
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
|
||||||
|
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'.");
|
||||||
|
int length = leIn.readInt(); |
||||||
|
nextInt = leIn.readInt(); |
||||||
|
if (nextInt == 0x4f464e49) { // Got an INFO, skip its length
|
||||||
|
// this part consist of Author, title, etc
|
||||||
|
// int skipped = 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.
|
||||||
|
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); |
||||||
|
} |
||||||
|
byte[] data = new byte[icoLength]; |
||||||
|
((InputStream) leIn).read(data, 0, icoLength); |
||||||
|
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 setJmeCursor(ciDat); |
||||||
|
|
||||||
|
} else if (riff == 0x58464952) { |
||||||
|
throw new IllegalArgumentException("Big-Endian RIFX is not supported. Sorry."); |
||||||
|
} else { |
||||||
|
throw new IllegalArgumentException("Unknown format."); |
||||||
|
} |
||||||
|
} else if (isIco) { |
||||||
|
DataInputStream in = new DataInputStream(inStream); |
||||||
|
int bytesToRead; |
||||||
|
while ((bytesToRead = in.available()) != 0) { |
||||||
|
byte[] icoimage2 = new byte[icoimages.length + bytesToRead]; |
||||||
|
System.arraycopy(icoimages, 0, icoimage2, 0, icoimages.length); |
||||||
|
in.read(icoimage2, icoimages.length, bytesToRead); |
||||||
|
icoimages = icoimage2; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
BufferedImage bi[] = parseICOImage(icoimages); |
||||||
|
CursorImageData cid = new CursorImageData(bi, 0, 0, 0); |
||||||
|
cid.completeCursor(); |
||||||
|
|
||||||
|
return setJmeCursor(cid); |
||||||
|
} |
||||||
|
|
||||||
|
private JmeCursor setJmeCursor(CursorImageData cid) { |
||||||
|
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.
|
||||||
|
int FDE_OFFSET = 6; // first directory entry offset
|
||||||
|
int DE_LENGTH = 16; // directory entry length
|
||||||
|
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 = ubyte(icoimage[5]); |
||||||
|
numImages <<= 8; |
||||||
|
numImages |= icoimage[4]; |
||||||
|
bi = new BufferedImage[numImages]; |
||||||
|
int[] colorCount = new int[numImages]; |
||||||
|
|
||||||
|
for (int i = 0; i < numImages; i++) { |
||||||
|
int width = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH]); |
||||||
|
|
||||||
|
int height = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 1]); |
||||||
|
|
||||||
|
colorCount[i] = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 2]); |
||||||
|
|
||||||
|
int bytesInRes = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 11]); |
||||||
|
bytesInRes <<= 8; |
||||||
|
bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 10]); |
||||||
|
bytesInRes <<= 8; |
||||||
|
bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 9]); |
||||||
|
bytesInRes <<= 8; |
||||||
|
bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 8]); |
||||||
|
|
||||||
|
int imageOffset = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 15]); |
||||||
|
imageOffset <<= 8; |
||||||
|
imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 14]); |
||||||
|
imageOffset <<= 8; |
||||||
|
imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 13]); |
||||||
|
imageOffset <<= 8; |
||||||
|
imageOffset |= 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 = ubyte(icoimage[imageOffset + 7]); |
||||||
|
_width <<= 8; |
||||||
|
_width |= ubyte(icoimage[imageOffset + 6]); |
||||||
|
_width <<= 8; |
||||||
|
_width |= ubyte(icoimage[imageOffset + 5]); |
||||||
|
_width <<= 8; |
||||||
|
_width |= ubyte(icoimage[imageOffset + 4]); |
||||||
|
|
||||||
|
// If width is 0 (for 256 pixels or higher), _width contains
|
||||||
|
// actual width.
|
||||||
|
|
||||||
|
if (width == 0) { |
||||||
|
width = _width; |
||||||
|
} |
||||||
|
|
||||||
|
int _height = ubyte(icoimage[imageOffset + 11]); |
||||||
|
_height <<= 8; |
||||||
|
_height |= ubyte(icoimage[imageOffset + 10]); |
||||||
|
_height <<= 8; |
||||||
|
_height |= ubyte(icoimage[imageOffset + 9]); |
||||||
|
_height <<= 8; |
||||||
|
_height |= 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 = ubyte(icoimage[imageOffset + 13]); |
||||||
|
planes <<= 8; |
||||||
|
planes |= ubyte(icoimage[imageOffset + 12]); |
||||||
|
|
||||||
|
int bitCount = ubyte(icoimage[imageOffset + 15]); |
||||||
|
bitCount <<= 8; |
||||||
|
bitCount |= 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.
|
||||||
|
|
||||||
|
int colorTableOffset = imageOffset + BMIH_LENGTH; |
||||||
|
|
||||||
|
if (colorCount[i] == 2) { |
||||||
|
int xorImageOffset = colorTableOffset + 2 * 4; |
||||||
|
|
||||||
|
int scanlineBytes = calcScanlineBytes(width, 1); |
||||||
|
int andImageOffset = xorImageOffset + scanlineBytes * height; |
||||||
|
|
||||||
|
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 ((ubyte(icoimage[xorImageOffset + row |
||||||
|
* scanlineBytes + col / 8]) |
||||||
|
& masks[col % 8]) != 0) { |
||||||
|
index = 1; |
||||||
|
} else { |
||||||
|
index = 0; |
||||||
|
} |
||||||
|
|
||||||
|
int rgb = 0; |
||||||
|
rgb |= (ubyte(icoimage[colorTableOffset + index * 4 |
||||||
|
+ 2])); |
||||||
|
rgb <<= 8; |
||||||
|
rgb |= (ubyte(icoimage[colorTableOffset + index * 4 |
||||||
|
+ 1])); |
||||||
|
rgb <<= 8; |
||||||
|
rgb |= (ubyte(icoimage[colorTableOffset + index |
||||||
|
* 4])); |
||||||
|
|
||||||
|
if ((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) { |
||||||
|
int xorImageOffset = colorTableOffset + 16 * 4; |
||||||
|
|
||||||
|
int scanlineBytes = calcScanlineBytes(width, 4); |
||||||
|
int andImageOffset = xorImageOffset + scanlineBytes * height; |
||||||
|
|
||||||
|
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 = ubyte(icoimage[xorImageOffset + row |
||||||
|
* scanlineBytes + col / 2]); |
||||||
|
index >>= 4; |
||||||
|
} else { |
||||||
|
index = ubyte(icoimage[xorImageOffset + row |
||||||
|
* scanlineBytes + col / 2]) |
||||||
|
& 15; |
||||||
|
} |
||||||
|
|
||||||
|
int rgb = 0; |
||||||
|
rgb |= (ubyte(icoimage[colorTableOffset + index * 4 |
||||||
|
+ 2])); |
||||||
|
rgb <<= 8; |
||||||
|
rgb |= (ubyte(icoimage[colorTableOffset + index * 4 |
||||||
|
+ 1])); |
||||||
|
rgb <<= 8; |
||||||
|
rgb |= (ubyte(icoimage[colorTableOffset + index |
||||||
|
* 4])); |
||||||
|
|
||||||
|
if ((ubyte(icoimage[andImageOffset + row |
||||||
|
* 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) { |
||||||
|
int xorImageOffset = colorTableOffset + 256 * 4; |
||||||
|
|
||||||
|
int scanlineBytes = calcScanlineBytes(width, 8); |
||||||
|
int andImageOffset = xorImageOffset + scanlineBytes * height; |
||||||
|
|
||||||
|
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 = ubyte(icoimage[xorImageOffset + row |
||||||
|
* scanlineBytes + col]); |
||||||
|
|
||||||
|
int rgb = 0; |
||||||
|
rgb |= (ubyte(icoimage[colorTableOffset + index * 4 |
||||||
|
+ 2])); |
||||||
|
rgb <<= 8; |
||||||
|
rgb |= (ubyte(icoimage[colorTableOffset + index * 4 |
||||||
|
+ 1])); |
||||||
|
rgb <<= 8; |
||||||
|
rgb |= (ubyte(icoimage[colorTableOffset + index * 4])); |
||||||
|
|
||||||
|
if ((ubyte(icoimage[andImageOffset + row |
||||||
|
* 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) { |
||||||
|
int scanlineBytes = calcScanlineBytes(width, 32); |
||||||
|
|
||||||
|
for (int row = 0; row < height; row++) { |
||||||
|
for (int col = 0; col < width; col++) { |
||||||
|
int rgb = ubyte(icoimage[colorTableOffset + row |
||||||
|
* scanlineBytes + col * 4 + 3]); |
||||||
|
rgb <<= 8; |
||||||
|
rgb |= ubyte(icoimage[colorTableOffset + row |
||||||
|
* scanlineBytes + col * 4 + 2]); |
||||||
|
rgb <<= 8; |
||||||
|
rgb |= ubyte(icoimage[colorTableOffset + row |
||||||
|
* scanlineBytes + col * 4 + 1]); |
||||||
|
rgb <<= 8; |
||||||
|
rgb |= ubyte(icoimage[colorTableOffset + row |
||||||
|
* scanlineBytes + col * 4]); |
||||||
|
|
||||||
|
bi[i].setRGB(col, height - 1 - row, rgb); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (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(byte b) { |
||||||
|
return (b < 0) ? 256 + b : b; // Convert byte to unsigned byte.
|
||||||
|
} |
||||||
|
|
||||||
|
private int calcScanlineBytes(int width, 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(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(BufferedImage[] bi, int delay, int hsX, int hsY) { |
||||||
|
IntBuffer singleCursor = null; |
||||||
|
ArrayList<IntBuffer> cursors = new ArrayList<IntBuffer>(); |
||||||
|
int bwidth = 0; |
||||||
|
int bheight = 0; |
||||||
|
// cursor type
|
||||||
|
// 1 - ICO
|
||||||
|
// 2 - CUR
|
||||||
|
// Anything else is invalid.
|
||||||
|
int curType = 0; |
||||||
|
|
||||||
|
// make the cursor image
|
||||||
|
for (int i = 0; i < bi.length; i++) { |
||||||
|
BufferedImage img = bi[i]; |
||||||
|
bwidth = img.getWidth(); |
||||||
|
bheight = img.getHeight(); |
||||||
|
curType = img.getType(); |
||||||
|
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 { |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"An image contained is not of the right type! Only proper ICO and CUR formats are valid."); |
||||||
|
} |
||||||
|
|
||||||
|
// We flip our image because .ICO and .CUR will always be reversed.
|
||||||
|
AffineTransform trans = AffineTransform.getScaleInstance(1, -1); |
||||||
|
trans.translate(0, -img.getHeight(null)); |
||||||
|
AffineTransformOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_BILINEAR); |
||||||
|
img = op.filter(img, null); |
||||||
|
|
||||||
|
singleCursor = BufferUtils.createIntBuffer(img.getWidth() * img.getHeight()); |
||||||
|
DataBufferInt dataIntBuf = (DataBufferInt) img.getData().getDataBuffer(); |
||||||
|
singleCursor = IntBuffer.wrap(dataIntBuf.getData()); |
||||||
|
cursors.add(singleCursor); |
||||||
|
} |
||||||
|
|
||||||
|
// put the image in the IntBuffer
|
||||||
|
data = BufferUtils.createIntBuffer(bwidth * bheight); |
||||||
|
imgDelay = BufferUtils.createIntBuffer(bi.length); |
||||||
|
for (int i = 0; i < cursors.size(); i++) { |
||||||
|
data.put(cursors.get(i)); |
||||||
|
if (delay > 0) { |
||||||
|
imgDelay.put(delay); |
||||||
|
} |
||||||
|
} |
||||||
|
width = bwidth; |
||||||
|
height = bheight; |
||||||
|
xHotSpot = hsX; |
||||||
|
yHotSpot = hsY; |
||||||
|
numImages = cursors.size(); |
||||||
|
data.rewind(); |
||||||
|
if (imgDelay != null) { |
||||||
|
imgDelay.rewind(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void addFrame(byte[] imgData, int rate, int jiffy, int width, int height) throws IOException { |
||||||
|
BufferedImage bi[] = parseICOImage(imgData); |
||||||
|
int hotspotx = 0; |
||||||
|
int hotspoty = 0; |
||||||
|
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. Leave at 0;
|
||||||
|
hotspotx = 0; |
||||||
|
hotspoty = 0; |
||||||
|
} |
||||||
|
// System.out.println("Image type = " + (type == 1 ? "CUR" : "ICO"));
|
||||||
|
if (rate == 0) { |
||||||
|
rate = jiffy; |
||||||
|
} |
||||||
|
CursorImageData cid = new CursorImageData(bi, rate, hotspotx, hotspoty); |
||||||
|
if (width == 0) { |
||||||
|
this.width = cid.width; |
||||||
|
} else { |
||||||
|
this.width = width; |
||||||
|
} |
||||||
|
if (height == 0) { |
||||||
|
this.height = cid.height; |
||||||
|
} else { |
||||||
|
this.height = height; |
||||||
|
} |
||||||
|
if (data == null) { |
||||||
|
data = BufferUtils.createIntBuffer(this.width * this.height * numImages); |
||||||
|
data.put(cid.data); |
||||||
|
} else { |
||||||
|
data.put(cid.data); |
||||||
|
} |
||||||
|
if (imgDelay == null && numImages > 1) { |
||||||
|
imgDelay = BufferUtils.createIntBuffer(numImages); |
||||||
|
imgDelay.put(cid.imgDelay); |
||||||
|
} else if (imgData != null) { |
||||||
|
imgDelay.put(cid.imgDelay); |
||||||
|
} |
||||||
|
xHotSpot = cid.xHotSpot; |
||||||
|
yHotSpot = cid.yHotSpot; |
||||||
|
cid = null; |
||||||
|
} |
||||||
|
|
||||||
|
void assembleCursor(ArrayList<byte[]> icons, int[] rate, int[] animSeq, int jiffy, int steps, int width, int height) throws IOException { |
||||||
|
// Jiffy multiplicator for LWJGL's delay, which is in milisecond.
|
||||||
|
final int MULT = 17; |
||||||
|
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) { |
||||||
|
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]); |
||||||
|
addFrame(frame, frRate, jiffy, width, height); |
||||||
|
// 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; |
||||||
|
} |
||||||
|
addFrame(frame, frRate, jiffy, width, height); |
||||||
|
// System.out.println("delay of " + frRate);
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called to rewind the buffers after filling them. |
||||||
|
*/ |
||||||
|
void completeCursor() { |
||||||
|
if (numImages == 1) { |
||||||
|
imgDelay = null; |
||||||
|
} else { |
||||||
|
imgDelay.rewind(); |
||||||
|
} |
||||||
|
data.rewind(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,151 @@ |
|||||||
|
package com.jme3.cursors.plugins; |
||||||
|
|
||||||
|
import java.nio.IntBuffer; |
||||||
|
|
||||||
|
/** |
||||||
|
* A Jme representation of the LWJGL Cursor class. |
||||||
|
* |
||||||
|
* @author MadJack |
||||||
|
* @creation Jun 6, 2012 12:12:38 PM |
||||||
|
*/ |
||||||
|
public class JmeCursor { |
||||||
|
|
||||||
|
private int width; |
||||||
|
private int height; |
||||||
|
private int xHotSpot; |
||||||
|
private int yHotSpot; |
||||||
|
private int numImages; |
||||||
|
private IntBuffer imagesData; |
||||||
|
private IntBuffer imagesDelay; |
||||||
|
|
||||||
|
/** |
||||||
|
* Queries the cursor's height. Note that |
||||||
|
* the coordinate system is the same as OpenGL. 0, 0 being lower left. |
||||||
|
* @return the height in pixel. |
||||||
|
*/ |
||||||
|
public int getHeight() { |
||||||
|
return height; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Queries the cursor's images' data. |
||||||
|
* @return An {@link IntBuffer} containing the cursor's image(s) data in |
||||||
|
* sequence. |
||||||
|
*/ |
||||||
|
public IntBuffer getImagesData() { |
||||||
|
return imagesData; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Queries the cursor's delay for each frame. |
||||||
|
* @return An {@link IntBuffer} containing the cursor's delay in |
||||||
|
* sequence. The delay is expressed in milliseconds. |
||||||
|
*/ |
||||||
|
public IntBuffer getImagesDelay() { |
||||||
|
return imagesDelay; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Queries the number of images contained in the cursor. Static cursors should |
||||||
|
* contain only 1 image. |
||||||
|
* @return The number of image(s) composing the cursor. 1 if the cursor is |
||||||
|
* static. |
||||||
|
*/ |
||||||
|
public int getNumImages() { |
||||||
|
return numImages; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Queries the cursor's width. Note that |
||||||
|
* the coordinate system is the same as OpenGL. 0, 0 being lower left. |
||||||
|
* @return the width of the cursor in pixel. |
||||||
|
*/ |
||||||
|
public int getWidth() { |
||||||
|
return width; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Queries the cursor's X hotspot coordinate. Note that |
||||||
|
* the coordinate system is the same as OpenGL. 0, 0 being lower left. |
||||||
|
* @return the coordinate on the cursor's X axis where the hotspot is located. |
||||||
|
*/ |
||||||
|
public int getXHotSpot() { |
||||||
|
return xHotSpot; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Queries the cursor's Y hotspot coordinate. Note that |
||||||
|
* the coordinate system is the same as OpenGL. 0, 0 being lower left. |
||||||
|
* @return the coordinate on the cursor's Y axis where the hotspot is located. |
||||||
|
*/ |
||||||
|
public int getYHotSpot() { |
||||||
|
return yHotSpot; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the cursor's height. |
||||||
|
* @param height The height of the cursor in pixels. Note that all images |
||||||
|
* in a cursor have to be the same size. |
||||||
|
*/ |
||||||
|
public void setHeight(int height) { |
||||||
|
this.height = height; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the cursor's image(s) data. Each image data should be consecutively |
||||||
|
* stored in the {@link IntBuffer} if more tha one image is contained in the |
||||||
|
* cursor. |
||||||
|
* @param imagesData the cursor's image(s) data. Each image data should be consecutively |
||||||
|
* stored in the {@link IntBuffer} if more than one image is contained in the |
||||||
|
* cursor. |
||||||
|
*/ |
||||||
|
public void setImagesData(IntBuffer imagesData) { |
||||||
|
this.imagesData = imagesData; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the cursor image delay for each frame of an animated cursor. If the |
||||||
|
* cursor has no animation and consist of only 1 image, null is expected. |
||||||
|
* @param imagesDelay |
||||||
|
*/ |
||||||
|
public void setImagesDelay(IntBuffer imagesDelay) { |
||||||
|
this.imagesDelay = imagesDelay; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the number of images in the cursor. |
||||||
|
* @param numImages number of images in the cursor. |
||||||
|
*/ |
||||||
|
public void setNumImages(int numImages) { |
||||||
|
this.numImages = numImages; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the cursor's width. |
||||||
|
* @param width The width of the cursor in pixels. Note that all images |
||||||
|
* in a cursor have to be the same size. |
||||||
|
*/ |
||||||
|
public void setWidth(int width) { |
||||||
|
this.width = width; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the cursor's X coordinate for its hotspot. |
||||||
|
* @param xHotSpot the cursor's X axis coordinate for its hotspot. Note that |
||||||
|
* the coordinate system is the same as OpenGL. 0, 0 being lower left. |
||||||
|
*/ |
||||||
|
public void setxHotSpot(int xHotSpot) { |
||||||
|
this.xHotSpot = xHotSpot; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the cursor's Y axis coordinate for its hotspot. |
||||||
|
* @param yHotSpot the cursor's Y axis coordinate for its hotspot. Note that |
||||||
|
* the coordinate system is the same as OpenGL. 0, 0 being lower left. |
||||||
|
*/ |
||||||
|
public void setyHotSpot(int yHotSpot) { |
||||||
|
this.yHotSpot = yHotSpot; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue