From fff0080ddc3b280f280f81efd6ce38d8ed9517f9 Mon Sep 17 00:00:00 2001 From: fd Date: Sun, 15 Feb 2015 18:51:07 -0500 Subject: [PATCH] Combo Color ordering General Image scaling Score fixed size width Spinner centering (slick has bad centering) Broke Ranking Panel --- src/itdelatrisu/opsu/GameData.java | 72 +- src/itdelatrisu/opsu/GameImage.java | 253 ++++- src/itdelatrisu/opsu/Opsu.java | 9 +- src/itdelatrisu/opsu/OsuHitObject.java | 5 + src/itdelatrisu/opsu/OsuParser.java | 35 +- src/itdelatrisu/opsu/Utils.java | 4 +- src/org/newdawn/slick/Image.java | 1403 ++++++++++++++++++++++++ 7 files changed, 1701 insertions(+), 80 deletions(-) create mode 100644 src/org/newdawn/slick/Image.java diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 80617239..fdd4176f 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -457,6 +457,35 @@ public class GameData { } } } + /** + * Draws a string of scoreSymbols. + * @param str the string to draw + * @param x the starting x coordinate + * @param y the y coordinate + * @param scale the scale to apply + * @param rightAlign align right (true) or left (false) + */ + private void drawFixedSizeSymbolString(String str, int x, int y, float scale, float fixedsize, boolean rightAlign) { + char[] c = str.toCharArray(); + int cx = x; + if (rightAlign) { + for (int i = c.length - 1; i >= 0; i--) { + Image digit = getScoreSymbolImage(c[i]); + if (scale != 1.0f) + digit = digit.getScaledCopy(scale); + cx -= fixedsize; + digit.draw(cx + (fixedsize-digit.getWidth())/2, y); + } + } else { + for (int i = 0; i < c.length; i++) { + Image digit = getScoreSymbolImage(c[i]); + if (scale != 1.0f) + digit = digit.getScaledCopy(scale); + digit.draw(cx + (fixedsize-digit.getWidth())/2, y); + cx += fixedsize; + } + } + } /** * Draws game elements: @@ -470,28 +499,28 @@ public class GameData { int marginX = (int) (width * 0.008f); // score - drawSymbolString((scoreDisplay < 100000000) ? String.format("%08d", scoreDisplay) : Long.toString(scoreDisplay), - width - marginX, 0, 1.0f, true); + drawFixedSizeSymbolString((scoreDisplay < 100000000) ? String.format("%08d", scoreDisplay) : Long.toString(scoreDisplay), + width - marginX, 0, 1.0f, getScoreSymbolImage('0').getWidth()-2, true); // score percentage int symbolHeight = getScoreSymbolImage('0').getHeight(); float scorePercent = getScorePercent(); drawSymbolString( String.format((scorePercent < 10f) ? "0%.2f%%" : "%.2f%%", scorePercent), - width - marginX, symbolHeight, 0.75f, true + width - marginX, symbolHeight, 0.60f, true ); // map progress circle g.setAntiAlias(true); g.setLineWidth(2f); g.setColor(Color.white); - int circleX = width - marginX - ( // max width: "100.00%" + float circleDiameter = symbolHeight * 0.60f; + int circleX = (int) (width - marginX - ( // max width: "100.00%" getScoreSymbolImage('1').getWidth() + getScoreSymbolImage('0').getWidth() * 4 + getScoreSymbolImage('.').getWidth() + getScoreSymbolImage('%').getWidth() - ); - float circleDiameter = symbolHeight * 0.75f; + ) * 0.60f - circleDiameter); g.drawOval(circleX, symbolHeight, circleDiameter, circleDiameter); OsuFile osu = MusicController.getOsuFile(); @@ -621,18 +650,16 @@ public class GameData { * @param osu the OsuFile */ public void drawRankingElements(Graphics g, OsuFile osu) { - // grade - Grade grade = getGrade(); - if (grade != Grade.NULL) - grade.getLargeImage().draw(width * 0.985f - grade.getLargeImage().getWidth(), height * 0.09f); - + + float marginX = width * 0.01f, marginY = height * 0.025f; // header & "Ranking" text Image rankingTitle = GameImage.RANKING_TITLE.getImage(); float rankingHeight = (rankingTitle.getHeight() * 0.75f) + 3; + + g.setColor(Utils.COLOR_BLACK_ALPHA); g.fillRect(0, 0, width, rankingHeight); rankingTitle.draw((width * 0.97f) - rankingTitle.getWidth(), 0); - float marginX = width * 0.01f, marginY = height * 0.01f; Utils.FONT_LARGE.drawString(marginX, marginY, String.format("%s - %s [%s]", osu.getArtist(), osu.getTitle(), osu.version), Color.white); Utils.FONT_MEDIUM.drawString(marginX, marginY + Utils.FONT_LARGE.getLineHeight() - 6, @@ -645,19 +672,20 @@ public class GameData { Image rankingPanel = GameImage.RANKING_PANEL.getImage(); int rankingPanelWidth = rankingPanel.getWidth(); int rankingPanelHeight = rankingPanel.getHeight(); - rankingPanel.draw(0, rankingHeight - (rankingHeight / 10f)); + rankingPanel.draw(0, rankingHeight);//rankingHeight - (rankingHeight / 10f)); - float symbolTextScale = (height / 15f) / getScoreSymbolImage('0').getHeight(); - float rankResultScale = (height * 0.03f) / hitResults[HIT_300].getHeight(); + float scoreTextScale = 1.2f; //(height / 15f) / getScoreSymbolImage('0').getHeight(); + float symbolTextScale = 1.2f; //(height / 15f) / getScoreSymbolImage('0').getHeight(); + float rankResultScale = 0.5f;//(height * 0.03f) / hitResults[HIT_300].getHeight(); // score - drawSymbolString((score < 100000000) ? String.format("%08d", score) : Long.toString(score), - (int) (width * 0.18f), height / 6, symbolTextScale, false); + drawFixedSizeSymbolString((score < 100000000) ? String.format("%08d", score) : Long.toString(score), + (int) (width * 0.18f), (int) (rankingHeight+50), scoreTextScale, getScoreSymbolImage('0').getWidth()*scoreTextScale-2, false); // result counts - float resultInitialX = rankingPanelWidth * 0.20f; - float resultInitialY = rankingHeight + (rankingPanelHeight * 0.27f) + (rankingHeight / 10f); - float resultHitInitialX = rankingPanelWidth * 0.05f; + float resultInitialX = 150;// rankingPanelWidth * 0.20f; + float resultInitialY = rankingHeight + (rankingPanelHeight * 0.20f) + (rankingHeight / 10f); + float resultHitInitialX = 0;//rankingPanelWidth * 0.05f; float resultHitInitialY = resultInitialY + (getScoreSymbolImage('0').getHeight() * symbolTextScale); float resultOffsetX = rankingPanelWidth / 2f; float resultOffsetY = rankingPanelHeight * 0.2f; @@ -700,6 +728,10 @@ public class GameData { (height * 0.99f) - GameImage.RANKING_PERFECT.getImage().getHeight() ); } + // grade + Grade grade = getGrade(); + if (grade != Grade.NULL) + grade.getLargeImage().draw(width * 0.985f - grade.getLargeImage().getWidth(), rankingHeight+marginY); // mod icons int modWidth = GameMod.AUTO.getImage().getWidth(); diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index ec6a3ff3..573d79df 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -35,31 +35,31 @@ public enum GameImage { CURSOR ("cursor", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(1 + ((h - 600) / 1000f)); + return img.getScaledCopy(h / 768f); } }, CURSOR_MIDDLE ("cursormiddle", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(1 + ((h - 600) / 1000f)); + return img.getScaledCopy(h / 768f); } }, CURSOR_TRAIL ("cursortrail", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(1 + ((h - 600) / 1000f)); + return img.getScaledCopy(h / 768f); } }, CURSOR_OLD ("cursor2", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(1 + ((h - 600) / 1000f)); + return img.getScaledCopy(h / 768f); } }, CURSOR_TRAIL_OLD ("cursortrail2", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(1 + ((h - 600) / 1000f)); + return img.getScaledCopy(h / 768f); } }, @@ -183,125 +183,280 @@ public enum GameImage { SCOREBAR_KI ("scorebar-ki", "png"), SCOREBAR_KI_DANGER ("scorebar-kidanger", "png"), SCOREBAR_KI_DANGER2 ("scorebar-kidanger2", "png"), - HIT_MISS ("hit0", "png"), - HIT_50 ("hit50", "png"), - HIT_100 ("hit100", "png"), - HIT_300 ("hit300", "png"), - HIT_100K ("hit100k", "png"), - HIT_300K ("hit300k", "png"), - HIT_300G ("hit300g", "png"), + HIT_MISS ("hit0", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + HIT_50 ("hit50", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + HIT_100 ("hit100", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + HIT_300 ("hit300", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + HIT_100K ("hit100k", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + HIT_300K ("hit300k", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + HIT_300G ("hit300g", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, HIT_SLIDER10 ("sliderpoint10", "png"), HIT_SLIDER30 ("sliderpoint30", "png"), RANKING_SS ("ranking-X", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h / 2f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_SS_SMALL ("ranking-X-small", "png"), RANKING_SSH ("ranking-XH", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h / 2f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_SSH_SMALL ("ranking-XH-small", "png"), RANKING_S ("ranking-S", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h / 2f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_S_SMALL ("ranking-S-small", "png"), RANKING_SH ("ranking-SH", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h / 2f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_SH_SMALL ("ranking-SH-small", "png"), RANKING_A ("ranking-A", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h / 2f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_A_SMALL ("ranking-A-small", "png"), RANKING_B ("ranking-B", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h / 2f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_B_SMALL ("ranking-B-small", "png"), RANKING_C ("ranking-C", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h / 2f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_C_SMALL ("ranking-C-small", "png"), RANKING_D ("ranking-D", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h / 2f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_D_SMALL ("ranking-D-small", "png"), RANKING_PANEL ("ranking-panel", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h * 0.63f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_PERFECT ("ranking-perfect", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h * 0.16f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_TITLE ("ranking-title", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h * 0.15f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_MAXCOMBO ("ranking-maxcombo", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h * 0.05f) / img.getHeight()); + return img.getScaledCopy(h / 768f); } }, RANKING_ACCURACY ("ranking-accuracy", "png") { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h * 0.05f) / img.getHeight()); + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_0 ("default-0", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_1 ("default-1", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_2 ("default-2", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_3 ("default-3", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_4 ("default-4", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_5 ("default-5", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_6 ("default-6", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_7 ("default-7", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_8 ("default-8", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + DEFAULT_9 ("default-9", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_0 ("score-0", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_1 ("score-1", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_2 ("score-2", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_3 ("score-3", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_4 ("score-4", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_5 ("score-5", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_6 ("score-6", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_7 ("score-7", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_8 ("score-8", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_9 ("score-9", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_COMMA ("score-comma", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_DOT ("score-dot", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_PERCENT ("score-percent", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); + } + }, + SCORE_X ("score-x", "png") { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy(h / 768f); } }, - DEFAULT_0 ("default-0", "png"), - DEFAULT_1 ("default-1", "png"), - DEFAULT_2 ("default-2", "png"), - DEFAULT_3 ("default-3", "png"), - DEFAULT_4 ("default-4", "png"), - DEFAULT_5 ("default-5", "png"), - DEFAULT_6 ("default-6", "png"), - DEFAULT_7 ("default-7", "png"), - DEFAULT_8 ("default-8", "png"), - DEFAULT_9 ("default-9", "png"), - SCORE_0 ("score-0", "png"), - SCORE_1 ("score-1", "png"), - SCORE_2 ("score-2", "png"), - SCORE_3 ("score-3", "png"), - SCORE_4 ("score-4", "png"), - SCORE_5 ("score-5", "png"), - SCORE_6 ("score-6", "png"), - SCORE_7 ("score-7", "png"), - SCORE_8 ("score-8", "png"), - SCORE_9 ("score-9", "png"), - SCORE_COMMA ("score-comma", "png"), - SCORE_DOT ("score-dot", "png"), - SCORE_PERCENT ("score-percent", "png"), - SCORE_X ("score-x", "png"), LIGHTING ("lighting", "png"), LIGHTING1 ("lighting1", "png"), diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java index 1a81726c..ec2e6d5d 100644 --- a/src/itdelatrisu/opsu/Opsu.java +++ b/src/itdelatrisu/opsu/Opsu.java @@ -102,6 +102,11 @@ public class Opsu extends StateBasedGame { } catch (FileNotFoundException e) { Log.error(e); } + try { + System.setOut(new PrintStream(new FileOutputStream("jnlog.txt", false))); + } catch (FileNotFoundException e) { + Log.error(e); + } Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { @@ -113,12 +118,12 @@ public class Opsu extends StateBasedGame { Options.parseOptions(); // only allow a single instance - try { + /*try { SERVER_SOCKET = new ServerSocket(Options.getPort()); } catch (IOException e) { ErrorHandler.error(String.format("Another program is already running on port %d.", Options.getPort()), e, false); System.exit(1); - } + }*/ // set path for lwjgl natives - NOT NEEDED if using JarSplice File nativeDir = new File("./target/natives/"); diff --git a/src/itdelatrisu/opsu/OsuHitObject.java b/src/itdelatrisu/opsu/OsuHitObject.java index fec96502..46598ccd 100644 --- a/src/itdelatrisu/opsu/OsuHitObject.java +++ b/src/itdelatrisu/opsu/OsuHitObject.java @@ -296,4 +296,9 @@ public class OsuHitObject { * @return true if new combo */ public boolean isNewCombo() { return (type & TYPE_NEWCOMBO) > 0; } + + public int getComboSkip() { + return (type>>4); + } + } diff --git a/src/itdelatrisu/opsu/OsuParser.java b/src/itdelatrisu/opsu/OsuParser.java index 08999f98..de819c93 100644 --- a/src/itdelatrisu/opsu/OsuParser.java +++ b/src/itdelatrisu/opsu/OsuParser.java @@ -139,11 +139,12 @@ public class OsuParser { private static OsuFile parseFile(File file, File dir, ArrayList osuFiles, boolean parseObjects) { OsuFile osu = new OsuFile(file); + String version =""; try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) { - // initialize timing point list osu.timingPoints = new ArrayList(); + version = in.readLine(); String line = in.readLine(); String tokens[] = null; while (line != null) { @@ -491,6 +492,8 @@ public class OsuParser { } break; default: + //System.out.println("Dead: "+line+" "+file); + line = in.readLine(); break; } @@ -500,8 +503,10 @@ public class OsuParser { } // if no custom colors, use the default color scheme - if (osu.combo == null) + if (osu.combo == null){ + System.out.println("Default Combo "+version+" "+file+" "+osu.combo); osu.combo = Utils.DEFAULT_COMBO; + } // parse hit objects now? if (parseObjects) @@ -526,6 +531,7 @@ public class OsuParser { + osu.hitObjectSlider + osu.hitObjectSpinner)]; try (BufferedReader in = new BufferedReader(new FileReader(osu.getFile()))) { + String version = in.readLine(); String line = in.readLine(); while (line != null) { line = line.trim(); @@ -544,17 +550,24 @@ public class OsuParser { int comboNumber = 1; // combo number int objectIndex = 0; + boolean first = true; while ((line = in.readLine()) != null && objectIndex < osu.objects.length) { line = line.trim(); - if (!isValidLine(line)) + if (!isValidLine(line)){ + System.out.println("Not Valid :"+line); continue; + } if (line.charAt(0) == '[') break; // lines must have at minimum 5 parameters int tokenCount = line.length() - line.replace(",", "").length(); - if (tokenCount < 4) + if (tokenCount < 4){ + System.out.println("(tokenCount < 4 :"+line); + continue; + + } try { // create a new OsuHitObject for each line @@ -563,9 +576,17 @@ public class OsuParser { // set combo info // - new combo: get next combo index, reset combo number // - else: maintain combo index, increase combo number - if ((hitObject.isNewCombo() && !hitObject.isSpinner()) || objectIndex == 0) { - comboIndex = (comboIndex + 1) % osu.combo.length; - comboNumber = 1; + if (((hitObject.isNewCombo()|| first) && !hitObject.isSpinner()) ) { + int skip = 1 + hitObject.getComboSkip(); + + for(int i=0; i < skip; i++){ + comboIndex = (comboIndex + 1) % osu.combo.length; + comboNumber = 1; + } + first=false; + } + if(hitObject.getType()>15){ + System.out.println(line+" "+hitObject.isCircle()+" "+hitObject.isSlider()+" "+hitObject.isSpinner()); } hitObject.setComboIndex(comboIndex); hitObject.setComboNumber(comboNumber++); diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 39270d73..e42af148 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -86,8 +86,8 @@ public class Utils { /** The default map colors, used when a map does not provide custom colors. */ public static final Color[] DEFAULT_COMBO = { - COLOR_GREEN_OBJECT, COLOR_BLUE_OBJECT, - COLOR_RED_OBJECT, COLOR_ORANGE_OBJECT + COLOR_ORANGE_OBJECT, COLOR_GREEN_OBJECT, + COLOR_BLUE_OBJECT, COLOR_RED_OBJECT, }; /** Game fonts. */ diff --git a/src/org/newdawn/slick/Image.java b/src/org/newdawn/slick/Image.java new file mode 100644 index 00000000..731cd180 --- /dev/null +++ b/src/org/newdawn/slick/Image.java @@ -0,0 +1,1403 @@ +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 + */ +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 it's 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 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 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 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); + } + + /** + * @see java.lang.Object#toString() + */ + 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])); + } + } + + /** + * 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; + } +}