package org.ripple.power.ui.graphics; import java.awt.Color; import java.awt.Image; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.awt.image.ImageProducer; import java.awt.image.WritableRaster; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import org.ripple.power.config.LSystem; import org.ripple.power.ui.UIRes; import org.ripple.power.utils.GraphicsUtils; public class LImage { public static class LFormatTGA { private static final int TGA_HEADER_SIZE = 18; private static final int TGA_HEADER_INVALID = 0; private static final int TGA_HEADER_UNCOMPRESSED = 1; private static final int TGA_HEADER_COMPRESSED = 2; public static class State { public int type; public int pixelDepth; public int width; public int height; public int[] pixels; public void dispose() { if (pixels != null) { pixels = null; } } } public static State inJustDecode(String res) throws IOException { return inJustDecode(UIRes.getStream(res)); } public static State inJustDecode(InputStream in) throws IOException { return loadHeader(in, new State()); } private static State loadHeader(InputStream in, State info) throws IOException { in.read(); in.read(); info.type = (byte) in.read(); in.read(); in.read(); in.read(); in.read(); in.read(); in.read(); in.read(); in.read(); in.read(); info.width = (in.read() & 0xff) | ((in.read() & 0xff) << 8); info.height = (in.read() & 0xff) | ((in.read() & 0xff) << 8); info.pixelDepth = in.read() & 0xff; return info; } private static final short getUnsignedByte(byte[] bytes, int byteIndex) { return (short) (bytes[byteIndex] & 0xFF); } private static final int getUnsignedShort(byte[] bytes, int byteIndex) { return (getUnsignedByte(bytes, byteIndex + 1) << 8) + getUnsignedByte(bytes, byteIndex + 0); } private static void readBuffer(InputStream in, byte[] buffer) throws IOException { int bytesRead = 0; int bytesToRead = buffer.length; for (; bytesToRead > 0;) { int read = in.read(buffer, bytesRead, bytesToRead); bytesRead += read; bytesToRead -= read; } } private static final void skipBytes(InputStream in, long toSkip) throws IOException { for (; toSkip > 0L;) { long skipped = in.skip(toSkip); if (skipped > 0) { toSkip -= skipped; } else if (skipped < 0) { toSkip = 0; } } } private static final int compareFormatHeader(InputStream in, byte[] header) throws IOException { readBuffer(in, header); boolean hasPalette = false; int result = TGA_HEADER_INVALID; int imgIDSize = getUnsignedByte(header, 0); if ((header[1] != (byte) 0) && (header[1] != (byte) 1)) { return TGA_HEADER_INVALID; } switch (getUnsignedByte(header, 2)) { case 0: result = TGA_HEADER_UNCOMPRESSED; break; case 1: hasPalette = true; result = TGA_HEADER_UNCOMPRESSED; throw new RuntimeException( "Indexed State is not yet supported !"); case 2: result = TGA_HEADER_UNCOMPRESSED; break; case 3: result = TGA_HEADER_UNCOMPRESSED; break; case 9: hasPalette = true; result = TGA_HEADER_COMPRESSED; throw new RuntimeException( "Indexed State is not yet supported !"); case 10: result = TGA_HEADER_COMPRESSED; break; case 11: result = TGA_HEADER_COMPRESSED; break; default: return TGA_HEADER_INVALID; } if (!hasPalette) { if (getUnsignedShort(header, 3) != 0) { return TGA_HEADER_INVALID; } } if (!hasPalette) { if (getUnsignedShort(header, 5) != 0) { return TGA_HEADER_INVALID; } } short paletteEntrySize = getUnsignedByte(header, 7); if (!hasPalette) { if (paletteEntrySize != 0) { return TGA_HEADER_INVALID; } } else { if ((paletteEntrySize != 15) && (paletteEntrySize != 16) && (paletteEntrySize != 24) && (paletteEntrySize != 32)) { return TGA_HEADER_INVALID; } } if (getUnsignedShort(header, 8) != 0) { return TGA_HEADER_INVALID; } if (getUnsignedShort(header, 10) != 0) { return TGA_HEADER_INVALID; } switch (getUnsignedByte(header, 16)) { case 1: case 8: case 15: case 16: throw new RuntimeException( "this State with non RGB or RGBA pixels are not yet supported."); case 24: case 32: break; default: return TGA_HEADER_INVALID; } if (imgIDSize != 0) { skipBytes(in, imgIDSize); } return result; } private static final void writePixel(int[] pixels, final byte red, final byte green, final byte blue, final byte alpha, final boolean hasAlpha, final int offset) { int pixel; if (hasAlpha) { pixel = (red & 0xff); pixel |= ((green & 0xff) << 8); pixel |= ((blue & 0xff) << 16); pixel |= ((alpha & 0xff) << 24); pixels[offset / 4] = pixel; } else { pixel = (red & 0xff); pixel |= ((green & 0xff) << 8); pixel |= ((blue & 0xff) << 16); pixels[offset / 4] = pixel; } } private static int[] readBuffer(InputStream in, int width, int height, int srcBytesPerPixel, boolean acceptAlpha, boolean flipVertically) throws IOException { int[] pixels = new int[width * height]; byte[] buffer = new byte[srcBytesPerPixel]; final boolean copyAlpha = (srcBytesPerPixel == 4) && acceptAlpha; final int dstBytesPerPixel = acceptAlpha ? srcBytesPerPixel : 3; final int trgLineSize = width * dstBytesPerPixel; int dstByteOffset = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int read = in.read(buffer, 0, srcBytesPerPixel); if (read < srcBytesPerPixel) { return pixels; } int actualByteOffset = dstByteOffset; if (!flipVertically) { actualByteOffset = ((height - y - 1) * trgLineSize) + (x * dstBytesPerPixel); } if (copyAlpha) { writePixel(pixels, buffer[2], buffer[1], buffer[0], buffer[3], true, actualByteOffset); } else { writePixel(pixels, buffer[2], buffer[1], buffer[0], (byte) 0, false, actualByteOffset); } dstByteOffset += dstBytesPerPixel; } } return pixels; } private static void loadUncompressed(byte[] header, State tga, InputStream in, boolean acceptAlpha, boolean flipVertically) throws IOException { // 图像宽 int orgWidth = getUnsignedShort(header, 12); // 图像高 int orgHeight = getUnsignedShort(header, 14); // 图像位图(24&32) int pixelDepth = getUnsignedByte(header, 16); tga.width = orgWidth; tga.height = orgHeight; tga.pixelDepth = pixelDepth; boolean isOriginBottom = (header[17] & 0x20) == 0; if (!isOriginBottom) { flipVertically = !flipVertically; } // 不支持的格式 if ((orgWidth <= 0) || (orgHeight <= 0) || ((pixelDepth != 24) && (pixelDepth != 32))) { throw new IOException("Invalid texture information !"); } int bytesPerPixel = (pixelDepth / 8); // 获取图像数据并转为int[] tga.pixels = readBuffer(in, orgWidth, orgHeight, bytesPerPixel, acceptAlpha, flipVertically); // 图像色彩模式 tga.type = (acceptAlpha && (bytesPerPixel == 4) ? 4 : 3); } private static void loadCompressed(byte[] header, State tga, InputStream in, boolean acceptAlpha, boolean flipVertically) throws IOException { int orgWidth = getUnsignedShort(header, 12); int orgHeight = getUnsignedShort(header, 14); int pixelDepth = getUnsignedByte(header, 16); tga.width = orgWidth; tga.height = orgHeight; tga.pixelDepth = pixelDepth; boolean isOriginBottom = (header[17] & 0x20) == 0; if (!isOriginBottom) { flipVertically = !flipVertically; } if ((orgWidth <= 0) || (orgHeight <= 0) || ((pixelDepth != 24) && (pixelDepth != 32))) { throw new IOException("Invalid texture information !"); } int bytesPerPixel = (pixelDepth / 8); int pixelCount = orgHeight * orgWidth; int currentPixel = 0; byte[] colorBuffer = new byte[bytesPerPixel]; int width = orgWidth; int height = orgHeight; final int dstBytesPerPixel = (acceptAlpha && (bytesPerPixel == 4) ? 4 : 3); final int trgLineSize = orgWidth * dstBytesPerPixel; int[] pixels = new int[width * height]; int dstByteOffset = 0; do { int chunkHeader = 0; try { chunkHeader = (byte) in.read() & 0xFF; } catch (IOException e) { throw new IOException( "Could not read RLE imageData header !"); } boolean repeatColor; if (chunkHeader < 128) { chunkHeader++; repeatColor = false; } else { chunkHeader -= 127; readBuffer(in, colorBuffer); repeatColor = true; } for (int counter = 0; counter < chunkHeader; counter++) { if (!repeatColor) { readBuffer(in, colorBuffer); } int x = currentPixel % orgWidth; int y = currentPixel / orgWidth; int actualByteOffset = dstByteOffset; if (!flipVertically) { actualByteOffset = ((height - y - 1) * trgLineSize) + (x * dstBytesPerPixel); } if (dstBytesPerPixel == 4) { writePixel(pixels, colorBuffer[2], colorBuffer[1], colorBuffer[0], colorBuffer[3], true, actualByteOffset); } else { writePixel(pixels, colorBuffer[2], colorBuffer[1], colorBuffer[0], (byte) 0, false, actualByteOffset); } dstByteOffset += dstBytesPerPixel; currentPixel++; if (currentPixel > pixelCount) { throw new IOException("Too many pixels read !"); } } } while (currentPixel < pixelCount); tga.pixels = pixels; tga.type = dstBytesPerPixel; } public static State load(String res) throws IOException { return load(res, new State()); } public static State load(String res, State tag) throws IOException { InputStream in = UIRes.getStream(res); State tga = load(in, tag, true, false); if (in != null) { try { in.close(); in = null; } catch (Exception e) { } } return tga; } public static State load(InputStream in, State tga, boolean acceptAlpha, boolean flipVertically) throws IOException { if (in.available() < TGA_HEADER_SIZE) { return (null); } byte[] header = new byte[TGA_HEADER_SIZE]; final int headerType = compareFormatHeader(in, header); if (headerType == TGA_HEADER_INVALID) { return (null); } if (headerType == TGA_HEADER_UNCOMPRESSED) { loadUncompressed(header, tga, in, acceptAlpha, flipVertically); } else if (headerType == TGA_HEADER_COMPRESSED) { loadCompressed(header, tga, in, acceptAlpha, flipVertically); } else { throw new IOException("State file be type 2 or type 10 !"); } return tga; } } private final static String tgaExtension = ".tga"; private final static ArrayList<LImage> images = new ArrayList<LImage>(100); private HashMap<Integer, LImage> subs; protected BufferedImage bufferedImage; private String fileName; private LGraphics g; private int width, height; private boolean isClose, isAutoDispose = true; public static LImage createImage(byte[] buffer) { return new LImage(GraphicsUtils.toolKit.createImage(buffer)); } public static LImage createImage(byte[] buffer, int imageoffset, int imagelength) { return new LImage(GraphicsUtils.toolKit.createImage(buffer, imageoffset, imagelength)); } public static LImage createImage(int width, int height) { return new LImage(width, height, false); } public static LImage createImage(int width, int height, boolean transparency) { return new LImage(width, height, transparency); } public static LImage createImage(int width, int height, int type) { return new LImage(width, height, type); } public static LImage createImage(String fileName) { return new LImage(fileName); } public static LImage createRGBImage(int[] rgb, int width, int height, boolean processAlpha) { if (rgb == null) { throw new NullPointerException(); } if (width <= 0 || height <= 0) { throw new IllegalArgumentException(); } BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); if (!processAlpha) { int l = rgb.length; int[] rgbAux = new int[l]; for (int i = 0; i < l; i++) { rgbAux[i] = rgb[i] | 0xff000000; } rgb = rgbAux; } img.setRGB(0, 0, width, height, rgb, 0, width); return new LImage(img); } public static LImage[] createImage(int count, int w, int h, boolean transparency) { LImage[] image = new LImage[count]; for (int i = 0; i < image.length; i++) { image[i] = new LImage(w, h, transparency); } return image; } public static LImage[] createImage(int count, int w, int h, int type) { LImage[] image = new LImage[count]; for (int i = 0; i < image.length; i++) { image[i] = new LImage(w, h, type); } return image; } public static LImage createImage(LImage image, int x, int y, int width, int height, int transform) { int[] buf = new int[width * height]; image.getPixels(buf, 0, width, x, y, width, height); int th; int tw; if ((transform & 4) != 0) { th = width; tw = height; } else { th = height; tw = width; } if (transform != 0) { int[] trans = new int[buf.length]; int sp = 0; for (int sy = 0; sy < height; sy++) { int tx; int ty; int td; switch (transform) { case LGraphics.TRANS_ROT90: tx = tw - sy - 1; ty = 0; td = tw; break; case LGraphics.TRANS_ROT180: tx = tw - 1; ty = th - sy - 1; td = -1; break; case LGraphics.TRANS_ROT270: tx = sy; ty = th - 1; td = -tw; break; case LGraphics.TRANS_MIRROR: tx = tw - 1; ty = sy; td = -1; break; case LGraphics.TRANS_MIRROR_ROT90: tx = tw - sy - 1; ty = th - 1; td = -tw; break; case LGraphics.TRANS_MIRROR_ROT180: tx = 0; ty = th - sy - 1; td = 1; break; case LGraphics.TRANS_MIRROR_ROT270: tx = sy; ty = 0; td = tw; break; default: throw new RuntimeException("Illegal transformation: " + transform); } int tp = ty * tw + tx; for (int sx = 0; sx < width; sx++) { trans[tp] = buf[sp++]; tp += td; } } buf = trans; } return createRGBImage(buf, tw, th, true); } public LImage(int width, int height) { this(width, height, true); } public LImage(int width, int height, boolean transparency) { try { this.width = width; this.height = height; this.bufferedImage = GraphicsUtils.createImage(width, height, transparency); } catch (Exception e) { try { this.width = width; this.height = height; this.bufferedImage = GraphicsUtils.createImage(width, height, transparency); } catch (Exception ex) { } } if (!images.contains(this)) { images.add(this); } } public LImage(int width, int height, int type) { this.width = width; this.height = height; this.bufferedImage = GraphicsUtils.createImage(width, height, type); if (!images.contains(this)) { images.add(this); } } public LImage(String fileName) { if (fileName == null) { throw new RuntimeException("file name is null !"); } String res; if (fileName.startsWith("/")) { res = fileName.substring(1); } else { res = fileName; } this.fileName = fileName; BufferedImage img = null; if (fileName.toLowerCase().lastIndexOf(tgaExtension) != -1) { try { LFormatTGA.State tga = LFormatTGA.load(res); if (tga != null) { img = GraphicsUtils.createImage(tga.width, tga.height, tga.type == 4 ? true : false); img.setRGB(0, 0, tga.width, tga.height, tga.pixels, 0, tga.width); tga.dispose(); tga = null; } } catch (IOException e) { e.printStackTrace(); } } else { img = GraphicsUtils.loadBufferedImage(res); } setImage(img); if (!images.contains(this)) { images.add(this); } } public LImage(BufferedImage img) { this.setImage(img); if (!images.contains(this)) { images.add(this); } } public LImage(Image img) { GraphicsUtils.waitImage(img); this.setImage(img); if (!images.contains(this)) { images.add(this); } } public void setImage(LImage img) { this.width = img.getWidth(); this.height = img.getHeight(); this.bufferedImage = img.bufferedImage; this.isAutoDispose = img.isAutoDispose; } public void setImage(BufferedImage img) { this.width = img.getWidth(); this.height = img.getHeight(); this.bufferedImage = img; } public void setImage(Image img) { this.width = img.getWidth(null); this.height = img.getHeight(null); this.bufferedImage = GraphicsUtils.getBufferImage(img); } public Object clone() { return new LImage(bufferedImage); } public ImageProducer getSource() { return bufferedImage.getSource(); } public boolean hasAlpha() { return bufferedImage.getColorModel().hasAlpha(); } public LGraphics getLGraphics() { if (g == null || g.isClose()) { g = new LGraphics(bufferedImage); } return g; } public LGraphics create() { return new LGraphics(bufferedImage); } public LImage getMirrorImage() { if (bufferedImage != null) { synchronized (bufferedImage) { LImage image = null; if (bufferedImage.getTransparency() == Transparency.TRANSLUCENT) { image = LImage.createImage(width, height, true); } else { image = LImage.createImage(width, height, false); } LGraphics g = image.getLGraphics(); g.drawMirrorImage(bufferedImage, 0, 0); g.dispose(); return image; } } return this; } public BufferedImage getBufferedImage() { return bufferedImage; } public int getWidth() { return bufferedImage.getWidth(); } public int getHeight() { return bufferedImage.getHeight(); } public Color getColorAt(int x, int y) { return new Color(this.getRGBAt(x, y), true); } public int getRGBAt(int x, int y) { if (x >= this.getWidth()) { throw new IndexOutOfBoundsException("X is out of bounds: " + x + "," + this.getWidth()); } else if (y >= this.getHeight()) { throw new IndexOutOfBoundsException("Y is out of bounds: " + y + "," + this.getHeight()); } else if (x < 0) { throw new IndexOutOfBoundsException("X is out of bounds: " + x); } else if (y < 0) { throw new IndexOutOfBoundsException("Y is out of bounds: " + y); } else { return this.bufferedImage.getRGB(x, y); } } public WritableRaster getRaster() { return bufferedImage.getRaster(); } public int[] getPixels() { int pixels[] = new int[width * height]; bufferedImage.getRGB(0, 0, width, height, pixels, 0, width); return pixels; } public int[] getPixels(int pixels[]) { bufferedImage.getRGB(0, 0, width, height, pixels, 0, width); return pixels; } public int[] getPixels(int x, int y, int w, int h) { int[] pixels = new int[w * h]; bufferedImage.getRGB(x, y, w, h, pixels, 0, w); return pixels; } public int[] getPixels(int offset, int stride, int x, int y, int width, int height) { int pixels[] = new int[width * height]; bufferedImage.getRGB(x, y, width, height, pixels, offset, stride); return pixels; } public int[] getPixels(int pixels[], int offset, int stride, int x, int y, int width, int height) { bufferedImage.getRGB(x, y, width, height, pixels, offset, stride); return pixels; } public void setPixels(int[] pixels, int width, int height) { bufferedImage.setRGB(0, 0, width, height, pixels, 0, width); } public void setPixels(int[] pixels, int offset, int stride, int x, int y, int width, int height) { bufferedImage.setRGB(x, y, width, height, pixels, offset, stride); } public int[] setPixels(int[] pixels, int x, int y, int w, int h) { bufferedImage.setRGB(x, y, w, h, pixels, 0, w); return pixels; } public void setPixel(Color c, int x, int y) { bufferedImage.setRGB(x, y, c.getRGB()); } public void setPixel(int rgb, int x, int y) { bufferedImage.setRGB(x, y, rgb); } public int getPixel(int x, int y) { return bufferedImage.getRGB(x, y); } public int getRGB(int x, int y) { return bufferedImage.getRGB(x, y); } public void setRGB(int rgb, int x, int y) { bufferedImage.setRGB(x, y, rgb); } public LImage getCacheSubImage(int x, int y, int w, int h) { if (subs == null) { subs = new HashMap<Integer, LImage>(10); } int hashCode = 1; hashCode = LSystem.unite(hashCode, x); hashCode = LSystem.unite(hashCode, y); hashCode = LSystem.unite(hashCode, w); hashCode = LSystem.unite(hashCode, h); LImage img = (LImage) subs.get(hashCode); if (img == null) { subs.put(hashCode, img = new LImage(bufferedImage.getSubimage(x, y, w, h))); } return img; } public LImage getSubImage(int x, int y, int w, int h) { return new LImage(bufferedImage.getSubimage(x, y, w, h)); } public LImage getSubImageSize(int x, int y, int w, int h) { return new LImage(bufferedImage.getSubimage(x, y, w - x, h - y)); } public LImage scaledInstance(int w, int h) { int width = getWidth(); int height = getHeight(); if (width == w && height == h) { return this; } return new LImage(GraphicsUtils.getResize(bufferedImage, w, h)); } public void getRGB(int pixels[], int offset, int stride, int x, int y, int width, int height) { getPixels(pixels, offset, stride, x, y, width, height); } public int hashCode() { return GraphicsUtils.hashImage(bufferedImage); } public boolean isClose() { return isClose || bufferedImage == null; } public boolean isAutoDispose() { return isAutoDispose && !isClose(); } public void setAutoDispose(boolean dispose) { this.isAutoDispose = dispose; } public String getPath() { return fileName; } public void dispose() { dispose(true); } private void dispose(boolean remove) { isClose = true; subs = null; if (bufferedImage != null) { bufferedImage.flush(); bufferedImage = null; } if (remove) { images.remove(this); } } public static void disposeAll() { for (LImage img : images) { if (img != null) { img.dispose(false); img = null; } } images.clear(); } }