mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-11-25 03:35:18 +01:00
Start on texture pack loader/scaler
This commit is contained in:
parent
66ca5199e3
commit
c958da0ae0
399
src/main/java/org/dynmap/hdmap/TexturePack.java
Normal file
399
src/main/java/org/dynmap/hdmap/TexturePack.java
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
package org.dynmap.hdmap;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.dynmap.Color;
|
||||||
|
import org.dynmap.DynmapPlugin;
|
||||||
|
import org.dynmap.Log;
|
||||||
|
import org.dynmap.kzedmap.KzedMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loader and processor class for minecraft texture packs
|
||||||
|
* Texture packs are found in dynmap/texturepacks directory, and either are either ZIP files
|
||||||
|
* or are directories whose content matches the structure of a zipped texture pack:
|
||||||
|
* ./terrain.png - main color data (required)
|
||||||
|
* misc/water.png - tone for water, biome sensitive (optional)
|
||||||
|
* misc/grasscolor.png - tone for grass color, biome sensitive (optional)
|
||||||
|
* misc/foliagecolor.png - tone for leaf color, biome sensitive (optional)
|
||||||
|
* BetterGlass/*.png - mod-based improved windows (future optional)
|
||||||
|
*/
|
||||||
|
public class TexturePack {
|
||||||
|
/* Loaded texture packs */
|
||||||
|
private static HashMap<String, TexturePack> packs = new HashMap<String, TexturePack>();
|
||||||
|
|
||||||
|
private static final String TERRAIN_PNG = "terrain.png";
|
||||||
|
private static final String GRASSCOLOR_PNG = "misc/grasscolor.png";
|
||||||
|
private static final String FOLIAGECOLOR_PNG = "misc/foliagecolor.png";
|
||||||
|
private static final String WATER_PNG = "misc/water.png";
|
||||||
|
|
||||||
|
private int[] terrain_argb;
|
||||||
|
private int terrain_width, terrain_height;
|
||||||
|
private int native_scale;
|
||||||
|
|
||||||
|
private int[] grasscolor_argb;
|
||||||
|
private int grasscolor_width, grasscolor_height;
|
||||||
|
|
||||||
|
private int[] foliagecolor_argb;
|
||||||
|
private int foliagecolor_width, foliagecolor_height;
|
||||||
|
|
||||||
|
private int[] water_argb;
|
||||||
|
private int water_width, water_height;
|
||||||
|
|
||||||
|
private HashMap<Integer, TexturePack> scaled_textures;
|
||||||
|
|
||||||
|
/** Get or load texture pack */
|
||||||
|
public static TexturePack getTexturePack(String tpname) {
|
||||||
|
TexturePack tp = packs.get(tpname);
|
||||||
|
if(tp != null)
|
||||||
|
return tp;
|
||||||
|
try {
|
||||||
|
tp = new TexturePack(tpname); /* Attempt to load pack */
|
||||||
|
packs.put(tpname, tp);
|
||||||
|
return tp;
|
||||||
|
} catch (FileNotFoundException fnfx) {
|
||||||
|
Log.severe("Error loading texture pack '" + tpname + "' - not found");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Constructor for texture pack, by name
|
||||||
|
*/
|
||||||
|
private TexturePack(String tpname) throws FileNotFoundException {
|
||||||
|
ZipFile zf = null;
|
||||||
|
File texturedir = getTexturePackDirectory();
|
||||||
|
try {
|
||||||
|
/* Try to open zip */
|
||||||
|
zf = new ZipFile(new File(texturedir, tpname + ".zip"));
|
||||||
|
/* Find and load terrain.png */
|
||||||
|
ZipEntry ze = zf.getEntry(TERRAIN_PNG); /* Try to find terrain.png */
|
||||||
|
if(ze == null) {
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
}
|
||||||
|
InputStream is = zf.getInputStream(ze); /* Get input stream for terrain.png */
|
||||||
|
loadTerrainPNG(is);
|
||||||
|
is.close();
|
||||||
|
/* Try to find and load misc/grasscolor.png */
|
||||||
|
ze = zf.getEntry(GRASSCOLOR_PNG);
|
||||||
|
if(ze != null) { /* Found it, so load it */
|
||||||
|
is = zf.getInputStream(ze);
|
||||||
|
loadGrassColorPNG(is);
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
/* Try to find and load misc/foliagecolor.png */
|
||||||
|
ze = zf.getEntry(FOLIAGECOLOR_PNG);
|
||||||
|
if(ze != null) { /* Found it, so load it */
|
||||||
|
is = zf.getInputStream(ze);
|
||||||
|
loadFoliageColorPNG(is);
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
/* Try to find and load misc/water.png */
|
||||||
|
ze = zf.getEntry(WATER_PNG);
|
||||||
|
if(ze != null) { /* Found it, so load it */
|
||||||
|
is = zf.getInputStream(ze);
|
||||||
|
loadWaterPNG(is);
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
zf.close();
|
||||||
|
return;
|
||||||
|
} catch (IOException iox) {
|
||||||
|
if(zf != null) {
|
||||||
|
try { zf.close(); } catch (IOException io) {}
|
||||||
|
}
|
||||||
|
/* No zip, or bad - try directory next */
|
||||||
|
}
|
||||||
|
/* Try loading terrain.png from directory of name */
|
||||||
|
File f = null;
|
||||||
|
FileInputStream fis = null;
|
||||||
|
try {
|
||||||
|
/* Open and load terrain.png */
|
||||||
|
f = new File(texturedir, tpname + "/" + TERRAIN_PNG);
|
||||||
|
fis = new FileInputStream(f);
|
||||||
|
loadTerrainPNG(fis);
|
||||||
|
fis.close();
|
||||||
|
/* Check for misc/grasscolor.png */
|
||||||
|
f = new File(texturedir, tpname + "/" + GRASSCOLOR_PNG);
|
||||||
|
if(f.canRead()) {
|
||||||
|
fis = new FileInputStream(f);
|
||||||
|
loadGrassColorPNG(fis);
|
||||||
|
fis.close();
|
||||||
|
}
|
||||||
|
/* Check for misc/foliagecolor.png */
|
||||||
|
f = new File(texturedir, tpname + "/" + FOLIAGECOLOR_PNG);
|
||||||
|
if(f.canRead()) {
|
||||||
|
fis = new FileInputStream(f);
|
||||||
|
loadFoliageColorPNG(fis);
|
||||||
|
fis.close();
|
||||||
|
}
|
||||||
|
/* Check for misc/water.png */
|
||||||
|
f = new File(texturedir, tpname + "/" + WATER_PNG);
|
||||||
|
if(f.canRead()) {
|
||||||
|
fis = new FileInputStream(f);
|
||||||
|
loadWaterPNG(fis);
|
||||||
|
fis.close();
|
||||||
|
}
|
||||||
|
} catch (IOException iox) {
|
||||||
|
if(fis != null) {
|
||||||
|
try { fis.close(); } catch (IOException io) {}
|
||||||
|
}
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Copy texture pack */
|
||||||
|
private TexturePack(TexturePack tp) {
|
||||||
|
this.terrain_argb = tp.terrain_argb;
|
||||||
|
this.terrain_width = tp.terrain_width;
|
||||||
|
this.terrain_height = tp.terrain_height;
|
||||||
|
this.native_scale = tp.native_scale;
|
||||||
|
|
||||||
|
this.grasscolor_argb = tp.grasscolor_argb;
|
||||||
|
this.grasscolor_height = tp.grasscolor_height;
|
||||||
|
this.grasscolor_width = tp.grasscolor_width;
|
||||||
|
|
||||||
|
this.foliagecolor_argb = tp.foliagecolor_argb;
|
||||||
|
this.foliagecolor_height = tp.foliagecolor_height;
|
||||||
|
this.foliagecolor_width = tp.foliagecolor_width;
|
||||||
|
|
||||||
|
this.water_argb = tp.water_argb;
|
||||||
|
this.water_height = tp.water_height;
|
||||||
|
this.water_width = tp.water_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load terrain.png */
|
||||||
|
private void loadTerrainPNG(InputStream is) throws IOException {
|
||||||
|
/* Load image */
|
||||||
|
BufferedImage img = ImageIO.read(is);
|
||||||
|
if(img == null) { throw new FileNotFoundException(); }
|
||||||
|
terrain_width = img.getWidth();
|
||||||
|
terrain_height = img.getHeight();
|
||||||
|
terrain_argb = new int[terrain_width * terrain_height];
|
||||||
|
img.getRGB(0, 0, terrain_width, terrain_height, terrain_argb, 0, terrain_width);
|
||||||
|
img.flush();
|
||||||
|
native_scale = terrain_width / 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load misc/grasscolor.png */
|
||||||
|
private void loadGrassColorPNG(InputStream is) throws IOException {
|
||||||
|
/* Load image */
|
||||||
|
BufferedImage img = ImageIO.read(is);
|
||||||
|
if(img == null) { throw new FileNotFoundException(); }
|
||||||
|
grasscolor_width = img.getWidth();
|
||||||
|
grasscolor_height = img.getHeight();
|
||||||
|
grasscolor_argb = new int[grasscolor_width * grasscolor_height];
|
||||||
|
img.getRGB(0, 0, grasscolor_width, grasscolor_height, grasscolor_argb, 0, grasscolor_width);
|
||||||
|
img.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load misc/foliagecolor.png */
|
||||||
|
private void loadFoliageColorPNG(InputStream is) throws IOException {
|
||||||
|
/* Load image */
|
||||||
|
BufferedImage img = ImageIO.read(is);
|
||||||
|
if(img == null) { throw new FileNotFoundException(); }
|
||||||
|
foliagecolor_width = img.getWidth();
|
||||||
|
foliagecolor_height = img.getHeight();
|
||||||
|
foliagecolor_argb = new int[foliagecolor_width * foliagecolor_height];
|
||||||
|
img.getRGB(0, 0, foliagecolor_width, foliagecolor_height, foliagecolor_argb, 0, foliagecolor_width);
|
||||||
|
img.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load misc/water.png */
|
||||||
|
private void loadWaterPNG(InputStream is) throws IOException {
|
||||||
|
/* Load image */
|
||||||
|
BufferedImage img = ImageIO.read(is);
|
||||||
|
if(img == null) { throw new FileNotFoundException(); }
|
||||||
|
water_width = img.getWidth();
|
||||||
|
water_height = img.getHeight();
|
||||||
|
water_argb = new int[water_width * water_height];
|
||||||
|
img.getRGB(0, 0, water_width, water_height, water_argb, 0, water_width);
|
||||||
|
img.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get texture pack directory */
|
||||||
|
private static File getTexturePackDirectory() {
|
||||||
|
// return new File(DynmapPlugin.dataDirectory, "texturepacks");
|
||||||
|
return new File("texturepacks");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resample terrain pack for given scale, and return copy using that scale
|
||||||
|
*/
|
||||||
|
public TexturePack resampleTexturePack(int scale) {
|
||||||
|
if(scaled_textures == null) scaled_textures = new HashMap<Integer, TexturePack>();
|
||||||
|
TexturePack stp = scaled_textures.get(scale);
|
||||||
|
if(stp != null)
|
||||||
|
return stp;
|
||||||
|
stp = new TexturePack(this); /* Make copy */
|
||||||
|
/* Scale terrain.png, if needed */
|
||||||
|
if(stp.native_scale != scale) {
|
||||||
|
stp.native_scale = scale;
|
||||||
|
stp.terrain_height = 16*scale;
|
||||||
|
stp.terrain_width = 16*scale;
|
||||||
|
stp.terrain_argb = new int[stp.terrain_height*stp.terrain_width];
|
||||||
|
scaleTerrainPNG(stp);
|
||||||
|
}
|
||||||
|
/* Remember it */
|
||||||
|
scaled_textures.put(scale, stp);
|
||||||
|
return stp;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Scale out terrain_argb into the terrain_argb of the provided destination, matching the scale of that destination
|
||||||
|
* @param tp
|
||||||
|
*/
|
||||||
|
private void scaleTerrainPNG(TexturePack tp) {
|
||||||
|
/* Terrain.png is 16x16 array of images : process one at a time */
|
||||||
|
for(int ty = 0; ty < 16; ty++) {
|
||||||
|
for(int tx = 0; tx < 16; tx++) {
|
||||||
|
int srcoff = ty*native_scale*terrain_width + tx*native_scale;
|
||||||
|
int destoff = ty*tp.native_scale*tp.terrain_width + tx*tp.native_scale;
|
||||||
|
scaleTerrainPNGSubImage(tp, srcoff, destoff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void scaleTerrainPNGSubImage(TexturePack tp, int srcoff, int destoff) {
|
||||||
|
int nativeres = native_scale;
|
||||||
|
int res = tp.native_scale;
|
||||||
|
Color c = new Color();
|
||||||
|
/* If we're scaling larger source pixels into smaller pixels, each destination pixel
|
||||||
|
* receives input from 1 or 2 source pixels on each axis
|
||||||
|
*/
|
||||||
|
if(res > nativeres) {
|
||||||
|
int weights[] = new int[res];
|
||||||
|
int offsets[] = new int[res];
|
||||||
|
/* LCM of resolutions is used as length of line (res * nativeres)
|
||||||
|
* Each native block is (res) long, each scaled block is (nativeres) long
|
||||||
|
* Each scaled block overlaps 1 or 2 native blocks: starting with native block 'offsets[]' with
|
||||||
|
* 'weights[]' of its (res) width in the first, and the rest in the second
|
||||||
|
*/
|
||||||
|
for(int v = 0, idx = 0; v < res*nativeres; v += nativeres, idx++) {
|
||||||
|
offsets[idx] = (v/res); /* Get index of the first native block we draw from */
|
||||||
|
if((v+nativeres-1)/res == offsets[idx]) { /* If scaled block ends in same native block */
|
||||||
|
weights[idx] = nativeres;
|
||||||
|
}
|
||||||
|
else { /* Else, see how much is in first one */
|
||||||
|
weights[idx] = (offsets[idx] + res) - v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Now, use weights and indices to fill in scaled map */
|
||||||
|
for(int y = 0, off = 0; y < res; y++) {
|
||||||
|
int ind_y = offsets[y];
|
||||||
|
int wgt_y = weights[y];
|
||||||
|
for(int x = 0; x < res; x++, off++) {
|
||||||
|
int ind_x = offsets[x];
|
||||||
|
int wgt_x = weights[x];
|
||||||
|
int accum_red = 0;
|
||||||
|
int accum_green = 0;
|
||||||
|
int accum_blue = 0;
|
||||||
|
int accum_alpha = 0;
|
||||||
|
for(int xx = 0; xx < 2; xx++) {
|
||||||
|
int wx = (xx==0)?wgt_x:(nativeres-wgt_x);
|
||||||
|
if(wx == 0) continue;
|
||||||
|
for(int yy = 0; yy < 2; yy++) {
|
||||||
|
int wy = (yy==0)?wgt_y:(nativeres-wgt_y);
|
||||||
|
if(wy == 0) continue;
|
||||||
|
/* Accumulate */
|
||||||
|
c.setARGB(terrain_argb[srcoff + (ind_y+yy)*terrain_width + ind_x + xx]);
|
||||||
|
accum_red += c.getRed() * wx * wy;
|
||||||
|
accum_green += c.getGreen() * wx * wy;
|
||||||
|
accum_blue += c.getBlue() * wx * wy;
|
||||||
|
accum_alpha += c.getAlpha() * wx * wy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Generate weighted compnents into color */
|
||||||
|
c.setRGBA(accum_red / (nativeres*nativeres), accum_green / (nativeres*nativeres),
|
||||||
|
accum_blue / (nativeres*nativeres), accum_alpha / (nativeres*nativeres));
|
||||||
|
tp.terrain_argb[destoff + (y*tp.terrain_width) + x] = c.getARGB();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { /* nativeres > res */
|
||||||
|
int weights[] = new int[nativeres];
|
||||||
|
int offsets[] = new int[nativeres];
|
||||||
|
/* LCM of resolutions is used as length of line (res * nativeres)
|
||||||
|
* Each native block is (res) long, each scaled block is (nativeres) long
|
||||||
|
* Each native block overlaps 1 or 2 scaled blocks: starting with scaled block 'offsets[]' with
|
||||||
|
* 'weights[]' of its (res) width in the first, and the rest in the second
|
||||||
|
*/
|
||||||
|
for(int v = 0, idx = 0; v < res*nativeres; v += res, idx++) {
|
||||||
|
offsets[idx] = (v/nativeres); /* Get index of the first scaled block we draw to */
|
||||||
|
if((v+res-1)/nativeres == offsets[idx]) { /* If native block ends in same scaled block */
|
||||||
|
weights[idx] = res;
|
||||||
|
}
|
||||||
|
else { /* Else, see how much is in first one */
|
||||||
|
weights[idx] = (offsets[idx] + nativeres) - v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int accum_red[] = new int[res*res];
|
||||||
|
int accum_green[] = new int[res*res];
|
||||||
|
int accum_blue[] = new int[res*res];
|
||||||
|
int accum_alpha[] = new int[res*res];
|
||||||
|
|
||||||
|
/* Now, use weights and indices to fill in scaled map */
|
||||||
|
for(int y = 0; y < nativeres; y++) {
|
||||||
|
int ind_y = offsets[y];
|
||||||
|
int wgt_y = weights[y];
|
||||||
|
for(int x = 0; x < nativeres; x++) {
|
||||||
|
int ind_x = offsets[x];
|
||||||
|
int wgt_x = weights[x];
|
||||||
|
c.setARGB(terrain_argb[srcoff + (y*terrain_width) + x]);
|
||||||
|
for(int xx = 0; xx < 2; xx++) {
|
||||||
|
int wx = (xx==0)?wgt_x:(res-wgt_x);
|
||||||
|
if(wx == 0) continue;
|
||||||
|
for(int yy = 0; yy < 2; yy++) {
|
||||||
|
int wy = (yy==0)?wgt_y:(res-wgt_y);
|
||||||
|
if(wy == 0) continue;
|
||||||
|
accum_red[(ind_y+yy)*res + (ind_x+xx)] += c.getRed() * wx * wy;
|
||||||
|
accum_green[(ind_y+yy)*res + (ind_x+xx)] += c.getGreen() * wx * wy;
|
||||||
|
accum_blue[(ind_y+yy)*res + (ind_x+xx)] += c.getBlue() * wx * wy;
|
||||||
|
accum_alpha[(ind_y+yy)*res + (ind_x+xx)] += c.getAlpha() * wx * wy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Produce normalized scaled values */
|
||||||
|
for(int y = 0; y < res; y++) {
|
||||||
|
for(int x = 0; x < res; x++) {
|
||||||
|
int off = (y*res) + x;
|
||||||
|
c.setRGBA(accum_red[off]/(nativeres*nativeres), accum_green[off]/(nativeres*nativeres),
|
||||||
|
accum_blue[off]/(nativeres*nativeres), accum_alpha[off]/(nativeres*nativeres));
|
||||||
|
tp.terrain_argb[destoff + y*tp.terrain_width + x] = c.getARGB();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void saveTerrainPNG(File f) throws IOException {
|
||||||
|
BufferedImage img = KzedMap.createBufferedImage(terrain_argb, terrain_width, terrain_height);
|
||||||
|
ImageIO.write(img, "png", f);
|
||||||
|
}
|
||||||
|
public static void main(String[] args) {
|
||||||
|
TexturePack tp = TexturePack.getTexturePack("test");
|
||||||
|
TexturePack tp2 = tp.resampleTexturePack(4);
|
||||||
|
try {
|
||||||
|
tp2.saveTerrainPNG(new File("test_terrain_4.png"));
|
||||||
|
} catch (IOException iox) {}
|
||||||
|
tp2 = tp.resampleTexturePack(16);
|
||||||
|
try {
|
||||||
|
tp2.saveTerrainPNG(new File("test_terrain_16.png"));
|
||||||
|
} catch (IOException iox) {}
|
||||||
|
tp2 = tp.resampleTexturePack(24);
|
||||||
|
try {
|
||||||
|
tp2.saveTerrainPNG(new File("test_terrain_24.png"));
|
||||||
|
} catch (IOException iox) {}
|
||||||
|
tp2 = tp.resampleTexturePack(64);
|
||||||
|
try {
|
||||||
|
tp2.saveTerrainPNG(new File("test_terrain_64.png"));
|
||||||
|
} catch (IOException iox) {}
|
||||||
|
tp2 = tp.resampleTexturePack(1);
|
||||||
|
try {
|
||||||
|
tp2.saveTerrainPNG(new File("test_terrain_1.png"));
|
||||||
|
} catch (IOException iox) {}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user