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

com.github.alex1304.jdash.graphics.SpriteFactory Maven / Gradle / Ivy

There is a newer version: 3.3.10
Show newest version
package com.github.alex1304.jdash.graphics;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.RGBImageFilter;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Predicate;

import javax.imageio.ImageIO;

import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.plist.XMLPropertyListConfiguration;

import com.github.alex1304.jdash.entity.IconType;
import com.github.alex1304.jdash.exception.SpriteLoadException;
import com.github.alex1304.jdash.util.Utils;

public class SpriteFactory {
	public static final Map COLORS = colors();
	
	private static BufferedImage spriteImg;
	private static Map> sprites;
	
	public BufferedImage makeSprite(IconType type, int id, int color1Id, int color2Id, boolean withGlowOutline) {
		if (!COLORS.containsKey(color1Id)) {
			throw new IllegalArgumentException("Color1 ID=" + color1Id + " does not exist");
		}
		if (!COLORS.containsKey(color2Id)) {
			throw new IllegalArgumentException("Color2 ID=" + color2Id + " does not exist");
		}
		List spList = sprites.get(type.name() + id);
		if (spList == null) {
			throw new IllegalArgumentException("Sprite ID=" + id + " for icon type " + type.name() + " does not exist");
		}
		spList = new ArrayList<>(spList);
		BufferedImage img = new BufferedImage(250, 250, spriteImg.getType());
		Graphics2D g = img.createGraphics();
		if (!withGlowOutline) {
			spList.removeIf(sp -> sp.getName().contains("_glow_"));
		}
		orderSprites(spList);
		for (Sprite sp : spList) {
			// Variables
			String name = sp.getName();
			int x = sp.getRectangle().x;
			int y = sp.getRectangle().y;
			int width = sp.getSizeX();
			int height = sp.getSizeY();
			int offX = sp.getOffsetX();
			int offY = sp.getOffsetY();
			int realWidth = sp.isRotated() ? sp.getRectangle().height : sp.getRectangle().width;
			int realHeight = sp.isRotated() ? sp.getRectangle().width : sp.getRectangle().height;
			Image subimg = spriteImg.getSubimage(x, y, realWidth, realHeight);
			int centerX = width / 2;
			int centerY = height / 2;
			// Fix orientation if rotated
			if (sp.isRotated()) {
				subimg = rotate(subimg, -90);
			}
			// Reduce brightness of robot/spider back legs
			if (name.contains("001D") && !name.contains("_glow_")) {
				subimg = reduceBrightness(subimg);
			}
			// Offset modifiers for robot legs
			if (name.contains("robot")) {
				if (name.contains(id + "_02_")) {
					subimg = rotate(subimg, 45);
					offX -= name.contains("001D") ? 50 : 40;
					offY -= 20;
					if (name.contains("_2_")) {
						switch (id) {
							case 2:
							case 5:
							case 6:
							case 8:
							case 9:
							case 11:
							case 12:
							case 15:
							case 17:
							case 24:
								offX += 15;
								offY -= 5;
								break;
							case 7:
							case 10:
							case 19:
							case 20:
								offX += 7;
								break;
							case 13:
								offX += 10;
								offY -= 4;
								break;
							case 18:
								offX -= 1;
								offY -= 1;
								break;
							case 21:
							case 25:
								offX += 12;
								break;
							case 22:
								offY -= 5;
								break;
							case 3:
							case 26:
								offX += 1;
								break;
							case 23:
								offX -= 3;
								offY -= 2;
								break;
						}
					}
				} else if (name.contains(id + "_03_")) {
					subimg = rotate(subimg, -45);
					offX -= name.contains("001D") ? 40 : 30;
					offY -= id == 21 && !name.contains("_2_") ? 52 : 60;
				} else if (name.contains(id + "_04_")) {
					offX -= name.contains("001D") ? 10 : 00;
					offY -= 70;
				}
			}
			// Offset modifiers for spider legs
			if (name.contains("spider")) {
				if (name.contains(id + "_02_") && name.endsWith("1D")) {
					offX += 18;
					offY -= 38;
				} else if (name.contains(id + "_02_") && name.endsWith("1DD")) {
					offX += 55;
					offY -= 38;
					if (name.contains("_2_")) {
						offX += 1;
					}
					subimg = horizontalFlip(subimg);
				} else if (name.contains(id + "_02_")) {
					offX -= 16;
					offY -= 38;
				} else if (name.contains(id + "_03_")) {
					offX -= 86;
					offY -= 38;
					if (id == 7) {
						offX += 15;
						offY += 13;
					}
					if (id == 15) {
						offX += 5;
						offY += 3;
					}
					if (name.contains("_2_")) {
						switch (id) {
							case 16:
								offY += 5;
								break;
							case 2:
							case 3:
								offX += 25;
								break;
							case 10:
								offX += 18;
								offY -= 5;
								break;
						}
					}
					if (name.contains("_glow_")) {
						offY += 3;
					}
					subimg = rotate(subimg, 45);
				} else if (name.contains(id + "_04_")) {
					offX -= 30;
					offY -= 20;
				}
			}
			
			// Apply player colors
			if (name.contains("_glow_")) {
				subimg = applyColor(subimg, getGlowColor(color1Id, color2Id));
			} else if (name.contains("_2_00")) {
				subimg = applyColor(subimg, COLORS.get(color2Id));
			} else if (!name.contains("extra") && !name.contains("_3_00")) {
				subimg = applyColor(subimg, COLORS.get(color1Id));
			}
			// Draw the result
			int drawX = 100 - centerX + offX;
			int drawY = 100 - centerY - offY;
			int drawOffX = 0, drawOffY;
			switch (type) {
				case ROBOT:
					drawOffY = -20;
					break;
				case SPIDER:
					drawOffX = 6;
					drawOffY = -5;
					break;
				case UFO:
					drawOffY = 30;
					break;
				default:
					drawOffY = 0;
					break;
			}
			g.drawImage(subimg, 25 + drawOffX + drawX, 25 + drawOffY + drawY, null);
		}
		g.dispose();
		if (withGlowOutline && type != IconType.ROBOT && type != IconType.SPIDER) {
			addGlow(img, color1Id, color2Id);
		}
		return img;
	}
	
