All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mathiewz.slick.Graphics Maven / Gradle / Ivy

Go to download

The main purpose of this libraryis to modernize and maintain the slick2D library.

The newest version!
package com.github.mathiewz.slick;

import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;

import com.github.mathiewz.slick.geom.Rectangle;
import com.github.mathiewz.slick.geom.Shape;
import com.github.mathiewz.slick.geom.ShapeRenderer;
import com.github.mathiewz.slick.opengl.TextureImpl;
import com.github.mathiewz.slick.opengl.renderer.LineStripRenderer;
import com.github.mathiewz.slick.opengl.renderer.Renderer;
import com.github.mathiewz.slick.opengl.renderer.SGL;
import com.github.mathiewz.slick.util.FastTrig;
import com.github.mathiewz.slick.util.Log;

/**
 * A graphics context that can be used to render primatives to the accelerated
 * canvas provided by LWJGL.
 *
 * @author kevin
 */
public class Graphics {
    /** The renderer to use for all GL operations */
    protected static final SGL GL = Renderer.get();
    /** The renderer to use line strips */
    private static final LineStripRenderer LSR = Renderer.getLineStripRenderer();
    
    /** The normal drawing mode */
    public static final int MODE_NORMAL = 1;
    
    /** Draw to the alpha map */
    public static final int MODE_ALPHA_MAP = 2;
    
    /** Draw using the alpha blending */
    public static final int MODE_ALPHA_BLEND = 3;
    
    /** Draw multiplying the source and destination colours */
    public static final int MODE_COLOR_MULTIPLY = 4;
    
    /** Draw adding the existing colour to the new colour */
    public static final int MODE_ADD = 5;
    
    /** Draw blending the new image into the old one by a factor of it's colour */
    public static final int MODE_SCREEN = 6;
    
    /** The default number of segments that will be used when drawing an oval */
    private static final int DEFAULT_SEGMENTS = 50;
    
    /** The last graphics context in use */
    protected static Graphics currentGraphics = null;
    
    /** The default font to use */
    protected static Font DEFAULT_FONT;
    
    /** The last set scale */
    private float sx = 1;
    /** The last set scale */
    private float sy = 1;
    
    /**
     * Set the current graphics context in use
     *
     * @param current
     *            The graphics context that should be considered current
     */
    public static void setCurrent(Graphics current) {
        if (currentGraphics != current) {
            if (currentGraphics != null) {
                currentGraphics.disable();
            }
            currentGraphics = current;
            currentGraphics.enable();
        }
    }
    
    /** The font in use */
    private Font font;
    
    /** The current color */
    private Color currentColor = Color.white;
    
    /** The width of the screen */
    protected int screenWidth;
    
    /** The height of the screen */
    protected int screenHeight;
    
    /** True if the matrix has been pushed to the stack */
    private boolean pushed;
    
    /** The graphics context clipping */
    private Rectangle clip;
    
    /** Buffer used for setting the world clip */
    private final DoubleBuffer worldClip = BufferUtils.createDoubleBuffer(4);
    
    /** The buffer used to read a screen pixel */
    private final ByteBuffer readBuffer = BufferUtils.createByteBuffer(4);
    
    /** True if we're antialias */
    private boolean antialias;
    
    /** The world clip recorded since last set */
    private Rectangle worldClipRecord;
    
    /** The current drawing mode */
    private int currentDrawingMode = MODE_NORMAL;
    
    /** The current line width */
    private float lineWidth = 1;
    
    /** The matrix stack */
    private final ArrayList stack = new ArrayList<>();
    /** The index into the stack we're using */
    private int stackIndex;
    
    /**
     * Default constructor for sub-classes
     */
    public Graphics() {
    }
    
    /**
     * Create a new graphics context. Only the container should be doing this
     * really
     *
     * @param width
     *            The width of the screen for this context
     * @param height
     *            The height of the screen for this context
     */
    public Graphics(int width, int height) {
        if (DEFAULT_FONT == null) {
            AccessController.doPrivileged((PrivilegedAction) () -> {
                try {
                    DEFAULT_FONT = new AngelCodeFont("com/github/mathiewz/slick/data/defaultfont.fnt", "com/github/mathiewz/slick/data/defaultfont.png");
                } catch (SlickException e) {
                    Log.error(e);
                }
                return null; // nothing to return
            });
        }
        
        font = DEFAULT_FONT;
        screenWidth = width;
        screenHeight = height;
    }
    
    /**
     * Set the dimensions considered by the graphics context
     *
     * @param width
     *            The width of the graphics context
     * @param height
     *            The height of the graphics context
     */
    void setDimensions(int width, int height) {
        screenWidth = width;
        screenHeight = height;
    }
    
