/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Modifications Copyright 2003-2004 Bytonic Software Copyright 2010 Google Inc. */ package com.googlecode.gwtquake.shared.common; import java.nio.ByteBuffer; import com.googlecode.gwtquake.shared.client.Dimension; public class QuakeImage { /* * skins will be outline flood filled and mip mapped pics and sprites with * alpha will be outline flood filled pic won't be mip mapped * * model skin sprite frame wall texture pic */ // enum imagetype_t public static final int it_skin = 0; public static final int it_sprite = 1; public static final int it_wall = 2; public static final int it_pic = 3; public static final int it_sky = 4; public static final int[] PALETTE_ABGR = new int[] { 0xff000000, 0xff0f0f0f, 0xff1f1f1f, 0xff2f2f2f, 0xff3f3f3f, 0xff4b4b4b, 0xff5b5b5b, 0xff6b6b6b, 0xff7b7b7b, 0xff8b8b8b, 0xff9b9b9b, 0xffababab, 0xffbbbbbb, 0xffcbcbcb, 0xffdbdbdb, 0xffebebeb, 0xff234b63, 0xff1f435b, 0xff1f3f53, 0xff1b3b4f, 0xff1b3747, 0xff172f3f, 0xff172b3b, 0xff132733, 0xff13232f, 0xff131f2b, 0xff0f1b27, 0xff0f1723, 0xff0b131b, 0xff0b0f17, 0xff070f13, 0xff070b0f, 0xff6f5f5f, 0xff675b5b, 0xff5f535b, 0xff5b4f57, 0xff534b53, 0xff4b474f, 0xff433f47, 0xff3b3b3f, 0xff37373b, 0xff2f2f33, 0xff2b2b2f, 0xff272727, 0xff232323, 0xff1b1b1b, 0xff171717, 0xff131313, 0xff53778f, 0xff43637b, 0xff3b5b73, 0xff2f4f67, 0xff4b97cf, 0xff3b7ba7, 0xff2f678b, 0xff27536f, 0xff279feb, 0xff238bcb, 0xff1f77af, 0xff1b6393, 0xff174f77, 0xff0f3b5b, 0xff0b273f, 0xff071723, 0xff2b3ba7, 0xff232f9f, 0xff1b2b97, 0xff13278b, 0xff0f1f7f, 0xff0b1773, 0xff071767, 0xff001357, 0xff000f4b, 0xff000f43, 0xff000f3b, 0xff000b33, 0xff000b2b, 0xff000b23, 0xff00071b, 0xff000713, 0xff4b5f7b, 0xff435773, 0xff3f536b, 0xff3b4f67, 0xff37475f, 0xff334357, 0xff2f3f53, 0xff2b374b, 0xff273343, 0xff232f3f, 0xff1b2737, 0xff17232f, 0xff131b27, 0xff0f171f, 0xff0b0f17, 0xff070b0f, 0xff173b6f, 0xff17375f, 0xff172f53, 0xff172b43, 0xff132337, 0xff0f1b27, 0xff0b131b, 0xff070b0f, 0xff4f5bb3, 0xff6f7bbf, 0xff939bcb, 0xffb7bbd7, 0xffdfd7cb, 0xffd3c7b3, 0xffc3b79f, 0xffb7a787, 0xffa79773, 0xff9b875b, 0xff8b7747, 0xff7f672f, 0xff6f5317, 0xff674b13, 0xff5b430f, 0xff533f0b, 0xff4b3707, 0xff3f2f07, 0xff332707, 0xff2b1f00, 0xff1f1700, 0xff130f00, 0xff0b0700, 0xff000000, 0xff57578b, 0xff4f4f83, 0xff47477b, 0xff434373, 0xff3b3b6b, 0xff333363, 0xff2f2f5b, 0xff2b2b57, 0xff23234b, 0xff1f1f3f, 0xff1b1b33, 0xff13132b, 0xff0f0f1f, 0xff0b0b13, 0xff07070b, 0xff000000, 0xff7b9f97, 0xff73978f, 0xff6b8b87, 0xff63837f, 0xff5f7b77, 0xff577373, 0xff4f6b6b, 0xff476363, 0xff435b5b, 0xff3b4f4f, 0xff334343, 0xff2b3737, 0xff232f2f, 0xff1b2323, 0xff131717, 0xff0b0f0f, 0xff3f4b9f, 0xff374393, 0xff2f3b8b, 0xff27377f, 0xff232f77, 0xff1b2b6b, 0xff172363, 0xff131f57, 0xff0f1b4f, 0xff0b1743, 0xff0b1337, 0xff070f2b, 0xff070b1f, 0xff000717, 0xff00000b, 0xff000000, 0xffcf7b77, 0xffc3736f, 0xffb76b67, 0xffa76363, 0xff9b5b5b, 0xff8f5753, 0xff7f4f4b, 0xff734747, 0xff673f3f, 0xff573737, 0xff4b2f2f, 0xff3f2727, 0xff2f1f23, 0xff23171b, 0xff170f13, 0xff07070b, 0xff7bab9b, 0xff6f9f8f, 0xff639787, 0xff578b7b, 0xff4b8373, 0xff437767, 0xff3b6f5f, 0xff336757, 0xff275b4b, 0xff1b4f3f, 0xff134337, 0xff0b3b2f, 0xff072f23, 0xff00231b, 0xff001713, 0xff000f0b, 0xff00ff00, 0xff0fe723, 0xff1bd33f, 0xff27bb53, 0xff2fa75f, 0xff338f5f, 0xff337b5f, 0xffffffff, 0xffd3ffff, 0xffa7ffff, 0xff7fffff, 0xff53ffff, 0xff27ffff, 0xff1febff, 0xff17d7ff, 0xff0fbfff, 0xff07abff, 0xff0093ff, 0xff007fef, 0xff006be3, 0xff0057d3, 0xff0047c7, 0xff003bb7, 0xff002bab, 0xff001f9b, 0xff00178f, 0xff000f7f, 0xff000773, 0xff00005f, 0xff000047, 0xff00002f, 0xff00001b, 0xff0000ef, 0xffff3737, 0xff0000ff, 0xffff0000, 0xff232b2b, 0xff171b1b, 0xff0f1313, 0xff7f97eb, 0xff5373c3, 0xff33579f, 0xff1b3f7b, 0xffc7d3eb, 0xff9babc7, 0xff778ba7, 0xff576b87, 0xff535b9f, /*0x00ffffff*/ }; public static final int[] PALETTE_ARGB = new int[PALETTE_ABGR.length]; public static final int ALPHA_MASK = 0x0ff000000; static { int len = PALETTE_ARGB.length; for (int i = 0; i < len; i++) { int abgr = QuakeImage.PALETTE_ABGR[i]; int argb = (abgr & 0xff00ff00) | ((abgr >> 16) & 255) | ((abgr & 255) << 16); PALETTE_ARGB[i] = argb; } } public final int width; public final int height; public final int[] data; public boolean hasAlpha; public QuakeImage(int width, int height, int[] data) { this.width = width; this.height = height; this.data = data; for (int i= 0; i < data.length; i++) { if ((data[i] & ALPHA_MASK) != ALPHA_MASK) { hasAlpha = true; break; } } } public static QuakeImage loadImage(byte[] raw, String ext) { byte[] decoded = null; Dimension dim = new Dimension(); int bits; if (ext.endsWith(".pcx")) { decoded = QuakeImage.LoadPCX(raw, null, dim); bits = 8; } else if (ext.endsWith(".wal")) { decoded = QuakeImage.GL_LoadWal(raw, dim); bits = 8; } else if (ext.endsWith(".tga")) { decoded = QuakeImage.LoadTGA(raw, dim); bits = 32; } else throw new RuntimeException("unknow image type!"); return new QuakeImage(dim.width, dim.height, bits == 8 ? applyPalette(decoded, dim.width, dim.height, PALETTE_ARGB) : bytesToIntsArgb(decoded)); } public static int[] applyPalette(byte[] data, int width, int height, int[] palette) { int[] trans = new int[data.length]; int p; // int rgb; int s = width * height; for (int i = 0; i < s; i++) { p = data[i] & 0xff; trans[i] = palette[p]; if (p == 255) { // transparent, so scan around for another color // to avoid alpha fringes // FIXME: do a full flood fill so mips work... if (i > width && (data[i - width] & 0xff) != 255) p = data[i - width] & 0xff; else if (i < s - width && (data[i + width] & 0xff) != 255) p = data[i + width] & 0xff; else if (i > 0 && (data[i - 1] & 0xff) != 255) p = data[i - 1] & 0xff; else if (i < s - 1 && (data[i + 1] & 0xff) != 255) p = data[i + 1] & 0xff; else p = 0; // copy rgb components // ((byte *)&trans[i])[0] = ((byte *)&d_8to24table[p])[0]; // ((byte *)&trans[i])[1] = ((byte *)&d_8to24table[p])[1]; // ((byte *)&trans[i])[2] = ((byte *)&d_8to24table[p])[2]; trans[i] = palette[p] & 0x00FFFFFF; // only rgb } } return trans; } /* ============== LoadPCX ============== */ public static byte[] LoadPCX(byte[] raw, byte[][] palette, Dimension dim) { QuakeFiles.pcx_t pcx; // // parse the PCX file // pcx = new QuakeFiles.pcx_t(raw); if (pcx.manufacturer != 0x0a || pcx.version != 5 || pcx.encoding != 1 || pcx.bits_per_pixel != 8 || pcx.xmax >= 640 || pcx.ymax >= 480) { // VID.Printf(Defines.PRINT_ALL, "Bad pcx file " +// filename // + '\n'); return null; } int width = pcx.xmax - pcx.xmin + 1; int height = pcx.ymax - pcx.ymin + 1; byte[] pix = new byte[width * height]; if (palette != null) { palette[0] = new byte[768]; System.arraycopy(raw, raw.length - 768, palette[0], 0, 768); } if (dim != null) { dim.width = width; dim.height = height; } // // decode pcx // int count = 0; byte dataByte = 0; int runLength = 0; int x, y; for (y = 0; y < height; y++) { for (x = 0; x < width;) { dataByte = pcx.data.get(); if ((dataByte & 0xC0) == 0xC0) { runLength = dataByte & 0x3F; dataByte = pcx.data.get(); // write runLength pixel while (runLength-- > 0) { pix[count++] = dataByte; x++; } } else { // write one pixel pix[count++] = dataByte; x++; } } } return pix; } // /* // ========================================================= // // TARGA LOADING // // ========================================================= // */ /* ============= LoadTGA ============= */ public static byte[] LoadTGA(byte[] raw, Dimension dim) { int columns, rows, numPixels; int pixbuf; // index into pic int row, column; ByteBuffer buf_p; // int length; QuakeFiles.tga_t targa_header; byte[] pic = null; targa_header = new QuakeFiles.tga_t(raw); if (targa_header.image_type != 2 && targa_header.image_type != 10) Com.Error(Constants.ERR_DROP, "LoadTGA: Only type 2 and 10 targa RGB images supported\n"); if (targa_header.colormap_type != 0 || (targa_header.pixel_size != 32 && targa_header.pixel_size != 24)) Com.Error (Constants.ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n"); columns = targa_header.width; rows = targa_header.height; numPixels = columns * rows; if (dim != null) { dim.width = columns; dim.height = rows; } pic = new byte[numPixels * 4]; // targa_rgba; if (targa_header.id_length != 0) targa_header.data.position(targa_header.id_length); // skip TARGA image comment buf_p = targa_header.data; byte red,green,blue,alphabyte; red = green = blue = alphabyte = 0; int packetHeader, packetSize, j; if (targa_header.image_type==2) { // Uncompressed, RGB images for(row=rows-1; row>=0; row--) { pixbuf = row * columns * 4; for(column=0; column<columns; column++) { switch (targa_header.pixel_size) { case 24: blue = buf_p.get(); green = buf_p.get(); red = buf_p.get(); pic[pixbuf++] = red; pic[pixbuf++] = green; pic[pixbuf++] = blue; pic[pixbuf++] = (byte)255; break; case 32: blue = buf_p.get(); green = buf_p.get(); red = buf_p.get(); alphabyte = buf_p.get(); pic[pixbuf++] = red; pic[pixbuf++] = green; pic[pixbuf++] = blue; pic[pixbuf++] = alphabyte; break; } } } } else if (targa_header.image_type==10) { // Runlength encoded RGB images for(row=rows-1; row>=0; row--) { pixbuf = row * columns * 4; breakOut: for(column=0; column<columns; ) { packetHeader= buf_p.get() & 0xFF; packetSize = 1 + (packetHeader & 0x7f); if ((packetHeader & 0x80) != 0) { // run-length packet switch (targa_header.pixel_size) { case 24: blue = buf_p.get(); green = buf_p.get(); red = buf_p.get(); alphabyte = (byte)255; break; case 32: blue = buf_p.get(); green = buf_p.get(); red = buf_p.get(); alphabyte = buf_p.get(); break; } for(j=0;j<packetSize;j++) { pic[pixbuf++]=red; pic[pixbuf++]=green; pic[pixbuf++]=blue; pic[pixbuf++]=alphabyte; column++; if (column==columns) { // run spans across rows column=0; if (row>0) row--; else // goto label breakOut; break breakOut; pixbuf = row * columns * 4; } } } else { // non run-length packet for(j=0;j<packetSize;j++) { switch (targa_header.pixel_size) { case 24: blue = buf_p.get(); green = buf_p.get(); red = buf_p.get(); pic[pixbuf++] = red; pic[pixbuf++] = green; pic[pixbuf++] = blue; pic[pixbuf++] = (byte)255; break; case 32: blue = buf_p.get(); green = buf_p.get(); red = buf_p.get(); alphabyte = buf_p.get(); pic[pixbuf++] = red; pic[pixbuf++] = green; pic[pixbuf++] = blue; pic[pixbuf++] = alphabyte; break; } column++; if (column==columns) { // pixel packet run spans across rows column=0; if (row>0) row--; else // goto label breakOut; break breakOut; pixbuf = row * columns * 4; } } } } } } return pic; } /* ================ GL_LoadWal ================ */ public static byte[] GL_LoadWal(byte[] raw, Dimension dim) { QuakeFiles.miptex_t mt = new QuakeFiles.miptex_t(raw); byte[] pix = new byte[mt.width * mt.height]; System.arraycopy(raw, mt.offsets[0], pix, 0, pix.length); dim.width = mt.width; dim.height = mt.height; return pix; } public static int[] bytesToIntsAbgr(byte[] pic) { int count = pic.length / 4; int[] tmp = new int[count]; for (int i = 0; i < count; i++) { tmp[i] = ((pic[4 * i + 0] & 0xFF) << 0) | // & 0x000000FF; ((pic[4 * i + 1] & 0xFF) << 8) | // & 0x0000FF00; ((pic[4 * i + 2] & 0xFF) << 16) | // & 0x00FF0000; ((pic[4 * i + 3] & 0xFF) << 24); // & 0xFF000000; } return tmp; } public static int[] bytesToIntsArgb(byte[] pic) { int count = pic.length / 4; int[] tmp = new int[count]; for (int i = 0; i < count; i++) { tmp[i] = ((pic[4 * i + 0] & 0xFF) << 16) | // & 0x000000FF; ((pic[4 * i + 1] & 0xFF) << 8) | // & 0x0000FF00; ((pic[4 * i + 2] & 0xFF) << 0) | // & 0x00FF0000; ((pic[4 * i + 3] & 0xFF) << 24); // & 0xFF000000; } return tmp; } static class floodfill_t { short x, y; } // must be a power of 2 static final int FLOODFILL_FIFO_SIZE = 0x1000; static final int FLOODFILL_FIFO_MASK = FLOODFILL_FIFO_SIZE - 1; // // #define FLOODFILL_STEP( off, dx, dy ) \ // { \ // if (pos[off] == fillcolor) \ // { \ // pos[off] = 255; \ // fifo[inpt].x = x + (dx), fifo[inpt].y = y + (dy); \ // inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; \ // } \ // else if (pos[off] != 255) fdc = pos[off]; \ // } // void FLOODFILL_STEP( int off, int dx, int dy ) // { // if (pos[off] == fillcolor) // { // pos[off] = 255; // fifo[inpt].x = x + dx; fifo[inpt].y = y + dy; // inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; // } // else if (pos[off] != 255) fdc = pos[off]; // } // TODO check this: R_FloodFillSkin( byte[] skin, int skinwidth, int skinheight) public void floodFill() { // byte fillcolor = *skin; // assume this is the pixel to fill floodfill_t[] fifo = new floodfill_t[FLOODFILL_FIFO_SIZE]; for (int j = 0; j < fifo.length; j++) { fifo[j] = new floodfill_t(); } int fillcolor = data[0]; // floodfill_t[] fifo = new floodfill_t[FLOODFILL_FIFO_SIZE]; int inpt = 0, outpt = 0; int filledcolor = 0xff000000; // can't fill to filled color or to transparent color (used as visited marker) if ((fillcolor == filledcolor) || (fillcolor == 0)) { return; } fifo[inpt].x = 0; fifo[inpt].y = 0; inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; while (outpt != inpt) { int x = fifo[outpt].x; int y = fifo[outpt].y; int fdc = filledcolor; // byte *pos = &skin[x + skinwidth * y]; int pos = x + width * y; // outpt = (outpt + 1) & FLOODFILL_FIFO_MASK; int off, dx, dy; if (x > 0) { // FLOODFILL_STEP( -1, -1, 0 ); off = -1; dx = -1; dy = 0; if (data[pos + off] == fillcolor) { data[pos + off] = 0; fifo[inpt].x = (short) (x + dx); fifo[inpt].y = (short) (y + dy); inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; } else if (data[pos + off] != 0) fdc = data[pos + off]; } if (x < width - 1) { // FLOODFILL_STEP( 1, 1, 0 ); off = 1; dx = 1; dy = 0; if (data[pos + off] == fillcolor) { data[pos + off] = 0; fifo[inpt].x = (short) (x + dx); fifo[inpt].y = (short) (y + dy); inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; } else if (data[pos + off] != 0) fdc = data[pos + off] & 0xff; } if (y > 0) { // FLOODFILL_STEP( -skinwidth, 0, -1 ); off = -width; dx = 0; dy = -1; if (data[pos + off] == fillcolor) { data[pos + off] = 0; fifo[inpt].x = (short) (x + dx); fifo[inpt].y = (short) (y + dy); inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; } else if (data[pos + off] != 0) fdc = data[pos + off]; } if (y < height - 1) { // FLOODFILL_STEP( skinwidth, 0, 1 ); off = width; dx = 0; dy = 1; if (data[pos + off] == fillcolor) { data[pos + off] = 0; fifo[inpt].x = (short) (x + dx); fifo[inpt].y = (short) (y + dy); inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; } else if (data[pos + off] != 0) fdc = data[pos + off]; } data[x + width * y] = fdc; } } }