	private static void orderSprites(List spList) {
		Collections.reverse(spList);
		pushSpriteToBackIf(spList, sp -> sp.getName().contains("_2_"));
		pullSpriteToFrontIf(spList, sp -> sp.getName().matches("(robot|spider)_[0-9]{2,3}_(02|03|04)_.*"));
		dupeSpriteIf(spList, sp -> sp.getName().matches("robot_[0-9]{2,3}_(02|03|04)_.*"), 1, false);
		dupeSpriteIf(spList, sp -> sp.getName().matches("spider_[0-9]{2,3}_02_.*") && !sp.getName().contains("extra"), 2, false);
		pullSpriteToFrontIf(spList, sp -> sp.getName().matches("robot_[0-9]{2,3}_02_.*") && !sp.getName().endsWith("D"));
		pullSpriteToFrontIf(spList, sp -> sp.getName().matches("robot_[0-9]{2,3}_04_.*") && !sp.getName().endsWith("D"));
		pushSpriteToBackIf(spList, sp -> !sp.getName().contains("_2_") && sp.getName().endsWith("D"));
		pushSpriteToBackIf(spList, sp -> sp.getName().contains("_2_") && sp.getName().endsWith("D"));
		pushSpriteToBackIf(spList, sp -> sp.getName().matches("spider_[0-9]{2,3}_04_.*"));
		pullSpriteToFrontIf(spList, sp -> sp.getName().contains("extra"));
		pushSpriteToBackIf(spList, sp -> sp.getName().contains("_glow_"));
	}
	
	private static void pullSpriteToFrontIf(List spList, Predicate cond) {
		int offset = 0;
		for (int i = 0 ; i < spList.size() ; i++) {
			Sprite sp = spList.get(i - offset);
			if (cond.test(sp)) {
				spList.remove(i - offset);
				spList.add(sp);
				offset++;
			}
		}
	}
	
	private static void pushSpriteToBackIf(List spList, Predicate cond) {
		for (int i = 0 ; i < spList.size() ; i++) {
			Sprite sp = spList.get(i);
			if (cond.test(sp)) {
				spList.remove(i);
				spList.add(0, sp);
			}
		}
	}
	
	private static void dupeSpriteIf(List spList, Predicate cond, int nbDup, boolean front) {
		final int initialSize = spList.size();
		int offset = 0;
		for (int i = 0 ; i < initialSize ; i++) {
			Sprite sp = spList.get(i + offset);
			if (cond.test(sp)) {
				Sprite dupe = sp.duplicate();
				for (int d = 0 ; d < nbDup ; d++) {
					if (front) {
						spList.add(dupe);
					} else {
						spList.add(0, dupe);
						offset++;
					}
					dupe = dupe.duplicate();
				}
			}
		}
	}