    /**
     * Set the drawing mode to use. This mode defines how pixels are drawn to
     * the graphics context. It can be used to draw into the alpha map.
     *
     * The mode supplied should be one of {@link Graphics#MODE_NORMAL} or
     * {@link Graphics#MODE_ALPHA_MAP} or {@link Graphics#MODE_ALPHA_BLEND}
     *
     * @param mode
     *            The mode to apply.
     */
    public void setDrawMode(int mode) {
        glOperation(() -> {
            currentDrawingMode = mode;
            if (currentDrawingMode == MODE_NORMAL) {
                GL.glEnable(GL11.GL_BLEND);
                GL.glColorMask(true, true, true, true);
                GL.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
            }
            if (currentDrawingMode == MODE_ALPHA_MAP) {
                GL.glDisable(GL11.GL_BLEND);
                GL.glColorMask(false, false, false, true);
            }
            if (currentDrawingMode == MODE_ALPHA_BLEND) {
                GL.glEnable(GL11.GL_BLEND);
                GL.glColorMask(true, true, true, false);
                GL.glBlendFunc(GL11.GL_DST_ALPHA, GL11.GL_ONE_MINUS_DST_ALPHA);
            }
            if (currentDrawingMode == MODE_COLOR_MULTIPLY) {
                GL.glEnable(GL11.GL_BLEND);
                GL.glColorMask(true, true, true, true);
                GL.glBlendFunc(GL11.GL_ONE_MINUS_SRC_COLOR, GL11.GL_SRC_COLOR);
            }
            if (currentDrawingMode == MODE_ADD) {
                GL.glEnable(GL11.GL_BLEND);
                GL.glColorMask(true, true, true, true);
                GL.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
            }
            if (currentDrawingMode == MODE_SCREEN) {
                GL.glEnable(GL11.GL_BLEND);
                GL.glColorMask(true, true, true, true);
                GL.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_COLOR);
            }
        });
    }
    
    /**
     * Clear the state of the alpha map across the entire screen. This sets
     * alpha to 0 everywhere, meaning in {@link Graphics#MODE_ALPHA_BLEND}
     * nothing will be drawn.
     */
    public void clearAlphaMap() {
        pushTransform();
        GL.glLoadIdentity();
        
        int originalMode = currentDrawingMode;
        setDrawMode(MODE_ALPHA_MAP);
        setColor(new Color(0, 0, 0, 0));
        fillRect(0, 0, screenWidth, screenHeight);
        setColor(currentColor);
        setDrawMode(originalMode);
        
        popTransform();
    }
    
    /**
     * Must be called before all OpenGL operations to maintain context for
     * dynamic images
     */
    private void predraw() {
        setCurrent(this);
    }
    
    /**
     * Must be called after all OpenGL operations to maintain context for
     * dynamic images
     */
    private void postdraw() {
    }
    
    /**
     * Enable rendering to this graphics context
     */
    protected void enable() {
    }
    
    /**
     * Flush this graphics context to the underlying rendering context
     */
    public void flush() {
        if (currentGraphics == this) {
            currentGraphics.disable();
            currentGraphics = null;
        }
    }
    
    /**
     * Disable rendering to this graphics context
     */
    protected void disable() {
    }
    
    /**
     * Get the current font
     *
     * @return The current font
     */
    public Font getFont() {
        return font;
    }
    
    /**
     * Set the background colour of the graphics context. This colour
     * is used when clearing the context. Note that calling this method
     * alone does not cause the context to be cleared.
     *
     * @param color
     *            The background color of the graphics context
     */
    public void setBackground(Color color) {
        glOperation(() -> {
            GL.glClearColor(color.r, color.g, color.b, color.a);
        });
    }
    
    /**
     * Get the current graphics context background color
     *
     * @return The background color of this graphics context
     */
    public Color getBackground() {
        predraw();
        FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
        GL.glGetFloat(GL11.GL_COLOR_CLEAR_VALUE, buffer);
        postdraw();
        
        return new Color(buffer);
    }
    
    /**
     * Clear the graphics context
     */
    public void clear() {
        glOperation(() -> {
            GL.glClear(GL11.GL_COLOR_BUFFER_BIT);
        });
    }
    
    /**
     * Reset the transformation on this graphics context
     */
    public void resetTransform() {
        sx = 1;
        sy = 1;
        
        if (pushed) {
            glOperation(() -> {
                GL.glPopMatrix();
                pushed = false;
            });
        }
    }
    
    /**
     * Check if we've pushed the previous matrix, if not then push it now.
     */
    private void checkPush() {
        if (!pushed) {
            glOperation(() -> {
                GL.glPushMatrix();
                pushed = true;
            });
        }
    }
    
    /**
     * Apply a scaling factor to everything drawn on the graphics context
     *
     * @param sx
     *            The scaling factor to apply to the x axis
     * @param sy
     *            The scaling factor to apply to the y axis
     */
    public void scale(float sx, float sy) {
        this.sx = this.sx * sx;
        this.sy = this.sy * sy;
        
        checkPush();
        
        glOperation(() -> {
            GL.glScalef(sx, sy, 1);
        });
    }
    
    /**
     * Apply a rotation to everything draw on the graphics context
     *
     * @param rx
     *            The x coordinate of the center of rotation
     * @param ry
     *            The y coordinate of the center of rotation
     * @param ang
     *            The angle (in degrees) to rotate by
     */
    public void rotate(float rx, float ry, float ang) {
        checkPush();
        
        glOperation(() -> {
            translate(rx, ry);
            GL.glRotatef(ang, 0, 0, 1);
            translate(-rx, -ry);
        });
    }
    
    /**
     * Apply a translation to everything drawn to the context
     *
     * @param x
     *            The amount to translate on the x-axis
     * @param y
     *            The amount of translate on the y-axis
     */
    public void translate(float x, float y) {
        checkPush();
        
        glOperation(() -> {
            GL.glTranslatef(x, y, 0);
        });
    }
    
    /**
     * Set the font to be used when rendering text
     *
     * @param font
     *            The font to be used when rendering text
     */
    public void setFont(Font font) {
        this.font = font;
    }
    
    /**
     * Reset to using the default font for this context
     */
    public void resetFont() {
        font = DEFAULT_FONT;
    }
    
    /**
     * Set the color to use when rendering to this context
     *
     * @param color
     *            The color to use when rendering to this context
     */
    public void setColor(Color color) {
        if (color == null) {
            return;
        }
        
        currentColor = new Color(color);
        glOperation(() -> {
            currentColor.bind();
        });
    }
    
    /**
     * Get the color in use by this graphics context
     *
     * @return The color in use by this graphics context
     */
    public Color getColor() {
        return new Color(currentColor);
    }
    
    /**
     * Draw a line on the canvas in the current colour
     *
     * @param x1
     *            The x coordinate of the start point
     * @param y1
     *            The y coordinate of the start point
     * @param x2
     *            The x coordinate of the end point
     * @param y2
     *            The y coordinate of the end point
     */
    public void drawLine(float x1, float y1, float x2, float y2) {
        float lineWidth = this.lineWidth - 1;
        
        if (LSR.applyGLLineFixes()) {
            if (x1 == x2) {
                if (y1 > y2) {
                    float temp = y2;
                    y2 = y1;
                    y1 = temp;
                }
                float step = 1 / sy;
                lineWidth = lineWidth / sy;
                fillRect(x1 - lineWidth / 2.0f, y1 - lineWidth / 2.0f, lineWidth + step, y2 - y1 + lineWidth + step);
                return;
            } else if (y1 == y2) {
                if (x1 > x2) {
                    float temp = x2;
                    x2 = x1;
                    x1 = temp;
                }
                float step = 1 / sx;
                lineWidth = lineWidth / sx;
                fillRect(x1 - lineWidth / 2.0f, y1 - lineWidth / 2.0f, x2 - x1 + lineWidth + step, lineWidth + step);
                return;
            }
        }
        
        predraw();
        currentColor.bind();
        TextureImpl.bindNone();
        
        LSR.start();
        LSR.vertex(x1, y1);
        LSR.vertex(x2, y2);
        LSR.end();
        
        postdraw();
    }
    
    /**
     * Draw the outline of the given shape.
     *
     * @param shape
     *            The shape to draw.
     * @param fill
     *            The fill type to apply
     */
    public void draw(Shape shape, ShapeFill fill) {
        glOperation(() -> {
            TextureImpl.bindNone();
            
            ShapeRenderer.draw(shape, fill);
            
            currentColor.bind();
        });
    }
    
    /**
     * Draw the the given shape filled in.
     *
     * @param shape
     *            The shape to fill.
     * @param fill
     *            The fill type to apply
     */
    public void fill(Shape shape, ShapeFill fill) {
        glOperation(() -> {
            TextureImpl.bindNone();
            
            ShapeRenderer.fill(shape, fill);
            
            currentColor.bind();
        });
    }
    
    /**
     * Draw the outline of the given shape.
     *
     * @param shape
     *            The shape to draw.
     */
    public void draw(Shape shape) {
        glOperation(() -> {
            TextureImpl.bindNone();
            currentColor.bind();
            
            ShapeRenderer.draw(shape);
            
        });
    }
    
    /**
     * Draw the the given shape filled in.
     *
     * @param shape
     *            The shape to fill.
     */
    public void fill(Shape shape) {
        glOperation(() -> {
            TextureImpl.bindNone();
            currentColor.bind();
            
            ShapeRenderer.fill(shape);
            
        });
    }
    
    /**
     * Draw the the given shape filled in with a texture
     *
     * @param shape
     *            The shape to texture.
     * @param image
     *            The image to tile across the shape
     */
    public void texture(Shape shape, Image image) {
        texture(shape, image, 0.01f, 0.01f, false);
    }
    
    /**
     * Draw the the given shape filled in with a texture
     *
     * @param shape
     *            The shape to texture.
     * @param image
     *            The image to tile across the shape
     * @param fill
     *            The shape fill to apply
     */
    public void texture(Shape shape, Image image, ShapeFill fill) {
        texture(shape, image, 0.01f, 0.01f, fill);
    }
    
    /**
     * Draw the the given shape filled in with a texture
     *
     * @param shape
     *            The shape to texture.
     * @param image
     *            The image to tile across the shape
     * @param fit
     *            True if we want to fit the image on to the shape
     */
    public void texture(Shape shape, Image image, boolean fit) {
        if (fit) {
            texture(shape, image, 1, 1, true);
        } else {
            texture(shape, image, 0.01f, 0.01f, false);
        }
    }
    
    /**
     * Draw the the given shape filled in with a texture
     *
     * @param shape
     *            The shape to texture.
     * @param image
     *            The image to tile across the shape
     * @param scaleX
     *            The scale to apply on the x axis for texturing
     * @param scaleY
     *            The scale to apply on the y axis for texturing
     */
    public void texture(Shape shape, Image image, float scaleX, float scaleY) {
        texture(shape, image, scaleX, scaleY, false);
    }
    
    /**
     * Draw the the given shape filled in with a texture
     *
     * @param shape
     *            The shape to texture.
     * @param image
     *            The image to tile across the shape
     * @param scaleX
     *            The scale to apply on the x axis for texturing
     * @param scaleY
     *            The scale to apply on the y axis for texturing
     * @param fit
     *            True if we want to fit the image on to the shape
     */
    public void texture(Shape shape, Image image, float scaleX, float scaleY, boolean fit) {
        glOperation(() -> {
            TextureImpl.bindNone();
            currentColor.bind();
            
            if (fit) {
                ShapeRenderer.textureFit(shape, image, scaleX, scaleY);
            } else {
                ShapeRenderer.texture(shape, image, scaleX, scaleY);
            }
            
        });
    }
    
    /**
     * Draw the the given shape filled in with a texture
     *
     * @param shape
     *            The shape to texture.
     * @param image
     *            The image to tile across the shape
     * @param scaleX
     *            The scale to apply on the x axis for texturing
     * @param scaleY
     *            The scale to apply on the y axis for texturing
     * @param fill
     *            The shape fill to apply
     */
    public void texture(Shape shape, Image image, float scaleX, float scaleY, ShapeFill fill) {
        glOperation(() -> {
            TextureImpl.bindNone();
            currentColor.bind();
            
            ShapeRenderer.texture(shape, image, scaleX, scaleY, fill);
            
        });
    }
    
    /**
     * Draw a rectangle to the canvas in the current colour
     *
     * @param x1
     *            The x coordinate of the top left corner
     * @param y1
     *            The y coordinate of the top left corner
     * @param width
     *            The width of the rectangle to draw
     * @param height
     *            The height of the rectangle to draw
     */
    public void drawRect(float x1, float y1, float width, float height) {
        drawLine(x1, y1, x1 + width, y1);
        drawLine(x1 + width, y1, x1 + width, y1 + height);
        drawLine(x1 + width, y1 + height, x1, y1 + height);
        drawLine(x1, y1 + height, x1, y1);
    }
    
    /**
     * Clear the clipping being applied. This will allow graphics to be drawn
     * anywhere on the screen
     */
    public void clearClip() {
        clip = null;
        glOperation(() -> GL.glDisable(GL11.GL_SCISSOR_TEST));
    }
    
    /**
     * Set clipping that controls which areas of the world will be drawn to.
     * Note that world clip is different from standard screen clip in that it's
     * defined in the space of the current world coordinate - i.e. it's affected
     * by translate, rotate, scale etc.
     *
     * @param x
     *            The x coordinate of the top left corner of the allowed area
     * @param y
     *            The y coordinate of the top left corner of the allowed area
     * @param width
     *            The width of the allowed area
     * @param height
     *            The height of the allowed area
     */
    public void setWorldClip(float x, float y, float width, float height) {
        glOperation(() -> {
            worldClipRecord = new Rectangle(x, y, width, height);
            
            GL.glEnable(GL11.GL_CLIP_PLANE0);
            worldClip.put(1).put(0).put(0).put(-x).flip();
            GL.glClipPlane(GL11.GL_CLIP_PLANE0, worldClip);
            GL.glEnable(GL11.GL_CLIP_PLANE1);
            worldClip.put(-1).put(0).put(0).put(x + width).flip();
            GL.glClipPlane(GL11.GL_CLIP_PLANE1, worldClip);
            
            GL.glEnable(GL11.GL_CLIP_PLANE2);
            worldClip.put(0).put(1).put(0).put(-y).flip();
            GL.glClipPlane(GL11.GL_CLIP_PLANE2, worldClip);
            GL.glEnable(GL11.GL_CLIP_PLANE3);
            worldClip.put(0).put(-1).put(0).put(y + height).flip();
            GL.glClipPlane(GL11.GL_CLIP_PLANE3, worldClip);
        });
    }
    
    /**
     * Clear world clipping setup. This does not effect screen clipping
     */
    public void clearWorldClip() {
        glOperation(() -> {
            worldClipRecord = null;
            GL.glDisable(GL11.GL_CLIP_PLANE0);
            GL.glDisable(GL11.GL_CLIP_PLANE1);
            GL.glDisable(GL11.GL_CLIP_PLANE2);
            GL.glDisable(GL11.GL_CLIP_PLANE3);
        });
    }
    
    /**
     * Set the world clip to be applied
     *
     * @see #setWorldClip(float, float, float, float)
     * @param clip
     *            The area still visible
     */
    public void setWorldClip(Rectangle clip) {
        if (clip == null) {
            clearWorldClip();
        } else {
            setWorldClip(clip.getX(), clip.getY(), clip.getWidth(), clip.getHeight());
        }
    }
    
    /**
     * Get the last set world clip or null of the world clip isn't set
     *
     * @return The last set world clip rectangle
     */
    public Rectangle getWorldClip() {
        return worldClipRecord;
    }
    
    /**
     * Set the clipping to apply to the drawing. Note that this clipping takes
     * no note of the transforms that have been applied to the context and is
     * always in absolute screen space coordinates.
     *
     * @param x
     *            The x coordinate of the top left corner of the allowed area
     * @param y
     *            The y coordinate of the top left corner of the allowed area
     * @param width
     *            The width of the allowed area
     * @param height
     *            The height of the allowed area
     */
    public void setClip(int x, int y, int width, int height) {
        glOperation(() -> {
            
            if (clip == null) {
                GL.glEnable(GL11.GL_SCISSOR_TEST);
                clip = new Rectangle(x, y, width, height);
            } else {
                clip.setBounds(x, y, width, height);
            }
            
            GL.glScissor(x, screenHeight - y - height, width, height);
        });
    }
    
    /**
     * Set the clipping to apply to the drawing. Note that this clipping takes
     * no note of the transforms that have been applied to the context and is
     * always in absolute screen space coordinates.
     *
     * @param rect
     *            The rectangle describing the clipped area in screen
     *            coordinates
     */
    public void setClip(Rectangle rect) {
        if (rect == null) {
            clearClip();
            return;
        }
        
        setClip((int) rect.getX(), (int) rect.getY(), (int) rect.getWidth(), (int) rect.getHeight());
    }
    
    /**
     * Return the currently applied clipping rectangle
     *
     * @return The current applied clipping rectangle or null if no clipping is
     *         applied
     */
    public Rectangle getClip() {
        return clip;
    }
    
    /**
     * Tile a rectangle with a pattern specifing the offset from the top corner
     * that one tile should match
     *
     * @param x
     *            The x coordinate of the rectangle
     * @param y
     *            The y coordinate of the rectangle
     * @param width
     *            The width of the rectangle
     * @param height
     *            The height of the rectangle
     * @param pattern
     *            The image to tile across the rectangle
     * @param offX
     *            The offset on the x axis from the top left corner
     * @param offY
     *            The offset on the y axis from the top left corner
     */
    public void fillRect(float x, float y, float width, float height, Image pattern, float offX, float offY) {
        int cols = (int) Math.ceil(width / pattern.getWidth()) + 2;
        int rows = (int) Math.ceil(height / pattern.getHeight()) + 2;
        
        Rectangle preClip = getWorldClip();
        setWorldClip(x, y, width, height);
        
        glOperation(() -> {
            // Draw all the quads we need
            for (int c = 0; c < cols; c++) {
                for (int r = 0; r < rows; r++) {
                    pattern.draw(c * pattern.getWidth() + x - offX, r * pattern.getHeight() + y - offY);
                }
            }
        });
        
        setWorldClip(preClip);
    }
    
    /**
     * Fill a rectangle on the canvas in the current color
     *
     * @param x1
     *            The x coordinate of the top left corner
     * @param y1
     *            The y coordinate of the top left corner
     * @param width
     *            The width of the rectangle to fill
     * @param height
     *            The height of the rectangle to fill
     */
    public void fillRect(float x1, float y1, float width, float height) {
        glOperation(() -> {
            TextureImpl.bindNone();
            currentColor.bind();
            
            GL.glBegin(GL11.GL_QUADS);
            GL.glVertex2f(x1, y1);
            GL.glVertex2f(x1 + width, y1);
            GL.glVertex2f(x1 + width, y1 + height);
            GL.glVertex2f(x1, y1 + height);
            GL.glEnd();
        });
    }
    
    /**
     * Draw an oval to the canvas
     *
     * @param x1
     *            The x coordinate of the top left corner of a box containing
     *            the oval
     * @param y1
     *            The y coordinate of the top left corner of a box containing
     *            the oval
     * @param width
     *            The width of the oval
     * @param height
     *            The height of the oval
     */
    public void drawOval(float x1, float y1, float width, float height) {
        drawOval(x1, y1, width, height, DEFAULT_SEGMENTS);
    }
    
    /**
     * Draw an oval to the canvas
     *
     * @param x1
     *            The x coordinate of the top left corner of a box containing
     *            the oval
     * @param y1
     *            The y coordinate of the top left corner of a box containing
     *            the oval
     * @param width
     *            The width of the oval
     * @param height
     *            The height of the oval
     * @param segments
     *            The number of line segments to use when drawing the oval
     */
    public void drawOval(float x1, float y1, float width, float height, int segments) {
        drawArc(x1, y1, width, height, segments, 0, 360);
    }
    
    /**
     * Draw an oval to the canvas
     *
     * @param x1
     *            The x coordinate of the top left corner of a box containing
     *            the arc
     * @param y1
     *            The y coordinate of the top left corner of a box containing
     *            the arc
     * @param width
     *            The width of the arc
     * @param height
     *            The height of the arc
     * @param start
     *            The angle the arc starts at
     * @param end
     *            The angle the arc ends at
     */
    public void drawArc(float x1, float y1, float width, float height, float start, float end) {
        drawArc(x1, y1, width, height, DEFAULT_SEGMENTS, start, end);
    }
    
    /**
     * Draw an oval to the canvas
     *
     * @param x1
     *            The x coordinate of the top left corner of a box containing
     *            the arc
     * @param y1
     *            The y coordinate of the top left corner of a box containing
     *            the arc
     * @param width
     *            The width of the arc
     * @param height
     *            The height of the arc
     * @param segments
     *            The number of line segments to use when drawing the arc
     * @param start
     *            The angle the arc starts at
     * @param end
     *            The angle the arc ends at
     */
    public void drawArc(float x1, float y1, float width, float height, int segments, float start, float end) {
        predraw();
        TextureImpl.bindNone();
        currentColor.bind();
        
        while (end < start) {
            end += 360;
        }
        
        float cx = x1 + width / 2.0f;
        float cy = y1 + height / 2.0f;
        
        LSR.start();
        int step = 360 / segments;
        
        for (int a = (int) start; a < (int) (end + step); a += step) {
            float ang = a;
            if (ang > end) {
                ang = end;
            }
            float x = (float) (cx + FastTrig.cos(Math.toRadians(ang)) * width / 2.0f);
            float y = (float) (cy + FastTrig.sin(Math.toRadians(ang)) * height / 2.0f);
            
            LSR.vertex(x, y);
        }
        LSR.end();
        postdraw();
    }
    
    /**
     * Fill an oval to the canvas
     *
     * @param x1
     *            The x coordinate of the top left corner of a box containing
     *            the oval
     * @param y1
     *            The y coordinate of the top left corner of a box containing
     *            the oval
     * @param width
     *            The width of the oval
     * @param height
     *            The height of the oval
     */
    public void fillOval(float x1, float y1, float width, float height) {
        fillOval(x1, y1, width, height, DEFAULT_SEGMENTS);
    }
    
    /**
     * Fill an oval to the canvas
     *
     * @param x1
     *            The x coordinate of the top left corner of a box containing
     *            the oval
     * @param y1
     *            The y coordinate of the top left corner of a box containing
     *            the oval
     * @param width
     *            The width of the oval
     * @param height
     *            The height of the oval
     * @param segments
     *            The number of line segments to use when filling the oval
     */
    public void fillOval(float x1, float y1, float width, float height, int segments) {
        fillArc(x1, y1, width, height, segments, 0, 360);
    }
    
    /**
     * Fill an arc to the canvas (a wedge)
     *
     * @param x1
     *            The x coordinate of the top left corner of a box containing
     *            the arc
     * @param y1
     *            The y coordinate of the top left corner of a box containing
     *            the arc
     * @param width
     *            The width of the arc
     * @param height
     *            The height of the arc
     * @param start
     *            The angle the arc starts at
     * @param end
     *            The angle the arc ends at
     */
    public void fillArc(float x1, float y1, float width, float height, float start, float end) {
        fillArc(x1, y1, width, height, DEFAULT_SEGMENTS, start, end);
    }
    
    /**
     * Fill an arc to the canvas (a wedge)
     *
     * @param x1
     *            The x coordinate of the top left corner of a box containing
     *            the arc
     * @param y1
     *            The y coordinate of the top left corner of a box containing
     *            the arc
     * @param width
     *            The width of the arc
     * @param height
     *            The height of the arc
     * @param segments
     *            The number of line segments to use when filling the arc
     * @param start
     *            The angle the arc starts at
     * @param end
     *            The angle the arc ends at
     */
    public void fillArc(float x1, float y1, float width, float height, int segments, float start, float end) {
        predraw();
        TextureImpl.bindNone();
        currentColor.bind();
        
        while (end < start) {
            end += 360;
        }
        
        float cx = x1 + width / 2.0f;
        float cy = y1 + height / 2.0f;
        
        GL.glBegin(GL11.GL_TRIANGLE_FAN);
        int step = 360 / segments;
        
        GL.glVertex2f(cx, cy);
        
        for (int a = (int) start; a < (int) (end + step); a += step) {
            float ang = a;
            if (ang > end) {
                ang = end;
            }
            
            float x = (float) (cx + FastTrig.cos(Math.toRadians(ang)) * width / 2.0f);
            float y = (float) (cy + FastTrig.sin(Math.toRadians(ang)) * height / 2.0f);
            
            GL.glVertex2f(x, y);
        }
        GL.glEnd();
        
        if (antialias) {
            GL.glBegin(GL11.GL_TRIANGLE_FAN);
            GL.glVertex2f(cx, cy);
            if (end != 360) {
                end -= 10;
            }
            
            for (int a = (int) start; a < (int) (end + step); a += step) {
                float ang = a;
                if (ang > end) {
                    ang = end;
                }
                
                float x = (float) (cx + FastTrig.cos(Math.toRadians(ang + 10)) * width / 2.0f);
                float y = (float) (cy + FastTrig.sin(Math.toRadians(ang + 10)) * height / 2.0f);
                
                GL.glVertex2f(x, y);
            }
            GL.glEnd();
        }
        
        postdraw();
    }
    
    /**
     * Draw a rounded rectangle
     *
     * @param x
     *            The x coordinate of the top left corner of the rectangle
     * @param y
     *            The y coordinate of the top left corner of the rectangle
     * @param width
     *            The width of the rectangle
     * @param height
     *            The height of the rectangle
     * @param cornerRadius
     *            The radius of the rounded edges on the corners
     */
    public void drawRoundRect(float x, float y, float width, float height, int cornerRadius) {
        drawRoundRect(x, y, width, height, cornerRadius, DEFAULT_SEGMENTS);
    }
    
    /**
     * Draw a rounded rectangle
     *
     * @param x
     *            The x coordinate of the top left corner of the rectangle
     * @param y
     *            The y coordinate of the top left corner of the rectangle
     * @param width
     *            The width of the rectangle
     * @param height
     *            The height of the rectangle
     * @param cornerRadius
     *            The radius of the rounded edges on the corners
     * @param segs
     *            The number of segments to make the corners out of
     */
    public void drawRoundRect(float x, float y, float width, float height, int cornerRadius, int segs) {
        if (cornerRadius < 0) {
            throw new IllegalArgumentException("corner radius must be > 0");
        }
        if (cornerRadius == 0) {
            drawRect(x, y, width, height);
            return;
        }
        
        int mr = (int) Math.min(width, height) / 2;
        // make sure that w & h are larger than 2*cornerRadius
        if (cornerRadius > mr) {
            cornerRadius = mr;
        }
        
        drawLine(x + cornerRadius, y, x + width - cornerRadius, y);
        drawLine(x, y + cornerRadius, x, y + height - cornerRadius);
        drawLine(x + width, y + cornerRadius, x + width, y + height - cornerRadius);
        drawLine(x + cornerRadius, y + height, x + width - cornerRadius, y + height);
        
        float d = cornerRadius * 2;
        // bottom right - 0, 90
        drawArc(x + width - d, y + height - d, d, d, segs, 0, 90);
        // bottom left - 90, 180
        drawArc(x, y + height - d, d, d, segs, 90, 180);
        // top right - 270, 360
        drawArc(x + width - d, y, d, d, segs, 270, 360);
        // top left - 180, 270
        drawArc(x, y, d, d, segs, 180, 270);
    }
    
    /**
     * Fill a rounded rectangle
     *
     * @param x
     *            The x coordinate of the top left corner of the rectangle
     * @param y
     *            The y coordinate of the top left corner of the rectangle
     * @param width
     *            The width of the rectangle
     * @param height
     *            The height of the rectangle
     * @param cornerRadius
     *            The radius of the rounded edges on the corners
     */
    public void fillRoundRect(float x, float y, float width, float height, int cornerRadius) {
        fillRoundRect(x, y, width, height, cornerRadius, DEFAULT_SEGMENTS);
    }
    
    /**
     * Fill a rounded rectangle
     *
     * @param x
     *            The x coordinate of the top left corner of the rectangle
     * @param y
     *            The y coordinate of the top left corner of the rectangle
     * @param width
     *            The width of the rectangle
     * @param height
     *            The height of the rectangle
     * @param cornerRadius
     *            The radius of the rounded edges on the corners
     * @param segs
     *            The number of segments to make the corners out of
     */
    public void fillRoundRect(float x, float y, float width, float height, int cornerRadius, int segs) {
        if (cornerRadius < 0) {
            throw new IllegalArgumentException("corner radius must be > 0");
        }
        if (cornerRadius == 0) {
            fillRect(x, y, width, height);
            return;
        }
        
        int mr = (int) Math.min(width, height) / 2;
        // make sure that w & h are larger than 2*cornerRadius
        if (cornerRadius > mr) {
            cornerRadius = mr;
        }
        
        float d = cornerRadius * 2;
        
        fillRect(x + cornerRadius, y, width - d, cornerRadius);
        fillRect(x, y + cornerRadius, cornerRadius, height - d);
        fillRect(x + width - cornerRadius, y + cornerRadius, cornerRadius, height - d);
        fillRect(x + cornerRadius, y + height - cornerRadius, width - d, cornerRadius);
        fillRect(x + cornerRadius, y + cornerRadius, width - d, height - d);
        
        // bottom right - 0, 90
        fillArc(x + width - d, y + height - d, d, d, segs, 0, 90);
        // bottom left - 90, 180
        fillArc(x, y + height - d, d, d, segs, 90, 180);
        // top right - 270, 360
        fillArc(x + width - d, y, d, d, segs, 270, 360);
        // top left - 180, 270
        fillArc(x, y, d, d, segs, 180, 270);
    }
    
    /**
     * Set the with of the line to be used when drawing line based primitives
     *
     * @param width
     *            The width of the line to be used when drawing line based
     *            primitives
     */
    public void setLineWidth(float width) {
        glOperation(() -> {
            lineWidth = width;
            LSR.setWidth(width);
            GL.glPointSize(width);
        });
    }
    
    /**
     * Get the width of lines being drawn in this context
     *
     * @return The width of lines being draw in this context
     */
    public float getLineWidth() {
        return lineWidth;
    }
    
    /**
     * Reset the line width in use to the default for this graphics context
     */
    public void resetLineWidth() {
        glOperation(() -> {
            
            Renderer.getLineStripRenderer().setWidth(1.0f);
            GL.glLineWidth(1.0f);
            GL.glPointSize(1.0f);
            
        });
    }
    
    /**
     * Indicate if we should antialias as we draw primitives
     *
     * @param anti
     *            True if we should antialias
     */
    public void setAntiAlias(boolean anti) {
        glOperation(() -> {
            antialias = anti;
            LSR.setAntiAlias(anti);
            if (anti) {
                GL.glEnable(GL11.GL_POLYGON_SMOOTH);
            } else {
                GL.glDisable(GL11.GL_POLYGON_SMOOTH);
            }
        });
    }
    
    /**
     * True if antialiasing has been turned on for this graphics context
     *
     * @return True if antialiasing has been turned on for this graphics context
     */
    public boolean isAntiAlias() {
        return antialias;
    }
    
    /**
     * Draw a string to the screen using the current font
     *
     * @param str
     *            The string to draw
     * @param x
     *            The x coordinate to draw the string at
     * @param y
     *            The y coordinate to draw the string at
     */
    public void drawString(String str, float x, float y) {
        glOperation(() -> {
            font.drawString(x, y, str, currentColor);
        });
    }
    
    /**
     * Draw an image to the screen
     *
     * @param image
     *            The image to draw to the screen
     * @param x
     *            The x location at which to draw the image
     * @param y
     *            The y location at which to draw the image
     * @param col
     *            The color to apply to the image as a filter
     */
    public void drawImage(Image image, float x, float y, Color col) {
        glOperation(() -> {
            image.draw(x, y, col);
            currentColor.bind();
        });
    }
    
    /**
     * Draw an animation to this graphics context
     *
     * @param anim
     *            The animation to be drawn
     * @param x
     *            The x position to draw the animation at
     * @param y
     *            The y position to draw the animation at
     */
    public void drawAnimation(Animation anim, float x, float y) {
        drawAnimation(anim, x, y, Color.white);
    }
    
    /**
     * Draw an animation to this graphics context
     *
     * @param anim
     *            The animation to be drawn
     * @param x
     *            The x position to draw the animation at
     * @param y
     *            The y position to draw the animation at
     * @param col
     *            The color to apply to the animation as a filter
     */
    public void drawAnimation(Animation anim, float x, float y, Color col) {
        glOperation(() -> {
            anim.draw(x, y, col);
            currentColor.bind();
        });
    }
    
    /**
     * Draw an image to the screen
     *
     * @param image
     *            The image to draw to the screen
     * @param x
     *            The x location at which to draw the image
     * @param y
     *            The y location at which to draw the image
     */
    public void drawImage(Image image, float x, float y) {
        drawImage(image, x, y, Color.white);
    }
    
    /**
     * Draw a section of an image at a particular location and scale on the
     * screen
     *
     * @param image
     *            The image to draw a section of
     * @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 the image)
     * @param srcy
     *            The y position of the rectangle to draw from this image (i.e.
     *            relative to the image)
     * @param srcx2
     *            The x position of the bottom right cornder of rectangle to
     *            draw from this image (i.e. relative to the image)
     * @param srcy2
     *            The t position of the bottom right cornder of rectangle to
     *            draw from this image (i.e. relative to the image)
     */
    public void drawImage(Image image, float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2) {
        glOperation(() -> {
            image.draw(x, y, x2, y2, srcx, srcy, srcx2, srcy2);
            currentColor.bind();
        });
    }
    
    /**
     * Draw a section of an image at a particular location and scale on the
     * screen
     *
     * @param image
     *            The image to draw a section of
     * @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 the image)
     * @param srcy
     *            The y position of the rectangle to draw from this image (i.e.
     *            relative to the image)
     * @param srcx2
     *            The x position of the bottom right cornder of rectangle to
     *            draw from this image (i.e. relative to the image)
     * @param srcy2
     *            The t position of the bottom right cornder of rectangle to
     *            draw from this image (i.e. relative to the image)
     */
    public void drawImage(Image image, float x, float y, float srcx, float srcy, float srcx2, float srcy2) {
        drawImage(image, x, y, x + image.getWidth(), y + image.getHeight(), srcx, srcy, srcx2, srcy2);
    }
    
    /**
     * Copy an area of the rendered screen into an image. The width and height
     * of the area are assumed to match that of the image
     *
     * @param target
     *            The target image
     * @param x
     *            The x position to copy from
     * @param y
     *            The y position to copy from
     */
    public void copyArea(Image target, int x, int y) {
        int format = target.getTexture().hasAlpha() ? GL11.GL_RGBA : GL11.GL_RGB;
        target.bind();
        GL.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0, format, x, screenHeight - (y + target.getHeight()), target.getTexture().getTextureWidth(), target.getTexture().getTextureHeight(), 0);
        target.ensureInverted();
    }
    
    /**
     * 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 single pixel in this graphics context
     *
     * @param x
     *            The x coordinate of the pixel to read
     * @param y
     *            The y coordinate of the pixel to read
     * @return The colour of the pixel at the specified location
     */
    public Color getPixel(int x, int y) {
        glOperation(() -> {
            GL.glReadPixels(x, screenHeight - y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, readBuffer);
        });
        
        return new Color(translate(readBuffer.get(0)), translate(readBuffer.get(1)), translate(readBuffer.get(2)), translate(readBuffer.get(3)));
    }
    
    /**
     * Get an ara of pixels as RGBA values into a buffer
     *
     * @param x
     *            The x position in the context to grab from
     * @param y
     *            The y position in the context to grab from
     * @param width
     *            The width of the area to grab from
     * @param height
     *            The hiehgt of the area to grab from
     * @param target
     *            The target buffer to grab into
     */
    public void getArea(int x, int y, int width, int height, ByteBuffer target) {
        if (target.capacity() < width * height * 4) {
            throw new IllegalArgumentException("Byte buffer provided to get area is not big enough");
        }
        
        glOperation(() -> {
            GL.glReadPixels(x, screenHeight - y - height, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, target);
        });
    }
    
    /**
     * Draw a section of an image at a particular location and scale on the
     * screen
     *
     * @param image
     *            The image to draw a section of
     * @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 the image)
     * @param srcy
     *            The y position of the rectangle to draw from this image (i.e.
     *            relative to the image)
     * @param srcx2
     *            The x position of the bottom right cornder of rectangle to
     *            draw from this image (i.e. relative to the image)
     * @param srcy2
     *            The t position of the bottom right cornder of rectangle to
     *            draw from this image (i.e. relative to the image)
     * @param col
     *            The color to apply to the image as a filter
     */
    public void drawImage(Image image, float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2, Color col) {
        glOperation(() -> {
            image.draw(x, y, x2, y2, srcx, srcy, srcx2, srcy2, col);
            currentColor.bind();
        });
    }
    
    /**
     * Draw a section of an image at a particular location and scale on the
     * screen
     *
     * @param image
     *            The image to draw a section of
     * @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 the image)
     * @param srcy
     *            The y position of the rectangle to draw from this image (i.e.
     *            relative to the image)
     * @param srcx2
     *            The x position of the bottom right cornder of rectangle to
     *            draw from this image (i.e. relative to the image)
     * @param srcy2
     *            The t position of the bottom right cornder of rectangle to
     *            draw from this image (i.e. relative to the image)
     * @param col
     *            The color to apply to the image as a filter
     */
    public void drawImage(Image image, float x, float y, float srcx, float srcy, float srcx2, float srcy2, Color col) {
        drawImage(image, x, y, x + image.getWidth(), y + image.getHeight(), srcx, srcy, srcx2, srcy2, col);
    }
    
    /**
     * Draw a line with a gradient between the two points.
     *
     * @param x1
     *            The starting x position to draw the line
     * @param y1
     *            The starting y position to draw the line
     * @param red1
     *            The starting position's shade of red
     * @param green1
     *            The starting position's shade of green
     * @param blue1
     *            The starting position's shade of blue
     * @param alpha1
     *            The starting position's alpha value
     * @param x2
     *            The ending x position to draw the line
     * @param y2
     *            The ending y position to draw the line
     * @param red2
     *            The ending position's shade of red
     * @param green2
     *            The ending position's shade of green
     * @param blue2
     *            The ending position's shade of blue
     * @param alpha2
     *            The ending position's alpha value
     */
    public void drawGradientLine(float x1, float y1, float red1, float green1, float blue1, float alpha1, float x2, float y2, float red2, float green2, float blue2, float alpha2) {
        glOperation(() -> {
            
            TextureImpl.bindNone();
            
            GL.glBegin(GL11.GL_LINES);
            
            GL.glColor4f(red1, green1, blue1, alpha1);
            GL.glVertex2f(x1, y1);
            
            GL.glColor4f(red2, green2, blue2, alpha2);
            GL.glVertex2f(x2, y2);
            
            GL.glEnd();
            
        });
    }
    
    /**
     * Draw a line with a gradient between the two points.
     *
     * @param x1
     *            The starting x position to draw the line
     * @param y1
     *            The starting y position to draw the line
     * @param Color1
     *            The starting position's color
     * @param x2
     *            The ending x position to draw the line
     * @param y2
     *            The ending y position to draw the line
     * @param Color2
     *            The ending position's color
     */
    public void drawGradientLine(float x1, float y1, Color Color1, float x2, float y2, Color Color2) {
        glOperation(() -> {
            
            TextureImpl.bindNone();
            
            GL.glBegin(GL11.GL_LINES);
            
            Color1.bind();
            GL.glVertex2f(x1, y1);
            
            Color2.bind();
            GL.glVertex2f(x2, y2);
            
            GL.glEnd();
            
        });
    }
    
    /**
     * Push the current state of the transform from this graphics contexts
     * onto the underlying graphics stack's transform stack. An associated
     * popTransform() must be performed to restore the state before the end
     * of the rendering loop.
     */
    public void pushTransform() {
        glOperation(() -> {
            
            FloatBuffer buffer;
            if (stackIndex >= stack.size()) {
                buffer = BufferUtils.createFloatBuffer(18);
                stack.add(buffer);
            } else {
                buffer = stack.get(stackIndex);
            }
            
            GL.glGetFloat(GL11.GL_MODELVIEW_MATRIX, buffer);
            buffer.put(16, sx);
            buffer.put(17, sy);
            stackIndex++;
            
        });
    }
    
    /**
     * Pop a previously pushed transform from the stack to the current. This should
     * only be called if a transform has been previously pushed.
     */
    public void popTransform() {
        if (stackIndex == 0) {
            throw new SlickException("Attempt to pop a transform that hasn't be pushed");
        }
        
        glOperation(() -> {
            stackIndex--;
            FloatBuffer oldBuffer = stack.get(stackIndex);
            GL.glLoadMatrix(oldBuffer);
            sx = oldBuffer.get(16);
            sy = oldBuffer.get(17);
        });
    }
    
    /**
     * Dispose this graphics context, this will release any underlying resourses. However
     * this will also invalidate it's use
     */
    public void destroy() {
        
    }
    
    private void glOperation(Runnable r) {
        predraw();
        r.run();
        postdraw();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy