View Javadoc
1   package org.newdawn.slick;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.nio.IntBuffer;
6   
7   import org.lwjgl.BufferUtils;
8   import org.newdawn.slick.opengl.EmptyImageData;
9   import org.newdawn.slick.opengl.ImageData;
10  import org.newdawn.slick.opengl.InternalTextureLoader;
11  import org.newdawn.slick.opengl.Texture;
12  import org.newdawn.slick.opengl.TextureImpl;
13  import org.newdawn.slick.opengl.pbuffer.GraphicsFactory;
14  import org.newdawn.slick.opengl.renderer.Renderer;
15  import org.newdawn.slick.opengl.renderer.SGL;
16  import org.newdawn.slick.util.FastTrig;
17  import org.newdawn.slick.util.Log;
18  
19  import javax.annotation.Nonnull;
20  import javax.annotation.Nullable;
21  
22  /**
23   * An image loaded from a file and renderable to the canvas.
24   *
25   * @author kevin
26   * @author liamzebedee #drawEmbedded(float, float, float, float, byte)
27   *
28   */
29  public class Image implements Renderable {
30  
31  
32      /**
33       * Get the maximum size of an image supported by the underlying
34       * hardware.
35       *
36       * @return The maximum size of the textures supported by the underlying
37       * hardware.
38       */
39      public static final int getMaxSingleImageSize() {
40          IntBuffer buffer = BufferUtils.createIntBuffer(16);
41          GL.glGetInteger(SGL.GL_MAX_TEXTURE_SIZE, buffer);
42          return buffer.get(0);
43      }
44  
45      /**
46       * Creates an image intended for use with offscreen rendering. Only one
47       * texture is created (the FBO/PBuffer-bound texture which will be used
48       * internally). This replaces the old way of offscreen rendering, using
49       * <tt>new Image(width, height)</tt>.
50       *
51       * @param width
52       *            the width of the offscreen image
53       * @param height
54       *            the height of the offscreen image
55       * @param filter
56       *            the desired filtering (FILTER_NEAREST or FILTER_LINEAR)
57       * @return a new Image prepared for use with getGraphics()
58       * @throws SlickException
59       *             if there was a problem constructing the offscreen image
60       */
61      @Nonnull
62      private static Image createOffscreenImage(int width, int height, int filter) throws SlickException {
63          // this is a bit hackish; ideally FBO/Image should be restructured into
64          // a more OpenGL-like design...
65          // but that would introduce a major overhaul of the library
66          Image i = new Image();
67          i.width = width;
68          i.height = height;
69          i.filter = filter;
70          i.inited = true; // so that initImpl() only gets called once
71          i.getGraphics(); // will call Image.setTexture, which calls reinit
72          return i;
73      }
74  
75      /**
76       * Creates an image intended for use with offscreen rendering with linear
77       * filtering. Only one texture is created (the FBO/PBuffer-bound texture
78       * which will be used internally). This replaces the old way of offscreen
79       * rendering, using <tt>new Image(width, height)</tt>.
80       *
81       * @param width
82       *            the width of the offscreen image
83       * @param height
84       *            the height of the offscreen image
85       * @return a new Image prepared for use with getGraphics()
86       * @throws SlickException
87       *             if there was a problem constructing the offscreen image
88       */
89      @Nonnull
90      public static Image createOffscreenImage(int width, int height) throws SlickException {
91          return createOffscreenImage(width, height, Image.FILTER_LINEAR);
92      }
93  
94      /** The top left corner identifier */
95      private static final int TOP_LEFT = 0;
96      /** The top right corner identifier */
97      private static final int TOP_RIGHT = 1;
98      /** The bottom right corner identifier */
99      private static final int BOTTOM_RIGHT = 2;
100     /** The bottom left corner identifier */
101     private static final int BOTTOM_LEFT = 3;
102 
103     /** The renderer to use for all GL operations. */
104     private static final SGL GL = Renderer.get();
105 
106     /** The sprite sheet currently in use. */
107     @Nullable
108     private static Texture inUse;
109     /** Use Linear Filtering (same as SGL.GL_LINEAR) */
110     private static final int FILTER_LINEAR = SGL.GL_LINEAR;
111     /** Use Nearest Filtering (same as SGL.GL_NEAREST) */
112     public static final int FILTER_NEAREST = SGL.GL_NEAREST;
113 
114     /** The OpenGL texture for this image. */
115     @Nullable
116     private Texture texture;
117     /** The width of the image. */
118     private int width;
119     /** The height of the image. */
120     private int height;
121     /** The texture coordinate width to use to find our image. */
122     private float textureWidth;
123     /** The texture coordinate height to use to find our image. */
124     private float textureHeight;
125     /** The x texture offset to use to find our image. */
126     private float textureOffsetX;
127     /** The y texture offset to use to find our image. */
128     private float textureOffsetY;
129     /** Angle to rotate the image to, in degrees. */
130     private float angle;
131     /** The alpha to draw the image at. */
132     private float alpha = 1.0f;
133     /** The name given for the image. */
134     private String ref;
135     /** True if this image's state has been initialised */
136     private boolean inited = false;
137     /** A pixelData holding the pixel data if it's been read for this texture */
138     @Nullable
139     private byte[] pixelData;
140     /** True if the image has been destroyed */
141     private boolean destroyed;
142 
143     /** The x coordinate of the centre of rotation */
144     private float centerX;
145     /** The y coordinate of the centre of rotation */
146     private float centerY;
147 
148     /** A meaningful name provided by the user of the image to tag it */
149     private String name;
150 
151     /** The colours for each of the corners */
152     private Color[] corners;
153     /** The OpenGL max filter */
154     private int filter = FILTER_LINEAR;
155 
156     /**
157      * Create a texture as a copy of another
158      *
159      * @param other The other texture to copy
160      */
161     Image(@Nonnull Image other) {
162         this.width = other.getWidth();
163         this.height = other.getHeight();
164         this.texture = other.texture;
165         this.textureWidth = other.textureWidth;
166         this.textureHeight = other.textureHeight;
167         this.ref = other.ref;
168         this.textureOffsetX = other.textureOffsetX;
169         this.textureOffsetY = other.textureOffsetY;
170 
171         centerX = width / 2f;
172         centerY = height / 2f;
173         inited = true;
174     }
175 
176     /**
177      * Cloning constructor - only used internally.
178      */
179     Image() {
180     }
181 
182     /**
183      * Creates an image using the specified texture
184      *
185      * @param texture
186      *            The texture to use
187      */
188     public Image(@Nonnull Texture texture) {
189         this.texture = texture;
190         ref = texture.toString();
191         clampTexture();
192     }
193 
194     /**
195      * Create an image based on a file at the specified location
196      *
197      * @param ref
198      *            The location of the image file to load
199      * @throws SlickException
200      *             Indicates a failure to load the image
201      */
202     public Image(String ref) throws SlickException  {
203         this(ref, false);
204     }
205 
206     /**
207      * Create an image based on a file at the specified location
208      *
209      * @param ref The location of the image file to load
210      * @param trans The color to be treated as transparent
211      * @throws SlickException Indicates a failure to load the image
212      */
213     public Image(String ref, Color trans) throws SlickException  {
214         this(ref, false, FILTER_LINEAR, trans);
215     }
216 
217 
218     /**
219      * Create an image based on a file at the specified location
220      *
221      * @param ref The location of the image file to load
222      * @param filter the filtering method to use when scaling this image
223      * @throws SlickException Indicates a failure to load the image
224      */
225     public Image(String ref, int filter) throws SlickException {
226         this(ref, false, filter);
227     }
228 
229     /**
230      * Create an image based on a file at the specified location
231      *
232      * @param ref The location of the image file to load
233      * @param flipped True if the image should be flipped on the y-axis on load
234      * @throws SlickException Indicates a failure to load the image
235      */
236     private Image(String ref, boolean flipped) throws SlickException {
237         this(ref, flipped, FILTER_LINEAR);
238     }
239 
240     /**
241      * Create an image based on a file at the specified location
242      *
243      * @param ref The location of the image file to load
244      * @param flipped True if the image should be flipped on the y-axis on load
245      * @param filter The filtering method to use when scaling this image
246      * @throws SlickException Indicates a failure to load the image
247      */
248     private Image(String ref, boolean flipped, int filter) throws SlickException {
249         this(ref, flipped, filter, null);
250     }
251 
252     /**
253      * Create an image based on a file at the specified location
254      *
255      * @param ref The location of the image file to load
256      * @param flipped True if the image should be flipped on the y-axis on load
257      * @param f The filtering method to use when scaling this image
258      * @param transparent The color to treat as transparent
259      * @throws SlickException Indicates a failure to load the image
260      */
261     public Image(String ref, boolean flipped, int f, @Nullable Color transparent) throws SlickException {
262         this.filter = f;
263         try {
264             this.ref = ref;
265             int[] trans = null;
266             if (transparent != null) {
267                 trans = new int[3];
268                 trans[0] = (int) (transparent.r * 255);
269                 trans[1] = (int) (transparent.g * 255);
270                 trans[2] = (int) (transparent.b * 255);
271             }
272             texture = InternalTextureLoader.get().getTexture(ref, flipped, filter, trans);
273         } catch (IOException e) {
274             Log.error(e);
275             throw new SlickException("Failed to load image from: "+ref, e);
276         }
277     }
278 
279     /**
280      * Set the image filtering to be used. Note that this will also affect any
281      * image that was derived from this one (i.e. sub-images etc)
282      *
283      * @param f The filtering mode to use
284      */
285     public void setFilter(int f) {
286         this.filter = f;
287 
288         texture.bind();
289         GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_MIN_FILTER, filter);
290         GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_MAG_FILTER, filter);
291     }
292 
293     /**
294      * Create an empty image
295      *
296      * @param width The width of the image
297      * @param height The height of the image
298      * @throws SlickException Indicates a failure to create the underlying resource
299      */
300     public Image(int width, int height) {
301         this(width, height, FILTER_NEAREST);
302     }
303 
304     /**
305      * Create an empty image
306      *
307      * @param width The width of the image
308      * @param height The height of the image
309      * @param f The filter to apply to scaling the new image
310      * @throws SlickException Indicates a failure to create the underlying resource
311      */
312     private Image(int width, int height, int f) {
313         this(new EmptyImageData(width, height), f);
314     }
315 
316     /**
317      * Create an image based on a file at the specified location
318      *
319      * @param in The input stream to read the image from
320      * @param ref The name that should be assigned to the image
321      * @param flipped True if the image should be flipped on the y-axis  on load
322      * @throws SlickException Indicates a failure to load the image
323      */
324     public Image(@Nonnull InputStream in, String ref, boolean flipped) throws SlickException {
325         this(in, ref, flipped, FILTER_LINEAR);
326     }
327 
328     /**
329      * Create an image based on a file at the specified location
330      *
331      * @param in The input stream to read the image from
332      * @param ref The name that should be assigned to the image
333      * @param flipped True if the image should be flipped on the y-axis on load
334      * @param filter The filter to use when scaling this image
335      * @throws SlickException Indicates a failure to load the image
336      */
337     private Image(@Nonnull InputStream in, String ref, boolean flipped, int filter) throws SlickException {
338         load(in, ref, flipped, filter, null);
339     }
340 
341     /**
342      * Create an image from a pixelData of pixels
343      *
344      * @param buffer The pixelData to use to create the image
345      */
346     Image(ImageBuffer buffer) {
347         this(buffer, FILTER_LINEAR);
348         TextureImpl.bindNone();
349     }
350 
351     /**
352      * Create an image from a pixelData of pixels
353      *
354      * @param buffer The pixelData to use to create the image
355      * @param filter The filter to use when scaling this image
356      */
357     Image(ImageBuffer buffer, int filter) {
358         this((ImageData) buffer, filter);
359         TextureImpl.bindNone();
360     }
361 
362     /**
363      * Create an image from a image data source
364      *
365      * @param data The pixelData to use to create the image
366      */
367     public Image(@Nonnull ImageData data) {
368         this(data, FILTER_LINEAR);
369     }
370 
371     /**
372      * Create an image from a image data source. Note that this method uses
373      *
374      * @param data The pixelData to use to create the image
375      * @param f The filter to use when scaling this image
376      */
377     private Image(@Nonnull ImageData data, int f) {
378         try {
379             this.filter = f;
380             texture = InternalTextureLoader.get().getTexture(data, this.filter);
381             ref = texture.toString();
382         } catch (IOException e) {
383             Log.error(e);
384         }
385     }
386 
387     /**
388      * Get the OpenGL image filter in use
389      *
390      * @return The filter for magnification
391      */
392     public int getFilter() {
393         return filter;
394     }
395 
396     /**
397      * Get the reference to the resource this image was loaded from, if any. Note that
398      * this can be null in the cases where an image was programatically generated.
399      *
400      * @return The reference to the resource the reference was loaded from
401      */
402     public String getResourceReference() {
403         return ref;
404     }
405 
406     /**
407      * Set the filter to apply when drawing this image
408      *
409      * @param r The red component of the filter colour
410      * @param g The green component of the filter colour
411      * @param b The blue component of the filter colour
412      * @param a The alpha component of the filter colour
413      */
414     public void setImageColor(float r, float g, float b, float a) {
415         setColor(TOP_LEFT, r, g, b, a);
416         setColor(TOP_RIGHT, r, g, b, a);
417         setColor(BOTTOM_LEFT, r, g, b, a);
418         setColor(BOTTOM_RIGHT, r, g, b, a);
419     }
420 
421     /**
422      * Set the filter to apply when drawing this image
423      *
424      * @param r The red component of the filter colour
425      * @param g The green component of the filter colour
426      * @param b The blue component of the filter colour
427      */
428     public void setImageColor(float r, float g, float b) {
429         setColor(TOP_LEFT, r, g, b);
430         setColor(TOP_RIGHT, r, g, b);
431         setColor(BOTTOM_LEFT, r, g, b);
432         setColor(BOTTOM_RIGHT, r, g, b);
433     }
434 
435     /**
436      * Set the color of the given corner when this image is rendered. This is
437      * useful lots of visual effect but especially light maps
438      *
439      * @param corner The corner identifier for the corner to be set
440      * @param r The red component value to set (between 0 and 1)
441      * @param g The green component value to set (between 0 and 1)
442      * @param b The blue component value to set (between 0 and 1)
443      * @param a The alpha component value to set (between 0 and 1)
444      */
445     void setColor(int corner, float r, float g, float b, float a) {
446         if (corners == null) {
447             corners = new Color[] {new Color(1,1,1,1f),new Color(1,1,1,1f), new Color(1,1,1,1f), new Color(1,1,1,1f)};
448         }
449 
450         corners[corner].r = r;
451         corners[corner].g = g;
452         corners[corner].b = b;
453         corners[corner].a = a;
454     }
455 
456     /**
457      * Set the color of the given corner when this image is rendered. This is
458      * useful lots of visual effect but especially light maps
459      *
460      * @param corner The corner identifier for the corner to be set
461      * @param r The red component value to set (between 0 and 1)
462      * @param g The green component value to set (between 0 and 1)
463      * @param b The blue component value to set (between 0 and 1)
464      */
465     void setColor(int corner, float r, float g, float b) {
466         if (corners == null) {
467             corners = new Color[] {new Color(1,1,1,1f),new Color(1,1,1,1f), new Color(1,1,1,1f), new Color(1,1,1,1f)};
468         }
469 
470         corners[corner].r = r;
471         corners[corner].g = g;
472         corners[corner].b = b;
473     }
474 
475     /**
476      * Clamp the loaded texture to it's edges
477      */
478     void clampTexture() {
479         if (GL.canTextureMirrorClamp()) {
480             GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT);
481             GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT);
482         } else {
483             GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_CLAMP);
484             GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_CLAMP);
485         }
486     }
487 
488     /**
489      * Give this image a meaningful tagging name. Can be used as user data/identifier
490      * for the image.
491      *
492      * @param name The name to assign the image
493      */
494     public void setName(String name) {
495         this.name = name;
496     }
497 
498     /**
499      * Return a meaningful tagging name that has been assigned to this image.
500      *
501      * @return A name or null if the name hasn't been set
502      */
503     public String getName() {
504         return name;
505     }
506 
507     /**
508      * Get a graphics context that can be used to draw to this image
509      *
510      * @return The graphics context used to render to this image
511      * @throws SlickException Indicates a failure to create a graphics context
512      */
513     Graphics getGraphics() throws SlickException {
514         return GraphicsFactory.getGraphicsForImage(this);
515     }
516 
517     /**
518      * Load the image
519      *
520      * @param in The input stream to read the image from
521      * @param ref The name that should be assigned to the image
522      * @param flipped True if the image should be flipped on the y-axis  on load
523      * @param f The filter to use when scaling this image
524      * @param transparent The color to treat as transparent
525      * @throws SlickException Indicates a failure to load the image
526      */
527     private void load(@Nonnull InputStream in, String ref, boolean flipped, int f, @Nullable Color transparent) throws SlickException {
528         this.filter = f;
529 
530         try {
531             this.ref = ref;
532             int[] trans = null;
533             if (transparent != null) {
534                 trans = new int[3];
535                 trans[0] = (int) (transparent.r * 255);
536                 trans[1] = (int) (transparent.g * 255);
537                 trans[2] = (int) (transparent.b * 255);
538             }
539             texture = InternalTextureLoader.get().getTexture(in, ref, flipped, filter, trans);
540         } catch (IOException e) {
541             Log.error(e);
542             throw new SlickException("Failed to load image from: "+ref, e);
543         }
544     }
545 
546     /**
547      * Bind to the texture of this image
548      */
549     public void bind() {
550         texture.bind();
551     }
552 
553     /**
554      * Reinitialise internal data and flushes the cached pixel data.
555      */
556     void reinit() {
557         inited = false;
558         flushPixelData();
559         init();
560     }
561 
562     /**
563      * Initialise internal data
564      */
565     final void init() {
566         if (inited) {
567             return;
568         }
569 
570         inited = true;
571         if (texture != null) {
572             width = texture.getImageWidth();
573             height = texture.getImageHeight();
574             textureOffsetX = 0;
575             textureOffsetY = 0;
576             textureWidth = texture.getWidth();
577             textureHeight = texture.getHeight();
578         }
579 
580         initImpl();
581 
582         centerX = width / 2f;
583         centerY = height / 2f;
584     }
585 
586     /**
587      * Hook for subclasses to perform initialisation
588      */
589     void initImpl() {
590 
591     }
592 
593     /**
594      * Draw this image at the current location
595      */
596     public void draw() {
597         draw(0,0);
598     }
599 
600     /**
601      * Draw the image based on it's center
602      *
603      * @param x The x coordinate to place the image's center at
604      * @param y The y coordinate to place the image's center at
605      */
606     public void drawCentered(float x, float y) {
607         draw(x-(getWidth()/2f),y-(getHeight()/2f));
608     }
609 
610     /**
611      * Draw this image at the specified location
612      *
613      * @param x The x location to draw the image at
614      * @param y The y location to draw the image at
615      */
616     public void draw(float x, float y) {
617         init();
618         draw(x,y,width,height);
619     }
620 
621     /**
622      * Draw this image at the specified location
623      *
624      * @param x The x location to draw the image at
625      * @param y The y location to draw the image at
626      * @param filter The color to filter with when drawing
627      */
628     public void draw(float x, float y, Color filter) {
629         init();
630         draw(x,y,width,height, filter);
631     }
632 
633     /**
634      * Unlike the other drawEmbedded methods, this allows for the embedded image
635      * to be rotated. This is done by applying a rotation transform to each
636      * vertex of the image. This ignores getRotation but depends on the
637      * center x/y (scaled accordingly to the new width/height).
638      *
639      * @param x the x to render the image at
640      * @param y the y to render the image at
641      * @param width the new width to render the image
642      * @param height the new height to render the image
643      * @param rotation the rotation to render the image in degrees, using getCenterOfRotationX/Y
644      */
645     void drawEmbedded(float x, float y, float width, float height, float rotation) {
646         if (rotation==0) {
647             drawEmbedded(x, y, width, height);
648             return;
649         }
650         init();
651         float scaleX = width/this.width;
652         float scaleY = height/this.height;
653 
654         float cx = getCenterOfRotationX()*scaleX;
655         float cy = getCenterOfRotationY()*scaleY;
656 
657         float p1x = -cx;
658         float p1y = -cy;
659         float p2x = width - cx;
660         float p2y = -cy;
661         float p3x = width - cx;
662         float p3y = height - cy;
663         float p4x = -cx;
664         float p4y = height - cy;
665 
666         double rad = Math.toRadians(rotation);
667         final float cos = (float) FastTrig.cos(rad);
668         final float sin = (float) FastTrig.sin(rad);
669 
670         float tx = getTextureOffsetX();
671         float ty = getTextureOffsetY();
672         float tw = getTextureWidth();
673         float th = getTextureHeight();
674 
675         float x1 = (cos * p1x - sin * p1y) + cx; // TOP LEFT
676         float y1 = (sin * p1x + cos * p1y) + cy;
677         float x2 = (cos * p4x - sin * p4y) + cx; // BOTTOM LEFT
678         float y2 = (sin * p4x + cos * p4y) + cy;
679         float x3 = (cos * p3x - sin * p3y) + cx; // BOTTOM RIGHT
680         float y3 = (sin * p3x + cos * p3y) + cy;
681         float x4 = (cos * p2x - sin * p2y) + cx; // TOP RIGHT
682         float y4 = (sin * p2x + cos * p2y) + cy;
683         if (corners == null) {
684             GL.glTexCoord2f(tx, ty);
685             GL.glVertex3f(x+x1, y+y1, 0);
686             GL.glTexCoord2f(tx, ty + th);
687             GL.glVertex3f(x+x2, y+y2, 0);
688             GL.glTexCoord2f(tx + tw, ty + th);
689             GL.glVertex3f(x+x3, y+y3, 0);
690             GL.glTexCoord2f(tx + tw, ty);
691             GL.glVertex3f(x+x4, y+y4, 0);
692         } else {
693             corners[TOP_LEFT].bind();
694             GL.glTexCoord2f(tx, ty);
695             GL.glVertex3f(x+x1, y+y1, 0);
696             corners[BOTTOM_LEFT].bind();
697             GL.glTexCoord2f(tx, ty + th);
698             GL.glVertex3f(x+x2, y+y2, 0);
699             corners[BOTTOM_RIGHT].bind();
700             GL.glTexCoord2f(tx + tw, ty + th);
701             GL.glVertex3f(x+x3, y+y3, 0);
702             corners[TOP_RIGHT].bind();
703             GL.glTexCoord2f(tx + tw, ty);
704             GL.glVertex3f(x+x4, y+y4, 0);
705         }
706     }
707 
708     /**
709      * Draw this image as part of a collection of images (getRotation is ignored).
710      *
711      * @param x The x location to draw the image at
712      * @param y The y location to draw the image at
713      */
714     public void drawEmbedded(float x,float y) {
715         drawEmbedded(x, y, getWidth(), getHeight());
716     }
717 
718     /**
719      * Draw this image as part of a collection of images (getRotation is ignored).
720      *
721      * @param x The x location to draw the image at
722      * @param y The y location to draw the image at
723      * @param width The width to render the image at
724      * @param height The height to render the image at
725      */
726     public void drawEmbedded(float x,float y,float width,float height) {
727         init();
728 
729         if (corners == null) {
730             GL.glTexCoord2f(textureOffsetX, textureOffsetY);
731             GL.glVertex3f(x, y, 0);
732             GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight);
733             GL.glVertex3f(x, y + height, 0);
734             GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY
735                     + textureHeight);
736             GL.glVertex3f(x + width, y + height, 0);
737             GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY);
738             GL.glVertex3f(x + width, y, 0);
739         } else {
740             corners[TOP_LEFT].bind();
741             GL.glTexCoord2f(textureOffsetX, textureOffsetY);
742             GL.glVertex3f(x, y, 0);
743             corners[BOTTOM_LEFT].bind();
744             GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight);
745             GL.glVertex3f(x, y + height, 0);
746             corners[BOTTOM_RIGHT].bind();
747             GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY
748                     + textureHeight);
749             GL.glVertex3f(x + width, y + height, 0);
750             corners[TOP_RIGHT].bind();
751             GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY);
752             GL.glVertex3f(x + width, y, 0);
753         }
754     }
755 
756     /**
757      * Draw a section of this image at a particular location and scale on the screen, while this
758      * is image is "in use", i.e. between calls to startUse and endUse  (rotation is ignored).
759      *
760      * @param x The x position to draw the image
761      * @param y The y position to draw the image
762      * @param x2 The x position of the bottom right corner of the drawn image
763      * @param y2 The y position of the bottom right corner of the drawn image
764      * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image)
765      * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image)
766      * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
767      * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
768      */
769     void drawEmbedded(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2) {
770         drawEmbedded(x,y,x2,y2,srcx,srcy,srcx2,srcy2,null);
771     }
772 
773     /**
774      * Draw a section of this image at a particular location and scale on the screen, while this
775      * is image is "in use", i.e. between calls to startUse and endUse  (rotation is ignored).
776      *
777      * @param x The x position to draw the image
778      * @param y The y position to draw the image
779      * @param x2 The x position of the bottom right corner of the drawn image
780      * @param y2 The y position of the bottom right corner of the drawn image
781      * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image)
782      * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image)
783      * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
784      * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
785      * @param filter The colour filter to apply when drawing
786      */
787     void drawEmbedded(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2, @Nullable Color filter) {
788         init();
789         if (filter != null) {
790             filter.bind();
791         }
792 
793         float mywidth = x2 - x;
794         float myheight = y2 - y;
795         float texwidth = srcx2 - srcx;
796         float texheight = srcy2 - srcy;
797 
798         float newTextureOffsetX = (((srcx) / (width)) * textureWidth)
799                 + textureOffsetX;
800         float newTextureOffsetY = (((srcy) / (height)) * textureHeight)
801                 + textureOffsetY;
802         float newTextureWidth = ((texwidth) / (width))
803                 * textureWidth;
804         float newTextureHeight = ((texheight) / (height))
805                 * textureHeight;
806 
807         GL.glTexCoord2f(newTextureOffsetX, newTextureOffsetY);
808         GL.glVertex3f(x,y, 0.0f);
809         GL.glTexCoord2f(newTextureOffsetX, newTextureOffsetY
810                 + newTextureHeight);
811         GL.glVertex3f(x,(y + myheight), 0.0f);
812         GL.glTexCoord2f(newTextureOffsetX + newTextureWidth,
813                 newTextureOffsetY + newTextureHeight);
814         GL.glVertex3f((x + mywidth),(y + myheight), 0.0f);
815         GL.glTexCoord2f(newTextureOffsetX + newTextureWidth,
816                 newTextureOffsetY);
817         GL.glVertex3f((x + mywidth),y, 0.0f);
818     }
819 
820     /**
821      * Get the x offset in texels into the source texture (normalized
822      * value between 0.0 and 1.0).
823      *
824      * @return The x offset
825      */
826     public float getTextureOffsetX() {
827         init();
828 
829         return textureOffsetX;
830     }
831 
832     /**
833      * Get the y offset in texels into the source texture (normalized
834      * value between 0.0 and 1.0).
835      *
836      * @return The y offset
837      */
838     public float getTextureOffsetY() {
839         init();
840 
841         return textureOffsetY;
842     }
843 
844     /**
845      * Get the width in texels into the source texture (normalized
846      * value between 0.0 and 1.0, i.e. Texture.getWidth).
847      *
848      * @return The width
849      */
850     public float getTextureWidth() {
851         init();
852 
853         return textureWidth;
854     }
855 
856     /**
857      * Get the height in texels into the source texture (normalized
858      * value between 0.0 and 1.0, i.e. Texture.getHeight)
859      *
860      * @return The height
861      */
862     public float getTextureHeight() {
863         init();
864 
865         return textureHeight;
866     }
867 
868     /**
869      * Draw the image with a given scale
870      *
871      * @param x The x position to draw the image at
872      * @param y The y position to draw the image at
873      * @param scale The scaling to apply
874      */
875     public void draw(float x,float y,float scale) {
876         init();
877         draw(x,y,width*scale,height*scale,Color.white);
878     }
879 
880     /**
881      * Draw the image with a given scale
882      *
883      * @param x The x position to draw the image at
884      * @param y The y position to draw the image at
885      * @param scale The scaling to apply
886      * @param filter The colour filter to adapt the image with
887      */
888     public void draw(float x,float y,float scale,Color filter) {
889         init();
890         draw(x,y,width*scale,height*scale,filter);
891     }
892 
893     /**
894      * Draw this image at a specified location and size
895      *
896      * @param x
897      *            The x location to draw the image at
898      * @param y
899      *            The y location to draw the image at
900      * @param width
901      *            The width to render the image at
902      * @param height
903      *            The height to render the image at
904      */
905     void draw(float x, float y, float width, float height) {
906         init();
907         draw(x,y,width,height,Color.white);
908     }
909 
910     /**
911      * Draw this image at a specified location and size
912      *
913      * @param x The x location to draw the image at
914      * @param y The y location to draw the image at
915      * @param hshear The amount to shear the bottom points by horizontally
916      * @param vshear The amount to shear the right points by vertically
917      */
918     public void drawSheared(float x,float y, float hshear, float vshear) {
919         this.drawSheared(x, y, hshear, vshear, Color.white);
920     }
921     /**
922      * Draw this image at a specified location and size. The center of rotation
923      * is <i>not</i> scaled according to the shear.
924      *
925      * @param x The x location to draw the image at
926      * @param y The y location to draw the image at
927      * @param hshear The amount to shear the bottom points by horizontally
928      * @param vshear The amount to shear the right points by vertically
929      * @param filter The colour filter to apply
930      */
931     void drawSheared(float x, float y, float hshear, float vshear, @Nullable Color filter) {
932         init();
933         if (alpha != 1) {
934             if (filter == null) {
935                 filter = Color.white;
936             }
937 
938             filter = new Color(filter);
939             filter.a *= alpha;
940         }
941         if (filter != null) {
942             filter.bind();
943         }
944 
945         texture.bind();
946 
947         GL.glTranslatef(x, y, 0);
948         if (angle != 0) {
949             GL.glTranslatef(centerX, centerY, 0.0f);
950             GL.glRotatef(angle, 0.0f, 0.0f, 1.0f);
951             GL.glTranslatef(-centerX, -centerY, 0.0f);
952         }
953 
954         GL.glBegin(SGL.GL_QUADS);
955         GL.glTexCoord2f(textureOffsetX, textureOffsetY);
956         GL.glVertex3f(0, 0, 0);
957         GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight);
958         GL.glVertex3f(hshear, height, 0);
959         GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY
960                 + textureHeight);
961         GL.glVertex3f(width + hshear, height + vshear, 0);
962         GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY);
963         GL.glVertex3f(width, vshear, 0);
964         GL.glEnd();
965 
966         if (angle != 0) {
967             GL.glTranslatef(centerX, centerY, 0.0f);
968             GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f);
969             GL.glTranslatef(-centerX, -centerY, 0.0f);
970         }
971         GL.glTranslatef(-x, -y, 0);
972     }
973 
974     /**
975      * Draw this image at a specified location and size
976      *
977      * @param x The x location to draw the image at
978      * @param y The y location to draw the image at
979      * @param width The width to render the image at
980      * @param height The height to render the image at
981      * @param filter The color to filter with while drawing
982      */
983     public void draw(float x,float y,float width,float height, @Nullable Color filter) {
984         init();
985         if (alpha != 1) {
986             if (filter == null) {
987                 filter = Color.white;
988             }
989 
990             filter = new Color(filter);
991             filter.a *= alpha;
992         }
993         if (filter != null) {
994             filter.bind();
995         }
996 
997         float centerX = this.centerX * (width / (float)getWidth());
998         float centerY = this.centerY * (height / (float)getHeight());
999 
1000         texture.bind();
1001 
1002         GL.glTranslatef(x, y, 0);
1003         if (angle != 0) {
1004             GL.glTranslatef(centerX, centerY, 0.0f);
1005             GL.glRotatef(angle, 0.0f, 0.0f, 1.0f);
1006             GL.glTranslatef(-centerX, -centerY, 0.0f);
1007         }
1008 
1009         GL.glBegin(SGL.GL_QUADS);
1010         drawEmbedded(0,0,width,height);
1011         GL.glEnd();
1012 
1013         if (angle != 0) {
1014             GL.glTranslatef(centerX, centerY, 0.0f);
1015             GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f);
1016             GL.glTranslatef(-centerX, -centerY, 0.0f);
1017         }
1018         GL.glTranslatef(-x, -y, 0);
1019     }
1020 
1021     /**
1022      * Draw this image at a specified location and size as a silohette
1023      *
1024      * @param x The x location to draw the image at
1025      * @param y The y location to draw the image at
1026      * @param width The width to render the image at
1027      * @param height The height to render the image at
1028      */
1029     void drawFlash(float x, float y, float width, float height) {
1030         drawFlash(x,y,width,height,Color.white);
1031     }
1032 
1033     /**
1034      * Set the centre of the rotation when applied to this image.
1035      *
1036      * @param x The x coordinate of center of rotation relative to the top left corner of the image
1037      * @param y The y coordinate of center of rotation relative to the top left corner of the image
1038      */
1039     public void setCenterOfRotation(float x, float y) {
1040         init();
1041         centerX = x;
1042         centerY = y;
1043     }
1044 
1045     /**
1046      * Get the x component of the center of rotation of this image
1047      *
1048      * @return The x component of the center of rotation
1049      */
1050     float getCenterOfRotationX() {
1051         init();
1052 
1053         return centerX;
1054     }
1055 
1056     /**
1057      * Get the y component of the center of rotation of this image.
1058      *
1059      * @return The y component of the center of rotation
1060      */
1061     float getCenterOfRotationY() {
1062         init();
1063 
1064         return centerY;
1065     }
1066 
1067     /**
1068      * Draw this image at a specified location and size as a silohette
1069      *
1070      * @param x The x location to draw the image at
1071      * @param y The y location to draw the image at
1072      * @param width The width to render the image at
1073      * @param height The height to render the image at
1074      * @param col The color for the sillohette
1075      */
1076     public void drawFlash(float x,float y,float width,float height, @Nonnull Color col) {
1077         init();
1078 
1079         col.bind();
1080         texture.bind();
1081 
1082         float centerX = this.centerX * (width / (float)getWidth());
1083         float centerY = this.centerY * (height / (float)getHeight());
1084 
1085         if (GL.canSecondaryColor()) {
1086             GL.glEnable(SGL.GL_COLOR_SUM_EXT);
1087             GL.glSecondaryColor3ubEXT((byte)(col.r * 255),
1088                     (byte)(col.g * 255),
1089                     (byte)(col.b * 255));
1090         }
1091 
1092         GL.glTexEnvi(SGL.GL_TEXTURE_ENV, SGL.GL_TEXTURE_ENV_MODE, SGL.GL_MODULATE);
1093 
1094         GL.glTranslatef(x, y, 0);
1095         if (angle != 0) {
1096             GL.glTranslatef(centerX, centerY, 0.0f);
1097             GL.glRotatef(angle, 0.0f, 0.0f, 1.0f);
1098             GL.glTranslatef(-centerX, -centerY, 0.0f);
1099         }
1100 
1101         GL.glBegin(SGL.GL_QUADS);
1102         drawEmbedded(0,0,width,height);
1103         GL.glEnd();
1104 
1105         if (angle != 0) {
1106             GL.glTranslatef(centerX, centerY, 0.0f);
1107             GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f);
1108             GL.glTranslatef(-centerX, -centerY, 0.0f);
1109         }
1110         GL.glTranslatef(-x, -y, 0);
1111 
1112         if (GL.canSecondaryColor()) {
1113             GL.glDisable(SGL.GL_COLOR_SUM_EXT);
1114         }
1115     }
1116 
1117     /**
1118      * Draw this image at a specified location and size in a white silohette
1119      *
1120      * @param x The x location to draw the image at
1121      * @param y The y location to draw the image at
1122      */
1123     public void drawFlash(float x,float y) {
1124         drawFlash(x,y,getWidth(),getHeight());
1125     }
1126 
1127     /**
1128      * Set the angle to rotate this image to.  The angle will be normalized to
1129      * be 0 <= angle < 360.  The image will be rotated around its center.
1130      *
1131      * @param angle The angle to be set (in degrees)
1132      */
1133     public void setRotation(float angle) {
1134         this.angle = angle % 360.0f;
1135     }
1136 
1137     /**
1138      * Get the current angle of rotation for this image.
1139      * The image will be rotated around its center.
1140      *
1141      * @return The current angle (in degrees)
1142      */
1143     public float getRotation() {
1144         return angle;
1145     }
1146 
1147     /**
1148      * Get the alpha value to use when rendering this image
1149      *
1150      * @return The alpha value to use when rendering this image
1151      */
1152     public float getAlpha() {
1153         return alpha;
1154     }
1155 
1156     /**
1157      * Set the alpha value to use when rendering this image
1158      *
1159      * @param alpha The alpha value to use when rendering this image
1160      */
1161     public void setAlpha(float alpha) {
1162         this.alpha = alpha;
1163     }
1164 
1165     /**
1166      * Add the angle provided to the current rotation.  The angle will be normalized to
1167      * be 0 <= angle < 360.  The image will be rotated around its center.
1168      *
1169      * @param angle The angle to add (in degrees)
1170      */
1171     public void rotate(float angle) {
1172         this.angle += angle;
1173         this.angle = this.angle % 360;
1174     }
1175 
1176     /**
1177      * Draw a section of this image at a particular location and scale on the screen
1178      *
1179      * @param x The x position to draw the image
1180      * @param y The y position to draw the image
1181      * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image)
1182      * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image)
1183      * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
1184      * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
1185      */
1186     public void draw(float x, float y, float srcx, float srcy, float srcx2, float srcy2) {
1187         draw(x,y,x+width,y+height,srcx,srcy,srcx2,srcy2);
1188     }
1189 
1190     /**
1191      * Draw a section of this image at a particular location and scale on the screen
1192      *
1193      * @param x The x position to draw the image
1194      * @param y The y position to draw the image
1195      * @param x2 The x position of the bottom right corner of the drawn image
1196      * @param y2 The y position of the bottom right corner of the drawn image
1197      * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image)
1198      * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image)
1199      * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
1200      * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
1201      */
1202     public void draw(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2) {
1203         draw(x,y,x2,y2,srcx,srcy,srcx2,srcy2,Color.white);
1204     }
1205 
1206     /**
1207      * Draw a section of this image at a particular location and scale on the screen.
1208      *
1209      * The center of rotation will be scaled according to the new size of the image.
1210      *
1211      * @param x The x position to draw the image
1212      * @param y The y position to draw the image
1213      * @param x2 The x position of the bottom right corner of the drawn image
1214      * @param y2 The y position of the bottom right corner of the drawn image
1215      * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image)
1216      * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image)
1217      * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
1218      * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image)
1219      * @param filter The colour filter to apply when drawing
1220      */
1221     public void draw(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2, @Nullable Color filter) {
1222         init();
1223 
1224         if (alpha != 1) {
1225             if (filter == null) {
1226                 filter = Color.white;
1227             }
1228 
1229             filter = new Color(filter);
1230             filter.a *= alpha;
1231         }
1232         filter.bind();
1233         texture.bind();
1234 
1235         float centerX = this.centerX * ((x2-x) / (float)getWidth());
1236         float centerY = this.centerY * ((y2-y) / (float)getHeight());
1237 
1238         GL.glTranslatef(x, y, 0);
1239         if (angle != 0) {
1240             GL.glTranslatef(centerX, centerY, 0.0f);
1241             GL.glRotatef(angle, 0.0f, 0.0f, 1.0f);
1242             GL.glTranslatef(-centerX, -centerY, 0.0f);
1243         }
1244 
1245         GL.glBegin(SGL.GL_QUADS);
1246         drawEmbedded(0,0,x2-x,y2-y,srcx,srcy,srcx2,srcy2);
1247         GL.glEnd();
1248 
1249         if (angle != 0) {
1250             GL.glTranslatef(centerX, centerY, 0.0f);
1251             GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f);
1252             GL.glTranslatef(-centerX, -centerY, 0.0f);
1253         }
1254         GL.glTranslatef(-x, -y, 0);
1255 
1256         //        GL.glBegin(SGL.GL_QUADS);
1257         //        drawEmbedded(x,y,x2,y2,srcx,srcy,srcx2,srcy2);
1258         //        GL.glEnd();
1259     }
1260 
1261     /**
1262      * Draw the image in a warper rectangle. The effects this can
1263      * have are many and varied, might be interesting though.
1264      * Note that this won't scale the centerX/centerY according
1265      * to the given points; this should be done before hand with
1266      * setCenterOfRotation.
1267      *
1268      * @param x1 The top left corner x coordinate
1269      * @param y1 The top left corner y coordinate
1270      * @param x2 The bottom left corner x coordinate
1271      * @param y2 The bottom left corner y coordinate
1272      * @param x3 The bottom right corner x coordinate
1273      * @param y3 The bottom right corner y coordinate
1274      * @param x4 The top right corner x coordinate
1275      * @param y4 The top right corner y coordinate
1276      */
1277     public void drawWarped(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
1278         Color.white.bind();
1279         texture.bind();
1280 
1281         GL.glTranslatef(x1, y1, 0);
1282         if (angle != 0) {
1283             GL.glTranslatef(centerX, centerY, 0.0f);
1284             GL.glRotatef(angle, 0.0f, 0.0f, 1.0f);
1285             GL.glTranslatef(-centerX, -centerY, 0.0f);
1286         }
1287 
1288         GL.glBegin(SGL.GL_QUADS);
1289         init();
1290 
1291         GL.glTexCoord2f(textureOffsetX, textureOffsetY);
1292         GL.glVertex3f(0, 0, 0);
1293         GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight);
1294         GL.glVertex3f(x2 - x1, y2 - y1, 0);
1295         GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY
1296                 + textureHeight);
1297         GL.glVertex3f(x3 - x1, y3 - y1, 0);
1298         GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY);
1299         GL.glVertex3f(x4 - x1, y4 - y1, 0);
1300         GL.glEnd();
1301 
1302         if (angle != 0) {
1303             GL.glTranslatef(centerX, centerY, 0.0f);
1304             GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f);
1305             GL.glTranslatef(-centerX, -centerY, 0.0f);
1306         }
1307         GL.glTranslatef(-x1, -y1, 0);
1308     }
1309 
1310     /**
1311      * Get the width of this image (e.g. Texture.getImageWidth)
1312      *
1313      * @return The width of this image
1314      */
1315     public int getWidth() {
1316         init();
1317         return width;
1318     }
1319 
1320     /**
1321      * Get the height of this image (e.g. Texture.getImageHeight)
1322      *
1323      * @return The height of this image
1324      */
1325     public int getHeight() {
1326         init();
1327         return height;
1328     }
1329 
1330     /**
1331      * Get a copy of this image. This is a shallow copy and does not
1332      * duplicate image data -- corner colors, alpha and rotation are
1333      * also not copied.
1334      *
1335      * @return The copy of this image
1336      */
1337     @Nonnull
1338     Image copy() {
1339         init();
1340         return getScaledCopy(width,height);
1341     }
1342 
1343     /**
1344      * Get a sub-part of this image. Note that the create image retains a reference to the
1345      * image data so should anything change it will affect sub-images too.
1346      *
1347      * The center of rotation will be the center of the new sub-image (half width/height).
1348      *
1349      * @param x The x coordinate of the sub-image
1350      * @param y The y coordinate of the sub-image
1351      * @param width The width of the sub-image
1352      * @param height The height of the sub-image
1353      * @return The image represent the sub-part of this image
1354      */
1355     @Nonnull
1356     public Image getSubImage(int x,int y,int width,int height) {
1357         init();
1358 
1359         float newTextureOffsetX = ((x / (float) this.width) * textureWidth) + textureOffsetX;
1360         float newTextureOffsetY = ((y / (float) this.height) * textureHeight) + textureOffsetY;
1361         float newTextureWidth = ((width / (float) this.width) * textureWidth);
1362         float newTextureHeight = ((height / (float) this.height) * textureHeight);
1363 
1364         Image sub = new Image();
1365         sub.inited = true;
1366         sub.filter = this.filter;
1367         sub.texture = this.texture;
1368         sub.ref = ref;
1369 
1370         sub.textureOffsetX = newTextureOffsetX;
1371         sub.textureOffsetY = newTextureOffsetY;
1372         sub.textureWidth = newTextureWidth;
1373         sub.textureHeight = newTextureHeight;
1374 
1375         sub.width = width;
1376         sub.height = height;
1377         sub.centerX = width / 2f;
1378         sub.centerY = height / 2f;
1379 
1380         return sub;
1381     }
1382 
1383     /**
1384      * Get a scaled copy of this image with a uniform scale
1385      *
1386      * @param scale The scale to apply
1387      * @return The new scaled image
1388      */
1389     @Nonnull
1390     public Image getScaledCopy(float scale) {
1391         init();
1392         return getScaledCopy((int) (width*scale),(int) (height*scale));
1393     }
1394 
1395     /**
1396      * Get a scaled copy of this image with its center of rotation
1397      * scaled accordingly.
1398      *
1399      * @param width The width of the copy
1400      * @param height The height of the copy
1401      * @return The new scaled image
1402      */
1403     @Nonnull
1404     Image getScaledCopy(int width, int height) {
1405         init();
1406         Image image = new Image();
1407         image.inited = true;
1408         image.filter = this.filter;
1409         image.texture = this.texture;
1410         image.ref = ref;
1411 
1412         image.textureOffsetX = this.textureOffsetX;
1413         image.textureOffsetY = this.textureOffsetY;
1414         image.textureWidth = this.textureWidth;
1415         image.textureHeight = this.textureHeight;
1416         image.width = width;
1417         image.height = height;
1418         image.centerX = this.centerX * (width / (float)this.width);
1419         image.centerY = this.centerY * (height / (float)this.height);
1420         return image;
1421     }
1422 
1423     /**
1424      * Make sure the texture cordinates are inverse on the y axis
1425      */
1426     public void ensureInverted() {
1427         init();
1428         if (textureHeight > 0) {
1429             textureOffsetY = textureOffsetY + textureHeight;
1430             textureHeight = -textureHeight;
1431 //            textureOffsetY = - height / (float)texture.getTextureHeight();
1432 //            System.out.println("blah "+textureOffsetY+" "+height+" "+textureHeight+" "+texture.getTextureHeight());
1433         }
1434     }
1435 
1436     /**
1437      * Get a copy image flipped on potentially two axis
1438      *
1439      * @param flipHorizontal True if we want to flip the image horizontally
1440      * @param flipVertical True if we want to flip the image vertically
1441      * @return The flipped image instance
1442      */
1443     @Nonnull
1444     public Image getFlippedCopy(boolean flipHorizontal, boolean flipVertical) {
1445         init();
1446         Image image = copy();
1447 
1448         if (flipHorizontal) {
1449             image.textureOffsetX = textureOffsetX + textureWidth;
1450             image.textureWidth = -textureWidth;
1451         }
1452         if (flipVertical) {
1453             image.textureOffsetY = textureOffsetY + textureHeight;
1454             image.textureHeight = -textureHeight;
1455         }
1456 
1457         return image;
1458     }
1459 
1460     /**
1461      * End the use of this sprite sheet and release the lock.
1462      *
1463      * @see #startUse
1464      */
1465     void endUse() {
1466         if (inUse != texture) {
1467             throw new RuntimeException("The sprite sheet is not currently in use");
1468         }
1469         inUse = null;
1470         GL.glEnd();
1471     }
1472 
1473     /**
1474      * Start using this sheet. This method can be used for optimal rendering of a collection
1475      * of sprites from a single sprite sheet. First, startUse(). Then render each sprite by
1476      * calling renderInUse(). Finally, endUse(). Between start and end there can be no rendering
1477      * of other sprites since the rendering is locked for this sprite sheet.
1478      */
1479     void startUse() {
1480         if (inUse != null) {
1481             throw new RuntimeException("Attempt to start use of a sprite sheet before ending use with another - see endUse()");
1482         }
1483         inUse = texture;
1484         init();
1485 
1486         Color.white.bind();
1487         texture.bind();
1488         GL.glBegin(SGL.GL_QUADS);
1489     }
1490 
1491     /**
1492      * @see java.lang.Object#toString()
1493      */
1494     @Nonnull
1495     public String toString() {
1496         init();
1497 
1498         return "[Image "+ref+" "+width+"x"+height+"  "+textureOffsetX+","+textureOffsetY+","+textureWidth+","+textureHeight+"]";
1499     }
1500 
1501     /**
1502      * Get the OpenGL texture holding this image
1503      *
1504      * @return The OpenGL texture holding this image
1505      */
1506     @Nullable
1507     public Texture getTexture() {
1508         return texture;
1509     }
1510 
1511     /**
1512      * Set the texture used by this image; if the given
1513      * texture is different from the current texture,
1514      * this image is assumed to be no longer destroyed.
1515      *
1516      * @param texture The texture used by this image
1517      */
1518     public void setTexture(@Nullable Texture texture) {
1519         if (texture!=this.texture)
1520             destroyed = false;
1521         this.texture = texture;
1522         reinit();
1523     }
1524 
1525     /**
1526      * Translate an unsigned int into a signed integer
1527      *
1528      * @param b The byte to convert
1529      * @return The integer value represented by the byte
1530      */
1531     private int translate(byte b) {
1532         if (b < 0) {
1533             return 256 + b;
1534         }
1535 
1536         return b;
1537     }
1538 
1539     /**
1540      * Get the colour of a pixel at a specified location in this image.
1541      *
1542      * The first time this method is called, the pixel data will be
1543      * copied from the backing texture and cached. Changing the image
1544      * afterwards with getGraphics().flush() will reset the pixel data,
1545      * meaning the next time you call this the texture will be re-copied
1546      * with new data. You can manually reset the pixel data with flushPixelData().
1547      *
1548      * This method accounts for sub-images and flipped copies, but however
1549      * does not account for scaled images (since the backing texture is not
1550      * actually re-sampled during a scale).
1551      *
1552      * @param x The x coordinate of the pixel
1553      * @param y The y coordinate of the pixel
1554      * @return The Color of the pixel at the specified location
1555      */
1556     @Nonnull
1557     public Color getColor(int x, int y) {
1558         if (pixelData == null) {
1559             pixelData = texture.getTextureData();
1560         }
1561 
1562         int xo = (int) (textureOffsetX * texture.getTextureWidth());
1563         int yo = (int) (textureOffsetY * texture.getTextureHeight());
1564         if (textureWidth < 0) {
1565             x = xo - x - 1;
1566         } else {
1567             x = xo + x;
1568         }
1569 
1570         if (textureHeight < 0) {
1571             y = yo - y - 1;
1572         } else {
1573             y = yo + y;
1574         }
1575         int offset = x + (y * texture.getTextureWidth());
1576         offset *= texture.hasAlpha() ? 4 : 3;
1577 
1578         if (texture.hasAlpha()) {
1579             return new Color(translate(pixelData[offset]),translate(pixelData[offset+1]),
1580                     translate(pixelData[offset+2]),translate(pixelData[offset+3]));
1581         } else {
1582             return new Color(translate(pixelData[offset]),translate(pixelData[offset+1]),
1583                     translate(pixelData[offset+2]));
1584         }
1585     }
1586 
1587     /**
1588      * Check if this image has been destroyed
1589      *
1590      * @return True if this image has been destroyed
1591      */
1592     boolean isDestroyed() {
1593         return destroyed;
1594     }
1595 
1596     /**
1597      * Destroy the image and release any native resources.
1598      * Calls on a destroyed image have undefined results
1599      *
1600      * @throws SlickException Indicates a failure to release resources on the graphics card
1601      */
1602     public void destroy() {
1603         if (isDestroyed()) {
1604             return;
1605         }
1606         flushPixelData();
1607         destroyed = true;
1608         texture.release();
1609         GraphicsFactory.releaseGraphicsForImage(this);
1610     }
1611 
1612     /**
1613      * Flush the current pixel data to force a re-read next update
1614      */
1615     public void flushPixelData() {
1616         pixelData = null;
1617     }
1618 
1619     /**
1620      * Draws an image according to a transform (Flip, FlipX, FlipY)
1621      * <li>    0x01 - Flip top-left corner with bottom right
1622      * <li> 0x02 - Flip on X axis
1623      * <li> 0x04 - Flip on Y axis
1624      *
1625      * @param x
1626      *            The x location to draw the image at
1627      * @param y
1628      *            The y location to draw the image at
1629      * @param width
1630      *            The width to render the image at
1631      * @param height
1632      *            The height to render the image at
1633      * @param transform The transform to use (flip, flipX or flipY)
1634      */
1635     void drawEmbedded(float x, float y, float width, float height, byte transform) {
1636         boolean rotate = (transform & 1) > 0;
1637         boolean flipY  = ((transform & 2) > 0) ^ rotate;
1638         boolean flipX  = ((transform & 4) > 0) ^ rotate;
1639 
1640         if (flipX) {
1641             x+=width;
1642             width*=-1;
1643         }
1644         if (flipY) {
1645             y+=height;
1646             height*=-1;
1647         }
1648         if (!rotate)
1649             drawEmbedded(x,y,width, height);
1650         else {
1651             init();
1652             if (corners == null) {
1653                 GL.glTexCoord2f(textureOffsetX + textureWidth,
1654                         textureOffsetY+ textureHeight);
1655                 GL.glVertex3f(x, y, 0);
1656                 GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight);
1657                 GL.glVertex3f(x, y + height, 0);
1658                 GL.glTexCoord2f(textureOffsetX , textureOffsetY);
1659                 GL.glVertex3f(x + width, y + height, 0);
1660                 GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY);
1661                 GL.glVertex3f(x + width, y, 0);
1662             } else {
1663                 corners[TOP_LEFT].bind();
1664                 GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY
1665                         + textureHeight);
1666                 GL.glVertex3f(x, y, 0);
1667                 corners[BOTTOM_LEFT].bind();
1668                 GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight);
1669                 GL.glVertex3f(x, y + height, 0);
1670                 corners[BOTTOM_RIGHT].bind();
1671                 GL.glTexCoord2f(textureOffsetX , textureOffsetY);
1672                 GL.glVertex3f(x + width, y + height, 0);
1673                 corners[TOP_RIGHT].bind();
1674                 GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY);
1675                 GL.glVertex3f(x + width, y, 0);
1676             }
1677         }
1678     }
1679 }