	private static Image rotate(Image img, double deg) {
		deg = deg % 360 + (Math.abs(deg) > 180 ? -Math.signum(deg) * 360 : 0);
		double rad = Math.toRadians(deg);
		int width = img.getWidth(null);
		int height = img.getHeight(null);
		// surrounding rectangle
		Rectangle2D newImgRect = AffineTransform.getRotateInstance(rad, 0, 0)
				.createTransformedShape(new Rectangle(width, height)).getBounds2D();
		int newWidth = (int) newImgRect.getWidth();
		int newHeight = (int) newImgRect.getHeight();
		BufferedImage newImage = new BufferedImage(newWidth, newHeight, spriteImg.getType());
		Graphics2D g2 = newImage.createGraphics();
		int tx, ty;
		if (deg < 0 && deg >= -90) {
			tx = 0;
			ty = (int) (width * Math.abs(Math.sin(rad)));
		} else if (deg < 90 && deg >= 0) {
			tx = (int) (height * Math.abs(Math.sin(rad)));
			ty = 0;
		} else if (deg < -90 && deg >= -180) {
			tx = (int) (width * Math.abs(Math.cos(rad)));
			ty = newHeight;
		} else {
			tx = newWidth;
			ty = (int) (height * Math.abs(Math.cos(rad)));
		}
		g2.translate(tx, ty);
		g2.rotate(rad, 0, 0);
		g2.drawImage(img, 0, 0, null);
		return newImage;
	}
	
	private static Image horizontalFlip(Image img) {
		int width = img.getWidth(null), height = img.getHeight(null);
		BufferedImage newImg = new BufferedImage(width, height, spriteImg.getType());
		Graphics2D g = newImg.createGraphics();
		AffineTransform flipTr = AffineTransform.getScaleInstance(-1, 1);
		flipTr.concatenate(AffineTransform.getTranslateInstance(-width, 0));
		g.transform(flipTr);
		g.drawImage(img, 0, 0, null);
		return newImg;
	}
	
