Parcourir la source

- move cursor loader to desktop package due to awt dependence

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9926 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
nor..67 il y a 13 ans
Parent
commit
fb2412d1b1

+ 739 - 0
engine/src/desktop/com/jme3/cursors/plugins/CursorLoader.java

@@ -0,0 +1,739 @@
+/*
+ * Copyright (c) 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 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 = false;
+        isAni = false;
+
+        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
+                                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);
+                                        // 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 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, 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, int curType) {
+            // cursor type
+            // 0 - Undefined (an array of images inside an ICO)
+            // 1 - ICO
+            // 2 - CUR
+            IntBuffer singleCursor = null;
+            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.
+                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);
+            }
+
+            int count;
+            if (multIcons) {
+                bwidth = 32;
+                bheight = 32;
+                count = 1;
+            } else {
+                count = cursors.size();
+            }
+            // put the image in the IntBuffer
+            data = BufferUtils.createIntBuffer(bwidth * bheight);
+            imgDelay = BufferUtils.createIntBuffer(bi.length);
+            for (int i = 0; i < count; i++) {
+                data.put(cursors.get(i));
+                if (delay > 0) {
+                    imgDelay.put(delay);
+                }
+            }
+            width = bwidth;
+            height = bheight;
+            xHotSpot = hsX;
+            yHotSpot = hsY;
+            numImages = count;
+            data.rewind();
+            if (imgDelay != null) {
+                imgDelay.rewind();
+            }
+        }
+
+        private void addFrame(byte[] imgData, int rate, int jiffy, int width, int height, int numSeq) 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. 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 (data == null) {
+                if (numSeq > numImages) {
+                    data = BufferUtils.createIntBuffer(this.width * this.height * numSeq);
+                } else {
+                    data = BufferUtils.createIntBuffer(this.width * this.height * numImages);
+                }
+                data.put(cid.data);
+            } else {
+                data.put(cid.data);
+            }
+            if (imgDelay == null && (numImages > 1 || numSeq > 1)) {
+                if (numSeq > numImages) {
+                    imgDelay = BufferUtils.createIntBuffer(numSeq);
+                } else {
+                    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 && 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]);
+                    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;
+                    }
+                    addFrame(frame, frRate, jiffy, width, height, 0);
+//                    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();
+        }
+    }
+}

+ 182 - 0
engine/src/desktop/com/jme3/cursors/plugins/JmeCursor.java

@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 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.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;
+    }
+
+
+}