/* * Copyright (c) 2013, Slick2D * 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 the Slick2D 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 org.newdawn.slick; import java.io.IOException; import java.io.InputStream; import org.newdawn.slick.opengl.ImageData; import org.newdawn.slick.opengl.InternalTextureLoader; import org.newdawn.slick.opengl.Texture; import org.newdawn.slick.opengl.TextureImpl; import org.newdawn.slick.opengl.pbuffer.GraphicsFactory; import org.newdawn.slick.opengl.renderer.Renderer; import org.newdawn.slick.opengl.renderer.SGL; import org.newdawn.slick.util.Log; /** * An image loaded from a file and renderable to the canvas * * @author kevin */ @SuppressWarnings("unused") public class Image implements Renderable { /** The top left corner identifier */ public static final int TOP_LEFT = 0; /** The top right corner identifier */ public static final int TOP_RIGHT = 1; /** The bottom right corner identifier */ public static final int BOTTOM_RIGHT = 2; /** The bottom left corner identifier */ public static final int BOTTOM_LEFT = 3; /** The renderer to use for all GL operations */ protected static SGL GL = Renderer.get(); /** The sprite sheet currently in use */ protected static Image inUse; /** Use Linear Filtering */ public static final int FILTER_LINEAR = 1; /** Use Nearest Filtering */ public static final int FILTER_NEAREST = 2; /** The OpenGL texture for this image */ protected Texture texture; /** The width of the image */ protected int width; /** The height of the image */ protected int height; /** The texture coordinate width to use to find our image */ protected float textureWidth; /** The texture coordinate height to use to find our image */ protected float textureHeight; /** The x texture offset to use to find our image */ protected float textureOffsetX; /** The y texture offset to use to find our image */ protected float textureOffsetY; /** Angle to rotate the image to. */ protected float angle; /** The alpha to draw the image at */ protected float alpha = 1.0f; /** The name given for the image */ protected String ref; /** True if this image's state has been initialised */ protected boolean inited = false; /** A pixelData holding the pixel data if it's been read for this texture */ protected byte[] pixelData; /** True if the image has been destroyed */ protected boolean destroyed; /** The x coordinate of the centre of rotation */ protected float centerX; /** The y coordinate of the centre of rotation */ protected float centerY; /** A meaningful name provided by the user of the image to tag it */ protected String name; /** The colours for each of the corners */ protected Color[] corners; /** The OpenGL max filter */ private int filter = SGL.GL_LINEAR; /** True if the image should be flipped vertically */ private boolean flipped; /** The transparent colour set if any */ private Color transparent; /** * Create a texture as a copy of another * * @param other The other texture to copy */ protected Image(Image other) { this.width = other.getWidth(); this.height = other.getHeight(); this.texture = other.texture; this.textureWidth = other.textureWidth; this.textureHeight = other.textureHeight; this.ref = other.ref; this.textureOffsetX = other.textureOffsetX; this.textureOffsetY = other.textureOffsetY; centerX = width / 2f; centerY = height / 2f; inited = true; } /** * Cloning constructor - only used internally. */ protected Image() { } /** * Creates an image using the specified texture * * @param texture * The texture to use */ public Image(Texture texture) { this.texture = texture; ref = texture.toString(); clampTexture(); } /** * Create an image based on a file at the specified location * * @param ref * The location of the image file to load * @throws SlickException * Indicates a failure to load the image */ public Image(String ref) throws SlickException { this(ref, false); } /** * Create an image based on a file at the specified location * * @param ref The location of the image file to load * @param trans The color to be treated as transparent * @throws SlickException Indicates a failure to load the image */ public Image(String ref, Color trans) throws SlickException { this(ref, false, FILTER_LINEAR, trans); } /** * Create an image based on a file at the specified location * * @param ref The location of the image file to load * @param flipped True if the image should be flipped on the y-axis on load * @throws SlickException Indicates a failure to load the image */ public Image(String ref, boolean flipped) throws SlickException { this(ref, flipped, FILTER_LINEAR); } /** * Create an image based on a file at the specified location * * @param ref The location of the image file to load * @param flipped True if the image should be flipped on the y-axis on load * @param filter The filtering method to use when scaling this image * @throws SlickException Indicates a failure to load the image */ public Image(String ref, boolean flipped, int filter) throws SlickException { this(ref, flipped, filter, null); } /** * Create an image based on a file at the specified location * * @param ref The location of the image file to load * @param flipped True if the image should be flipped on the y-axis on load * @param f The filtering method to use when scaling this image * @param transparent The color to treat as transparent * @throws SlickException Indicates a failure to load the image */ public Image(String ref, boolean flipped, int f, Color transparent) throws SlickException { this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; this.transparent = transparent; this.flipped = flipped; try { this.ref = ref; int[] trans = null; if (transparent != null) { trans = new int[3]; trans[0] = (int) (transparent.r * 255); trans[1] = (int) (transparent.g * 255); trans[2] = (int) (transparent.b * 255); } texture = InternalTextureLoader.get().getTexture(ref, flipped, filter, trans); } catch (IOException e) { Log.error(e); throw new SlickException("Failed to load image from: "+ref, e); } } /** * Set the image filtering to be used. Note that this will also affect any * image that was derived from this one (i.e. sub-images etc) * * @param f The filtering mode to use */ public void setFilter(int f) { this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; texture.bind(); GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_MIN_FILTER, filter); GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_MAG_FILTER, filter); } /** * Create an empty image * * @param width The width of the image * @param height The height of the image * @throws SlickException Indicates a failure to create the underlying resource */ public Image(int width, int height) throws SlickException { this(width, height, FILTER_NEAREST); } /** * Create an empty image * * @param width The width of the image * @param height The height of the image * @param f The filter to apply to scaling the new image * @throws SlickException Indicates a failure to create the underlying resource */ public Image(int width, int height, int f) throws SlickException { ref = super.toString(); this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; try { texture = InternalTextureLoader.get().createTexture(width, height, this.filter); } catch (IOException e) { Log.error(e); throw new SlickException("Failed to create empty image "+width+"x"+height); } init(); } /** * Create an image based on a file at the specified location * * @param in The input stream to read the image from * @param ref The name that should be assigned to the image * @param flipped True if the image should be flipped on the y-axis on load * @throws SlickException Indicates a failure to load the image */ public Image(InputStream in, String ref, boolean flipped) throws SlickException { this(in, ref, flipped, FILTER_LINEAR); } /** * Create an image based on a file at the specified location * * @param in The input stream to read the image from * @param ref The name that should be assigned to the image * @param flipped True if the image should be flipped on the y-axis on load * @param filter The filter to use when scaling this image * @throws SlickException Indicates a failure to load the image */ public Image(InputStream in, String ref, boolean flipped,int filter) throws SlickException { load(in, ref, flipped, filter, null); } /** * Create an image from a pixelData of pixels * * @param buffer The pixelData to use to create the image */ Image(ImageBuffer buffer) { this(buffer, FILTER_LINEAR); TextureImpl.bindNone(); } /** * Create an image from a pixelData of pixels * * @param buffer The pixelData to use to create the image * @param filter The filter to use when scaling this image */ Image(ImageBuffer buffer, int filter) { this((ImageData) buffer, filter); TextureImpl.bindNone(); } /** * Create an image from a image data source * * @param data The pixelData to use to create the image */ public Image(ImageData data) { this(data, FILTER_LINEAR); } /** * Create an image from a image data source. Note that this method uses * * @param data The pixelData to use to create the image * @param f The filter to use when scaling this image */ public Image(ImageData data, int f) { try { this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; texture = InternalTextureLoader.get().getTexture(data, this.filter); ref = texture.toString(); } catch (IOException e) { Log.error(e); } } /** * Get the OpenGL image filter in use * * @return The filter for magnification */ public int getFilter() { return filter; } /** * Get the reference to the resource this image was loaded from, if any. Note that * this can be null in the cases where an image was programatically generated. * * @return The reference to the resource the reference was loaded from */ public String getResourceReference() { return ref; } /** * Set the filter to apply when drawing this image * * @param r The red component of the filter colour * @param g The green component of the filter colour * @param b The blue component of the filter colour * @param a The alpha component of the filter colour */ public void setImageColor(float r, float g, float b, float a) { setColor(TOP_LEFT, r, g, b, a); setColor(TOP_RIGHT, r, g, b, a); setColor(BOTTOM_LEFT, r, g, b, a); setColor(BOTTOM_RIGHT, r, g, b, a); } /** * Set the filter to apply when drawing this image * * @param r The red component of the filter colour * @param g The green component of the filter colour * @param b The blue component of the filter colour */ public void setImageColor(float r, float g, float b) { setColor(TOP_LEFT, r, g, b); setColor(TOP_RIGHT, r, g, b); setColor(BOTTOM_LEFT, r, g, b); setColor(BOTTOM_RIGHT, r, g, b); } /** * Set the color of the given corner when this image is rendered. This is * useful lots of visual effect but especially light maps * * @param corner The corner identifier for the corner to be set * @param r The red component value to set (between 0 and 1) * @param g The green component value to set (between 0 and 1) * @param b The blue component value to set (between 0 and 1) * @param a The alpha component value to set (between 0 and 1) */ public void setColor(int corner, float r, float g, float b, float a) { if (corners == null) { 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)}; } corners[corner].r = r; corners[corner].g = g; corners[corner].b = b; corners[corner].a = a; } /** * Set the color of the given corner when this image is rendered. This is * useful lots of visual effect but especially light maps * * @param corner The corner identifier for the corner to be set * @param r The red component value to set (between 0 and 1) * @param g The green component value to set (between 0 and 1) * @param b The blue component value to set (between 0 and 1) */ public void setColor(int corner, float r, float g, float b) { if (corners == null) { 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)}; } corners[corner].r = r; corners[corner].g = g; corners[corner].b = b; } /** * Clamp the loaded texture to it's edges */ public void clampTexture() { if (GL.canTextureMirrorClamp()) { GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT); GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT); } else { GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_CLAMP); GL.glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_CLAMP); } } /** * Give this image a meaningful tagging name. Can be used as user data/identifier * for the image. * * @param name The name to assign the image */ public void setName(String name) { this.name = name; } /** * Return a meaningful tagging name that has been assigned to this image. * * @return A name or null if the name hasn't been set */ public String getName() { return name; } /** * Get a graphics context that can be used to draw to this image * * @return The graphics context used to render to this image * @throws SlickException Indicates a failure to create a graphics context */ public Graphics getGraphics() throws SlickException { return GraphicsFactory.getGraphicsForImage(this); } /** * Load the image * * @param in The input stream to read the image from * @param ref The name that should be assigned to the image * @param flipped True if the image should be flipped on the y-axis on load * @param f The filter to use when scaling this image * @param transparent The color to treat as transparent * @throws SlickException Indicates a failure to load the image */ private void load(InputStream in, String ref, boolean flipped, int f, Color transparent) throws SlickException { this.filter = f == FILTER_LINEAR ? SGL.GL_LINEAR : SGL.GL_NEAREST; try { this.ref = ref; int[] trans = null; if (transparent != null) { trans = new int[3]; trans[0] = (int) (transparent.r * 255); trans[1] = (int) (transparent.g * 255); trans[2] = (int) (transparent.b * 255); } texture = InternalTextureLoader.get().getTexture(in, ref, flipped, filter, trans); } catch (IOException e) { Log.error(e); throw new SlickException("Failed to load image from: "+ref, e); } } /** * Bind to the texture of this image */ public void bind() { texture.bind(); } /** * Reinitialise internal data */ protected void reinit() { inited = false; init(); } /** * Initialise internal data */ protected final void init() { if (inited) { return; } inited = true; if (texture != null) { width = texture.getImageWidth(); height = texture.getImageHeight(); textureOffsetX = 0; textureOffsetY = 0; textureWidth = texture.getWidth(); textureHeight = texture.getHeight(); } initImpl(); centerX = width / 2f; centerY = height / 2f; } /** * Hook for subclasses to perform initialisation */ protected void initImpl() { } /** * Draw this image at the current location */ public void draw() { draw(0,0); } /** * Draw the image based on its center * * @param x The x coordinate to place the image's center at * @param y The y coordinate to place the image's center at */ public void drawCentered(float x, float y) { draw(x-(getWidth()/2),y-(getHeight()/2)); } /** * Draw the image based on its center with a color filter * * @param x The x coordinate to place the image's center at * @param y The y coordinate to place the image's center at * @param color The color filter to apply */ public void drawCentered(float x, float y, Color color) { draw(x - (getWidth() / 2f), y - (getHeight() / 2f), color); } /** * Draw this image at the specified location * * @param x The x location to draw the image at * @param y The y location to draw the image at */ @Override public void draw(float x, float y) { init(); draw(x,y,width,height); } /** * Draw this image at the specified location * * @param x The x location to draw the image at * @param y The y location to draw the image at * @param filter The color to filter with when drawing */ @Override public void draw(float x, float y, Color filter) { init(); draw(x,y,width,height, filter); } /** * Draw this image as part of a collection of images * * @param x The x location to draw the image at * @param y The y location to draw the image at * @param width The width to render the image at * @param height The height to render the image at */ public void drawEmbedded(float x,float y,float width,float height) { init(); if (corners == null) { GL.glTexCoord2f(textureOffsetX, textureOffsetY); GL.glVertex3f(x, y, 0); GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); GL.glVertex3f(x, y + height, 0); GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + textureHeight); GL.glVertex3f(x + width, y + height, 0); GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); GL.glVertex3f(x + width, y, 0); } else { corners[TOP_LEFT].bind(); GL.glTexCoord2f(textureOffsetX, textureOffsetY); GL.glVertex3f(x, y, 0); corners[BOTTOM_LEFT].bind(); GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); GL.glVertex3f(x, y + height, 0); corners[BOTTOM_RIGHT].bind(); GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + textureHeight); GL.glVertex3f(x + width, y + height, 0); corners[TOP_RIGHT].bind(); GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); GL.glVertex3f(x + width, y, 0); } } /** * Get the x offset in texels into the source texture * * @return The x offset */ public float getTextureOffsetX() { init(); return textureOffsetX; } /** * Get the y offset in texels into the source texture * * @return The y offset */ public float getTextureOffsetY() { init(); return textureOffsetY; } /** * Get the width in texels into the source texture * * @return The width */ public float getTextureWidth() { init(); return textureWidth; } /** * Get the height in texels into the source texture * * @return The height */ public float getTextureHeight() { init(); return textureHeight; } /** * Draw the image with a given scale * * @param x The x position to draw the image at * @param y The y position to draw the image at * @param scale The scaling to apply */ public void draw(float x,float y,float scale) { init(); draw(x,y,width*scale,height*scale,Color.white); } /** * Draw the image with a given scale * * @param x The x position to draw the image at * @param y The y position to draw the image at * @param scale The scaling to apply * @param filter The colour filter to adapt the image with */ public void draw(float x,float y,float scale,Color filter) { init(); draw(x,y,width*scale,height*scale,filter); } /** * Draw this image at a specified location and size * * @param x * The x location to draw the image at * @param y * The y location to draw the image at * @param width * The width to render the image at * @param height * The height to render the image at */ @Override public void draw(float x,float y,float width,float height) { init(); draw(x,y,width,height,Color.white); } /** * Draw this image at a specified location and size * * @param x The x location to draw the image at * @param y The y location to draw the image at * @param hshear The amount to shear the bottom points by horizontally * @param vshear The amount to shear the right points by vertically */ public void drawSheared(float x,float y, float hshear, float vshear) { this.drawSheared(x, y, hshear, vshear, Color.white); } /** * Draw this image at a specified location and size * * @param x The x location to draw the image at * @param y The y location to draw the image at * @param hshear The amount to shear the bottom points by horizontally * @param vshear The amount to shear the right points by vertically * @param filter The colour filter to apply */ public void drawSheared(float x,float y, float hshear, float vshear, Color filter) { if (alpha != 1) { if (filter == null) { filter = Color.white; } filter = new Color(filter); filter.a *= alpha; } if (filter != null) { filter.bind(); } texture.bind(); GL.glTranslatef(x, y, 0); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glBegin(SGL.GL_QUADS); init(); GL.glTexCoord2f(textureOffsetX, textureOffsetY); GL.glVertex3f(0, 0, 0); GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); GL.glVertex3f(hshear, height, 0); GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + textureHeight); GL.glVertex3f(width + hshear, height + vshear, 0); GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); GL.glVertex3f(width, vshear, 0); GL.glEnd(); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glTranslatef(-x, -y, 0); } /** * Draw this image at a specified location and size * * @param x The x location to draw the image at * @param y The y location to draw the image at * @param width The width to render the image at * @param height The height to render the image at * @param filter The color to filter with while drawing */ @Override public void draw(float x,float y,float width,float height,Color filter) { if (alpha != 1) { if (filter == null) { filter = Color.white; } filter = new Color(filter); filter.a *= alpha; } if (filter != null) { filter.bind(); } texture.bind(); GL.glTranslatef(x, y, 0); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glBegin(SGL.GL_QUADS); drawEmbedded(0,0,width,height); GL.glEnd(); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glTranslatef(-x, -y, 0); } /** * Draw this image at a specified location and size as a silohette * * @param x The x location to draw the image at * @param y The y location to draw the image at * @param width The width to render the image at * @param height The height to render the image at */ public void drawFlash(float x,float y,float width,float height) { drawFlash(x,y,width,height,Color.white); } /** * Set the centre of the rotation when applied to this image * * @param x The x coordinate of center of rotation relative to the top left corner of the image * @param y The y coordinate of center of rotation relative to the top left corner of the image */ public void setCenterOfRotation(float x, float y) { centerX = x; centerY = y; } /** * Get the x component of the center of rotation of this image * * @return The x component of the center of rotation */ public float getCenterOfRotationX() { init(); return centerX; } /** * Get the y component of the center of rotation of this image * * @return The y component of the center of rotation */ public float getCenterOfRotationY() { init(); return centerY; } /** * Draw this image at a specified location and size as a silohette * * @param x The x location to draw the image at * @param y The y location to draw the image at * @param width The width to render the image at * @param height The height to render the image at * @param col The color for the sillohette */ public void drawFlash(float x,float y,float width,float height, Color col) { init(); col.bind(); texture.bind(); if (GL.canSecondaryColor()) { GL.glEnable(SGL.GL_COLOR_SUM_EXT); GL.glSecondaryColor3ubEXT((byte)(col.r * 255), (byte)(col.g * 255), (byte)(col.b * 255)); } GL.glTexEnvi(SGL.GL_TEXTURE_ENV, SGL.GL_TEXTURE_ENV_MODE, SGL.GL_MODULATE); GL.glTranslatef(x, y, 0); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glBegin(SGL.GL_QUADS); drawEmbedded(0,0,width,height); GL.glEnd(); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glTranslatef(-x, -y, 0); if (GL.canSecondaryColor()) { GL.glDisable(SGL.GL_COLOR_SUM_EXT); } } /** * Draw this image at a specified location and size in a white silohette * * @param x The x location to draw the image at * @param y The y location to draw the image at */ public void drawFlash(float x,float y) { drawFlash(x,y,getWidth(),getHeight()); } /** * Set the angle to rotate this image to. The angle will be normalized to * be {@literal 0 <= angle < 360}. The image will be rotated around its center. * * @param angle The angle to be set */ public void setRotation(float angle) { this.angle = angle % 360.0f; } /** * Get the current angle of rotation for this image. * The image will be rotated around its center. * * @return The current angle. */ public float getRotation() { return angle; } /** * Get the alpha value to use when rendering this image * * @return The alpha value to use when rendering this image */ public float getAlpha() { return alpha; } /** * Set the alpha value to use when rendering this image * * @param alpha The alpha value to use when rendering this image */ public void setAlpha(float alpha) { this.alpha = alpha; } /** * Add the angle provided to the current rotation. The angle will be normalized to * be {@literal 0 <= angle < 360}. The image will be rotated around its center. * * @param angle The angle to add. */ public void rotate(float angle) { this.angle += angle; this.angle = this.angle % 360; } /** * Get a sub-part of this image. Note that the create image retains a reference to the * image data so should anything change it will affect sub-images too. * * @param x The x coordinate of the sub-image * @param y The y coordinate of the sub-image * @param width The width of the sub-image * @param height The height of the sub-image * @return The image represent the sub-part of this image */ public Image getSubImage(int x,int y,int width,int height) { init(); float newTextureOffsetX = ((x / (float) this.width) * textureWidth) + textureOffsetX; float newTextureOffsetY = ((y / (float) this.height) * textureHeight) + textureOffsetY; float newTextureWidth = ((width / (float) this.width) * textureWidth); float newTextureHeight = ((height / (float) this.height) * textureHeight); Image sub = new Image(); sub.inited = true; sub.texture = this.texture; sub.textureOffsetX = newTextureOffsetX; sub.textureOffsetY = newTextureOffsetY; sub.textureWidth = newTextureWidth; sub.textureHeight = newTextureHeight; sub.width = width; sub.height = height; sub.ref = ref; sub.centerX = width / 2f; sub.centerY = height / 2f; return sub; } /** * Draw a section of this image at a particular location and scale on the screen * * @param x The x position to draw the image * @param y The y position to draw the image * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) */ public void draw(float x, float y, float srcx, float srcy, float srcx2, float srcy2) { draw(x,y,x+width,y+height,srcx,srcy,srcx2,srcy2); } /** * Draw a section of this image at a particular location and scale on the screen * * @param x The x position to draw the image * @param y The y position to draw the image * @param x2 The x position of the bottom right corner of the drawn image * @param y2 The y position of the bottom right corner of the drawn image * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) */ public void draw(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2) { draw(x,y,x2,y2,srcx,srcy,srcx2,srcy2,Color.white); } /** * Draw a section of this image at a particular location and scale on the screen * * @param x The x position to draw the image * @param y The y position to draw the image * @param x2 The x position of the bottom right corner of the drawn image * @param y2 The y position of the bottom right corner of the drawn image * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) * @param filter The colour filter to apply when drawing */ public void draw(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2, Color filter) { init(); if (alpha != 1) { if (filter == null) { filter = Color.white; } filter = new Color(filter); filter.a *= alpha; } filter.bind(); texture.bind(); GL.glTranslatef(x, y, 0); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glBegin(SGL.GL_QUADS); drawEmbedded(0,0,x2-x,y2-y,srcx,srcy,srcx2,srcy2); GL.glEnd(); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glTranslatef(-x, -y, 0); // GL.glBegin(SGL.GL_QUADS); // drawEmbedded(x,y,x2,y2,srcx,srcy,srcx2,srcy2); // GL.glEnd(); } /** * Draw a section of this image at a particular location and scale on the screen, while this * is image is "in use", i.e. between calls to startUse and endUse. * * @param x The x position to draw the image * @param y The y position to draw the image * @param x2 The x position of the bottom right corner of the drawn image * @param y2 The y position of the bottom right corner of the drawn image * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) */ public void drawEmbedded(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2) { drawEmbedded(x,y,x2,y2,srcx,srcy,srcx2,srcy2,null); } /** * Draw a section of this image at a particular location and scale on the screen, while this * is image is "in use", i.e. between calls to startUse and endUse. * * @param x The x position to draw the image * @param y The y position to draw the image * @param x2 The x position of the bottom right corner of the drawn image * @param y2 The y position of the bottom right corner of the drawn image * @param srcx The x position of the rectangle to draw from this image (i.e. relative to this image) * @param srcy The y position of the rectangle to draw from this image (i.e. relative to this image) * @param srcx2 The x position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) * @param srcy2 The t position of the bottom right cornder of rectangle to draw from this image (i.e. relative to this image) * @param filter The colour filter to apply when drawing */ public void drawEmbedded(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2, Color filter) { if (filter != null) { filter.bind(); } float mywidth = x2 - x; float myheight = y2 - y; float texwidth = srcx2 - srcx; float texheight = srcy2 - srcy; float newTextureOffsetX = (((srcx) / (width)) * textureWidth) + textureOffsetX; float newTextureOffsetY = (((srcy) / (height)) * textureHeight) + textureOffsetY; float newTextureWidth = ((texwidth) / (width)) * textureWidth; float newTextureHeight = ((texheight) / (height)) * textureHeight; GL.glTexCoord2f(newTextureOffsetX, newTextureOffsetY); GL.glVertex3f(x,y, 0.0f); GL.glTexCoord2f(newTextureOffsetX, newTextureOffsetY + newTextureHeight); GL.glVertex3f(x,(y + myheight), 0.0f); GL.glTexCoord2f(newTextureOffsetX + newTextureWidth, newTextureOffsetY + newTextureHeight); GL.glVertex3f((x + mywidth),(y + myheight), 0.0f); GL.glTexCoord2f(newTextureOffsetX + newTextureWidth, newTextureOffsetY); GL.glVertex3f((x + mywidth),y, 0.0f); } /** * Draw the image in a warper rectangle. The effects this can * have are many and varied, might be interesting though. * * @param x1 The top left corner x coordinate * @param y1 The top left corner y coordinate * @param x2 The top right corner x coordinate * @param y2 The top right corner y coordinate * @param x3 The bottom right corner x coordinate * @param y3 The bottom right corner y coordinate * @param x4 The bottom left corner x coordinate * @param y4 The bottom left corner y coordinate */ public void drawWarped(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { Color.white.bind(); texture.bind(); GL.glTranslatef(x1, y1, 0); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glBegin(SGL.GL_QUADS); init(); GL.glTexCoord2f(textureOffsetX, textureOffsetY); GL.glVertex3f(0, 0, 0); GL.glTexCoord2f(textureOffsetX, textureOffsetY + textureHeight); GL.glVertex3f(x2 - x1, y2 - y1, 0); GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY + textureHeight); GL.glVertex3f(x3 - x1, y3 - y1, 0); GL.glTexCoord2f(textureOffsetX + textureWidth, textureOffsetY); GL.glVertex3f(x4 - x1, y4 - y1, 0); GL.glEnd(); if (angle != 0) { GL.glTranslatef(centerX, centerY, 0.0f); GL.glRotatef(-angle, 0.0f, 0.0f, 1.0f); GL.glTranslatef(-centerX, -centerY, 0.0f); } GL.glTranslatef(-x1, -y1, 0); } /** * Get the width of this image * * @return The width of this image */ public int getWidth() { init(); return width; } /** * Get the height of this image * * @return The height of this image */ public int getHeight() { init(); return height; } /** * Get a copy of this image. This is a shallow copy and does not * duplicate image adata. * * @return The copy of this image */ public Image copy() { init(); return getSubImage(0,0,width,height); } /** * Get a scaled copy of this image with a uniform scale * * @param scale The scale to apply * @return The new scaled image */ public Image getScaledCopy(float scale) { init(); return getScaledCopy((int) (width*scale),(int) (height*scale)); } /** * Get a scaled copy of this image * * @param width The width of the copy * @param height The height of the copy * @return The new scaled image */ public Image getScaledCopy(int width, int height) { init(); Image image = copy(); image.width = width; image.height = height; image.centerX = width / 2f; image.centerY = height / 2f; return image; } /** * Make sure the texture cordinates are inverse on the y axis */ public void ensureInverted() { if (textureHeight > 0) { textureOffsetY = textureOffsetY + textureHeight; textureHeight = -textureHeight; } } /** * Get a copy image flipped on potentially two axis * * @param flipHorizontal True if we want to flip the image horizontally * @param flipVertical True if we want to flip the image vertically * @return The flipped image instance */ public Image getFlippedCopy(boolean flipHorizontal, boolean flipVertical) { init(); Image image = copy(); if (flipHorizontal) { image.textureOffsetX = textureOffsetX + textureWidth; image.textureWidth = -textureWidth; } if (flipVertical) { image.textureOffsetY = textureOffsetY + textureHeight; image.textureHeight = -textureHeight; } return image; } /** * End the use of this sprite sheet and release the lock. * * @see #startUse */ public void endUse() { if (inUse != this) { throw new RuntimeException("The sprite sheet is not currently in use"); } inUse = null; GL.glEnd(); } /** * Start using this sheet. This method can be used for optimal rendering of a collection * of sprites from a single sprite sheet. First, startUse(). Then render each sprite by * calling renderInUse(). Finally, endUse(). Between start and end there can be no rendering * of other sprites since the rendering is locked for this sprite sheet. */ public void startUse() { if (inUse != null) { throw new RuntimeException("Attempt to start use of a sprite sheet before ending use with another - see endUse()"); } inUse = this; init(); Color.white.bind(); texture.bind(); GL.glBegin(SGL.GL_QUADS); } @Override public String toString() { init(); return "[Image "+ref+" "+width+"x"+height+" "+textureOffsetX+","+textureOffsetY+","+textureWidth+","+textureHeight+"]"; } /** * Get the OpenGL texture holding this image * * @return The OpenGL texture holding this image */ public Texture getTexture() { return texture; } /** * Set the texture used by this image * * @param texture The texture used by this image */ public void setTexture(Texture texture) { this.texture = texture; reinit(); } /** * Translate an unsigned int into a signed integer * * @param b The byte to convert * @return The integer value represented by the byte */ private int translate(byte b) { if (b < 0) { return 256 + b; } return b; } /** * Get the colour of a pixel at a specified location in this image * * @param x The x coordinate of the pixel * @param y The y coordinate of the pixel * @return The Color of the pixel at the specified location */ public Color getColor(int x, int y) { if (pixelData == null) { pixelData = texture.getTextureData(); } int xo = (int) (textureOffsetX * texture.getTextureWidth()); int yo = (int) (textureOffsetY * texture.getTextureHeight()); if (textureWidth < 0) { x = xo - x; } else { x = xo + x; } if (textureHeight < 0) { y = yo - y; } else { y = yo + y; } int offset = x + (y * texture.getTextureWidth()); offset *= texture.hasAlpha() ? 4 : 3; if (texture.hasAlpha()) { return new Color(translate(pixelData[offset]),translate(pixelData[offset+1]), translate(pixelData[offset+2]),translate(pixelData[offset+3])); } else { return new Color(translate(pixelData[offset]),translate(pixelData[offset+1]), translate(pixelData[offset+2])); } } /** * Get the alpha value of a pixel at a specified location in this image, * or 1f if the image does not support transparency. * * @param x The x coordinate of the pixel * @param y The y coordinate of the pixel * @return The alpha level of the pixel at the specified location */ public float getAlphaAt(int x, int y) { if (!texture.hasAlpha()) return 1f; if (pixelData == null) pixelData = texture.getTextureData(); // scale coordinates based on the image scale x = x * texture.getImageWidth() / width; y = y * texture.getImageHeight() / height; int xo = (int) (textureOffsetX * texture.getTextureWidth()); int yo = (int) (textureOffsetY * texture.getTextureHeight()); x = (textureWidth < 0) ? xo - x : xo + x; y = (textureHeight < 0) ? yo - y : yo + y; int offset = x + (y * texture.getTextureWidth()); offset *= 4; return (offset + 3 >= pixelData.length) ? 1f : translate(pixelData[offset + 3]) / 255f; } /** * Check if this image has been destroyed * * @return True if this image has been destroyed */ public boolean isDestroyed() { return destroyed; } /** * Destroy the image and release any native resources. * Calls on a destroyed image have undefined results * * @throws SlickException Indicates a failure to release resources on the graphics card */ public void destroy() throws SlickException { if (isDestroyed()) { return; } destroyed = true; texture.release(); GraphicsFactory.releaseGraphicsForImage(this); } /** * Flush the current pixel data to force a re-read next update */ public void flushPixelData() { pixelData = null; } }