	private static Image applyColor(Image img, Color color) {
		return Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(img.getSource(), new RGBImageFilter() {
			@Override
			public int filterRGB(int x, int y, int rgb) {
				return rgb & color.getRGB();
			}
		}));
	}
	
	private static Image reduceBrightness(Image img) {
		return Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(img.getSource(), new RGBImageFilter() {
			@Override
			public int filterRGB(int x, int y, int rgb) {
				return rgb & 0xFF808080;
			}
		}));
	}
	
	private static Color getGlowColor(int color1Id, int color2Id) {
		if (color2Id == 15) {
			color2Id = color1Id == 15 ? 12 : color1Id;
		}
		return COLORS.get(color2Id);
	}
	
	private static void addGlow(BufferedImage img, int color1Id, int color2Id) {
		// White glow if both colors are black. If color2 is black, use color1 instead.
		final Color color = getGlowColor(color1Id, color2Id);
		final int w = img.getWidth(), h = img.getHeight();
		final int treshold = 120;
		final int glowWidth = 4;
		// create an array of distances (1 per pixel) and fill it with -1's
		int[][] distances = new int[h][w];
		for (int i = 0 ; i < h ; i++) {
			Arrays.fill(distances[i], -1);
		}
		// for each pixel, mark the black ones as distance 0, and add them to a deque
		ArrayDeque deque = new ArrayDeque<>();
		for (int y = 0; y < h; y++) {
		    for (int x = 0; x < w; x++) {
				int data = img.getRGB(x, y);
				int alpha = (data & 0xff000000) >> 24;
				int red = (data & 0x00ff0000) >> 16;
				int green = (data & 0x0000ff00) >> 8;
				int blue = data & 0x000000ff;
				if (red < treshold && green < treshold && blue < treshold && (alpha > 255 - treshold || alpha == -1)) {
					distances[y][x] = 0;
					deque.add(new Point(x, y));
				} 
		    }
		}
		// for each pixel in deque, look at pixels immediately around that are -1,
		// change them to the value of distance + 1 and add them to deque
		while (!deque.isEmpty()) {
			Point pix = deque.remove();
			int pixDistance = distances[pix.y][pix.x];
			for (int y = Math.max(0, pix.y - 1) ; y <= Math.min(pix.y + 1, h - 1) ; y++) {
				for (int x = Math.max(0, pix.x - 1) ; x <= Math.min(pix.x + 1, w - 1) ; x++) {
					int lookAtDistance = distances[y][x];
					if (lookAtDistance == -1) {
						distances[y][x] = pixDistance + 1;
						deque.add(new Point(x, y));
					}
				}
			}
		}
		// Colorize all pixels that have a distance value less than or equal to glowWidth
		for (int y = 0; y < h; y++) {
		    for (int x = 0; x < w; x++) {
				int alpha = (img.getRGB(x, y) & 0xff000000) >> 24;
		    	// Apply color only if transparent pixel
		    	if (alpha != -1 && alpha < treshold && distances[y][x] <= glowWidth) {
		    		img.setRGB(x, y, color.getRGB());
		    	}
		    }
		}
	}
	
	@Override
	public String toString() {
		return sprites.toString();
	}
	
	private static void loadSprites(XMLPropertyListConfiguration spritePlist) {
		sprites = new HashMap<>();
		final Map prefixes = new LinkedHashMap<>();
		prefixes.put("frames.player_ball_", IconType.BALL);
		prefixes.put("frames.player_", IconType.CUBE);
		prefixes.put("frames.ship_", IconType.SHIP);
		prefixes.put("frames.bird_", IconType.UFO);
		prefixes.put("frames.dart_", IconType.WAVE);
		prefixes.put("frames.robot_", IconType.ROBOT);
		prefixes.put("frames.spider_", IconType.SPIDER);
		final Map> offsets = new HashMap<>();
		final Map> sizes = new HashMap<>();
		final Map> sourceSizes = new HashMap<>();
		final Map> rectangles = new HashMap<>();
		final Map> rotatedStates = new HashMap<>();
		Iterator it = spritePlist.getKeys();
		for (String key = (it.hasNext() ? it.next() : null) ; it.hasNext() ; key = it.next()) {
			for (Entry prefix : prefixes.entrySet()) {
				if (key.startsWith(prefix.getKey())) {
					String partKey = prefix.getValue().name() + Utils.partialParseInt(key.substring(prefix.getKey().length()));
					final String f_key = key;
					if (key.endsWith("spriteOffset")) {
						offsets.compute(partKey, (k, v) -> {
							if (v == null) {
								v = new LinkedHashMap<>();
							}
							v.put(f_key, parseSimpleTuple(spritePlist.getString(f_key)));
							return v;
						});
					} else if (key.endsWith("spriteSize")) {
						sizes.compute(partKey, (k, v) -> {
							if (v == null) {
								v = new LinkedHashMap<>();
							}
							v.put(f_key, parseSimpleTuple(spritePlist.getString(f_key)));
							return v;
						});
					} else if (key.endsWith("spriteSourceSize")) {
						sourceSizes.compute(partKey, (k, v) -> {
							if (v == null) {
								v = new LinkedHashMap<>();
							}
							v.put(f_key, parseSimpleTuple(spritePlist.getString(f_key)));
							return v;
						});
					} else if (key.endsWith("textureRect")) {
						rectangles.compute(partKey, (k, v) -> {
							if (v == null) {
								v = new LinkedHashMap<>();
							}
							v.put(f_key, parseDoubleTuple(spritePlist.getString(f_key)));
							return v;
						});
					} else if (key.endsWith("textureRotated")) {
						rotatedStates.compute(partKey, (k, v) -> {
							if (v == null) {
								v = new LinkedHashMap<>();
							}
							v.put(f_key, spritePlist.getBoolean(f_key));
							return v;
						});
					}
					break;
				}
			}
		}
		for (String key : offsets.keySet()) {
			List spList = new ArrayList<>();
			Iterator> offsetIt = offsets.get(key).entrySet().iterator();
			Iterator sizeIt = sizes.get(key).values().iterator();
			Iterator sourceSizeIt = sourceSizes.get(key).values().iterator();
			Iterator rectangleIt = rectangles.get(key).values().iterator();
			Iterator rotatedIt = rotatedStates.get(key).values().iterator();
			while (offsetIt.hasNext()) {
				Entry entry = offsetIt.next();
				String spriteName = entry.getKey().split("\\.")[1];
				double[] offset = entry.getValue();
				double[] size = sizeIt.next();
				double[] sourceSize = sourceSizeIt.next();
				double[] rectangle = rectangleIt.next();
				boolean isRotated = rotatedIt.next();
				spList.add(new Sprite(spriteName, (int) offset[0], (int) offset[1], (int) size[0], (int) size[1], (int) sourceSize[0], (int) sourceSize[1],
						new Rectangle((int) rectangle[0], (int) rectangle[1], (int) rectangle[2], (int) rectangle[3]), isRotated));
			}
			sprites.put(key, spList);
		}
	}
	
	private static double[] parseSimpleTuple(String tupleStr) {
		String[] split = tupleStr.substring(1, tupleStr.length() - 1).split(",");
		return new double[] { Double.parseDouble(split[0]), Double.parseDouble(split[1]) };
	}
	
	private static double[] parseDoubleTuple(String tupleStr) {
		String[] split = tupleStr.substring(2, tupleStr.length() - 2).split("\\}?,\\{?");
		return new double[] { Double.parseDouble(split[0]), Double.parseDouble(split[1]), Double.parseDouble(split[2]), Double.parseDouble(split[3]) };
	}
	
	private static Map colors() {
		Map colors = new HashMap<>();
		colors.put(0, new Color(125, 255, 0));
		colors.put(1, new Color(0, 255, 0));
		colors.put(2, new Color(0, 255, 125));
		colors.put(3, new Color(0, 255, 255));
		colors.put(4, new Color(0, 125, 255));
		colors.put(5, new Color(0, 0, 255));
		colors.put(6, new Color(125, 0, 255));
		colors.put(7, new Color(255, 0, 255));
		colors.put(8, new Color(255, 0, 125));
		colors.put(9, new Color(255, 0, 0));
		colors.put(10, new Color(255, 125, 0));
		colors.put(11, new Color(255, 255, 0));
		colors.put(12, new Color(255, 255, 255));
		colors.put(13, new Color(185, 0, 255));
		colors.put(14, new Color(255, 185, 0));
		colors.put(15, new Color(0, 0, 0));
		colors.put(16, new Color(0, 200, 255));
		colors.put(17, new Color(175, 175, 175));
		colors.put(18, new Color(90, 90, 90));
		colors.put(19, new Color(255, 125, 125));
		colors.put(20, new Color(0, 175, 75));
		colors.put(21, new Color(0, 125, 125));
		colors.put(22, new Color(0, 75, 175));
		colors.put(23, new Color(75, 0, 175));
		colors.put(24, new Color(125, 0, 125));
		colors.put(25, new Color(175, 0, 75));
		colors.put(26, new Color(175, 75, 0));
		colors.put(27, new Color(125, 125, 0));
		colors.put(28, new Color(75, 175, 0));
		colors.put(29, new Color(255, 75, 0));
		colors.put(30, new Color(150, 50, 0));
		colors.put(31, new Color(150, 100, 0));
		colors.put(32, new Color(100, 150, 0));
		colors.put(33, new Color(0, 150, 100));
		colors.put(34, new Color(0, 100, 150));
		colors.put(35, new Color(100, 0, 150));
		colors.put(36, new Color(150, 0, 100));
		colors.put(37, new Color(150, 0, 0));
		colors.put(38, new Color(0, 150, 0));
		colors.put(39, new Color(0, 0, 150));
		colors.put(40, new Color(125, 255, 175));
		colors.put(41, new Color(125, 125, 255));
		return Collections.unmodifiableMap(colors);
	}

	/**
	 * Creates a new sprite factory. This method will load the sprite images in
	 * memory, that's why an {@link IOException} needs to be checked when calling
	 * this method.
	 * 
	 * @return the sprite factory
	 * @throws SpriteLoadException if something goes wrong when loading the sprites.
	 */
	public static SpriteFactory create() throws SpriteLoadException {
		try {
			if (sprites == null) {
				spriteImg = ImageIO.read(SpriteFactory.class.getResource("/GJ_GameSheet02-uhd.png"));
				loadSprites(new Configurations()
						.fileBased(XMLPropertyListConfiguration.class, SpriteFactory.class
								.getResource("/GJ_GameSheet02-uhd.plist")));
			}
			return new SpriteFactory();
		} catch (IOException | ConfigurationException e) {
			throw new SpriteLoadException(e);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy