diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index aa50a32d..aeb58063 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -120,6 +120,7 @@ import com.sk89q.worldedit.util.formatting.component.CommandUsageBox; import com.sk89q.worldedit.util.formatting.component.MessageBox; import com.sk89q.worldedit.world.registry.BundledBlockData; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; @@ -135,6 +136,7 @@ import javax.management.InstanceAlreadyExistsException; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationListener; +import org.json.simple.parser.ParseException; /**[ WorldEdit action] * | @@ -326,7 +328,14 @@ public class Fawe { synchronized (this) { tmp = textures; if (tmp == null) { - textures = tmp = new TextureUtil(); + try { + textures = tmp = new TextureUtil(); + tmp.loadModTextures(); + } catch (IOException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } } } } @@ -518,6 +527,7 @@ public class Fawe { File extraBlocks = MainUtil.copyFile(jar, "extrablocks.json", null); if (extraBlocks != null && extraBlocks.exists()) { try { + BundledBlockData.getInstance().loadFromResource(); BundledBlockData.getInstance().add(extraBlocks.toURI().toURL(), true); } catch (Throwable ignore) { Fawe.debug("Invalid format: extrablocks.json"); diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java index 025fd6e9..5a9ae772 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java @@ -1,7 +1,9 @@ package com.boydti.fawe.jnbt.anvil; +import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; import com.boydti.fawe.object.PseudoRandom; +import com.boydti.fawe.util.CachedTextureUtil; import com.boydti.fawe.util.MathMan; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MutableBlockVector; @@ -235,6 +237,24 @@ public class HeightMapMCAGenerator extends MCAWriter implements Extent { } } + public void setColor(BufferedImage img) { + CachedTextureUtil textureUtil = new CachedTextureUtil(Fawe.get().getTextureUtil()); + if (img.getWidth() != getWidth() || img.getHeight() != getLength()) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + int index = 0; + for (int y = 0; y < img.getHeight(); y++) { + for (int x = 0; x < img.getWidth(); x++) { + int color = img.getRGB(x, y); + BaseBlock block = textureUtil.getNearestBlock(color); + if (block == null) { + continue; + } + char combined = (char) block.getCombined(); + main[index] = combined; + floor[index++] = combined; + } + } + } + public void setBiome(Mask mask, byte biome) { int index = 0; for (int z = 0; z < getLength(); z++) { diff --git a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java index d032b636..7ea1b155 100644 --- a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java +++ b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java @@ -138,6 +138,7 @@ public class CreateFromImage extends Command { fp.sendMessage(BBC.getPrefix() + "/2 cfi addore[s]"); fp.sendMessage(BBC.getPrefix() + "/2 cfi addschems"); fp.sendMessage(BBC.getPrefix() + "/2 cfi setheight"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi setcolor"); fp.sendMessage(BBC.getPrefix() + "/2 cfi done"); fp.sendMessage(BBC.getPrefix() + "/2 cfi cancel"); File folder = new File(PS.imp().getWorldContainer(), plot.getWorldName() + File.separator + "region"); @@ -161,12 +162,12 @@ public class CreateFromImage extends Command { return; } if (argList.size() == 1) { - if (StringMan.isEqualIgnoreCaseToAny(argList.get(0), "setbiome", "setoverlay", "setmain", "setfloor", "setcolumn")) { + if (StringMan.isEqualIgnoreCaseToAny(argList.get(0), "setbiome", "setoverlay", "setmain", "setfloor", "setcolumn", "setcolor")) { C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " [white-only]"); C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " "); return; } else if (!StringMan.isEqualIgnoreCaseToAny(argList.get(0), "done", "cancel", "addcaves", "addore", "addores", "addschems", "setheight")) { - C.COMMAND_SYNTAX.send(player, "/2 cfi "); + C.COMMAND_SYNTAX.send(player, "/2 cfi "); return; } } @@ -196,6 +197,16 @@ public class CreateFromImage extends Command { player.sendMessage(BBC.getPrefix() + "Added schems, what's next?"); return; } + case "setcolor": { + if (argList.size() != 2) { + C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " "); + return; + } + BufferedImage image = getImgurImage(argList.get(1), fp); + generator.setColor(image); + player.sendMessage("Set color, what's next?"); + return; + } case "setheight": { if (argList.size() != 2) { C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " "); diff --git a/core/src/main/java/com/boydti/fawe/util/CachedTextureUtil.java b/core/src/main/java/com/boydti/fawe/util/CachedTextureUtil.java new file mode 100644 index 00000000..f39ab311 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/util/CachedTextureUtil.java @@ -0,0 +1,79 @@ +package com.boydti.fawe.util; + +import com.boydti.fawe.FaweCache; +import com.sk89q.worldedit.blocks.BaseBlock; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import org.json.simple.parser.ParseException; + +public class CachedTextureUtil extends TextureUtil { + private final TextureUtil parent; + private Int2ObjectOpenHashMap colorBlockMap; + + + public CachedTextureUtil(TextureUtil parent) { + super(parent.getFolder()); + this.parent = parent; + this.colorBlockMap = new Int2ObjectOpenHashMap<>(); + } + + @Override + public BaseBlock getNearestBlock(int color) { + Integer value = colorBlockMap.get(color); + if (value != null) { + return FaweCache.CACHE_BLOCK[value]; + } + BaseBlock result = parent.getNearestBlock(color); + if (result != null) { + colorBlockMap.put((int) color, (Integer) result.getCombined()); + } + return result; + } + + @Override + public BaseBlock getDarkerBlock(BaseBlock block) { + return parent.getDarkerBlock(block); + } + + @Override + public int getColor(BaseBlock block) { + return parent.getColor(block); + } + + @Override + public File getFolder() { + return parent.getFolder(); + } + + @Override + public void loadModTextures() throws IOException, ParseException { + parent.loadModTextures(); + } + + @Override + public BaseBlock getNearestBlock(BaseBlock block, boolean darker) { + return parent.getNearestBlock(block, darker); + } + + @Override + public BaseBlock getNearestBlock(int color, boolean darker) { + return parent.getNearestBlock(color, darker); + } + + @Override + public long colorDistance(int c1, int c2) { + return parent.colorDistance(c1, c2); + } + + @Override + public int getColor(BufferedImage image) { + return parent.getColor(image); + } + + @Override + public BaseBlock getLighterBlock(BaseBlock block) { + return parent.getLighterBlock(block); + } +} diff --git a/core/src/main/java/com/boydti/fawe/util/TextureUtil.java b/core/src/main/java/com/boydti/fawe/util/TextureUtil.java index 233f00cb..a148bb38 100644 --- a/core/src/main/java/com/boydti/fawe/util/TextureUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/TextureUtil.java @@ -38,6 +38,224 @@ public class TextureUtil { private int[] validColors; private int[] validBlockIds; + public TextureUtil() { + this(MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.TEXTURES)); + } + + public TextureUtil(File folder) { + this.folder = folder; + } + + public BaseBlock getNearestBlock(int color) { + long min = Long.MAX_VALUE; + int closest = 0; + int red1 = (color >> 16) & 0xFF; + int green1 = (color >> 8) & 0xFF; + int blue1 = (color >> 0) & 0xFF; + int alpha = (color >> 24) & 0xFF; + for (int i = 0; i < validColors.length; i++) { + int other = validColors[i]; + if (((other >> 24) & 0xFF) == alpha) { + long distance = colorDistance(red1, green1, blue1, other); + if (distance < min) { + min = distance; + closest = validBlockIds[i]; + } + } + } + if (min == Long.MAX_VALUE) return null; + return FaweCache.CACHE_BLOCK[closest]; + } + + public BaseBlock getLighterBlock(BaseBlock block) { + return getNearestBlock(block, false); + } + + public BaseBlock getDarkerBlock(BaseBlock block) { + return getNearestBlock(block, false); + } + + public int getColor(BaseBlock block) { + return blockColors[block.getCombined()]; + } + + public File getFolder() { + return folder; + } + + public void loadModTextures() throws IOException, ParseException { + Int2ObjectOpenHashMap colorMap = new Int2ObjectOpenHashMap<>(); + if (folder.exists()) { + // Get all the jar files + for (File file : folder.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".jar"); + } + })) { + ZipFile zipFile = new ZipFile(file); + + BundledBlockData bundled = BundledBlockData.getInstance(); + + // Get all the groups in the current jar + // The vanilla textures are in `assets/minecraft` + // A jar may contain textures for multiple mods + Set mods = new HashSet(); + { + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + Path path = Paths.get(name); + if (path.startsWith("assets" + File.separator)) { + String[] split = path.toString().split(Pattern.quote(File.separator)); + if (split.length > 1) { + String modId = split[1]; + mods.add(modId); + } + } + continue; + } + } + for (String modId : mods) { + String modelsDir = "assets" + "/" + modId + "/" + "models" + "/" + "block"; + String texturesDir = "assets" + "/" + modId + "/" + "textures" + "/" + "blocks"; + Map texturesMap = new ConcurrentHashMap<>(); + { // Read models + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory()) { + continue; + } + String name = entry.getName(); + if (!name.endsWith(".json")) { + continue; + } + Path path = Paths.get(name); + if (path.startsWith(modelsDir)) { + String[] split = path.toString().split("[/|\\\\|\\.]"); + String blockName = getFileName(path.toString()); + // Ignore special models + if (blockName.startsWith("#")) { + continue; + } + try (InputStream is = zipFile.getInputStream(entry)) { //Read from a file, or a HttpRequest, or whatever. + JSONParser parser = new JSONParser(); + JSONObject root = (JSONObject) parser.parse(new InputStreamReader(is, "UTF-8")); + // Try to work out the texture names for this file + addTextureNames(blockName, root, texturesMap); + } + } + } + } + // Add some possible variations for the file names to try and match it to a block + // - As vanilla minecraft doesn't use consistent naming for the assets and block names + for (String key : new ArrayList<>(texturesMap.keySet())) { + String value = texturesMap.get(key); + texturesMap.put(alphabetize(key), value); + String[] split = key.split("_"); + if (split.length > 1) { + key = StringMan.join(Arrays.copyOfRange(split, 0, split.length - 1), "_"); + texturesMap.putIfAbsent(key, value); + } + } + // Try to match the textures to a block + Int2ObjectOpenHashMap idMap = new Int2ObjectOpenHashMap<>(); + for (String id : bundled.stateMap.keySet()) { + if (id.startsWith(modId)) { + BaseBlock block = bundled.findByState(id); + BundledBlockData.BlockEntry state = bundled.findById(block.getId()); + // Ignore non blocks + if (!state.material.isRenderedAsNormalBlock()) { + continue; + } + if (state.material.getLightValue() != 0) { + continue; + } + id = id.substring(modId.length() + 1).replaceAll(":", "_"); + String texture = texturesMap.remove(id); + if (texture == null) { + texture = texturesMap.remove(alphabetize(id)); + } + if (texture != null) { + int combined = block.getCombined(); + if (id.startsWith("log_") || id.startsWith("log2_")) { + combined += 12; + } + idMap.put(combined, texture); + } + } + } + { // Calculate the colors for each block + for (Int2ObjectMap.Entry entry : idMap.int2ObjectEntrySet()) { + int combined = entry.getIntKey(); + String path = texturesDir + "/" + entry.getValue() + ".png"; + ZipEntry textureEntry = zipFile.getEntry(path); + try (InputStream is = zipFile.getInputStream(textureEntry)) { + BufferedImage image = ImageIO.read(is); + int color = getColor(image); + colorMap.put((int) combined, (Integer) color); + } + } + } + } + // Close the file + zipFile.close(); + } + } + // Convert the color map to a simple array + validBlockIds = new int[colorMap.size()]; + validColors = new int[colorMap.size()]; + int index = 0; + for (Int2ObjectMap.Entry entry : colorMap.int2ObjectEntrySet()) { + int combinedId = entry.getIntKey(); + int color = entry.getValue(); + blockColors[combinedId] = color; + validBlockIds[index] = combinedId; + validColors[index] = color; + index++; + } + } + + protected BaseBlock getNearestBlock(BaseBlock block, boolean darker) { + int color = getColor(block); + if (color == 0) { + return block; + } + BaseBlock darkerBlock = getNearestBlock(color, darker); + return darkerBlock != null ? darkerBlock : block; + } + + protected BaseBlock getNearestBlock(int color, boolean darker) { + long min = Long.MAX_VALUE; + int closest = 0; + int red1 = (color >> 16) & 0xFF; + int green1 = (color >> 8) & 0xFF; + int blue1 = (color >> 0) & 0xFF; + int alpha = (color >> 24) & 0xFF; + int intensity1 = red1 + green1 + blue1; + for (int i = 0; i < validColors.length; i++) { + int other = validColors[i]; + if (other != color && ((other >> 24) & 0xFF) == alpha) { + int red2 = (other >> 16) & 0xFF; + int green2 = (other >> 8) & 0xFF; + int blue2 = (other >> 0) & 0xFF; + int intensity2 = red2 + green2 + blue2; + if (darker ? intensity2 >= intensity1 : intensity1 >= intensity2) { + continue; + } + long distance = colorDistance(red1, green1, blue1, other); + if (distance < min) { + min = distance; + closest = validBlockIds[i]; + } + } + } + if (min == Long.MAX_VALUE) return null; + return FaweCache.CACHE_BLOCK[closest]; + } + private String getFileName(String path) { String[] split = path.toString().split("[/|\\\\]"); String name = split[split.length - 1]; @@ -108,162 +326,6 @@ public class TextureUtil { } } - public TextureUtil() throws IOException, ParseException { - this(MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.TEXTURES)); - } - - public TextureUtil(File folder) throws IOException, ParseException { - this.folder = folder; - loadModTextures(); - } - - public void loadModTextures() throws IOException, ParseException { - if (!folder.exists()) { - return; - } - for (File file : folder.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".jar"); - } - })) { - ZipFile zipFile = new ZipFile(file); - // get mods - - BundledBlockData bundled = BundledBlockData.getInstance(); - bundled.loadFromResource(); - - Set mods = new HashSet(); - { - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - String name = entry.getName(); - Path path = Paths.get(name); - if (path.startsWith("assets" + File.separator)) { - String[] split = path.toString().split(Pattern.quote(File.separator)); - if (split.length > 1) { - String modId = split[1]; - if (mods.add(modId)) { - } - } - } - continue; - } - } - Int2ObjectOpenHashMap colorMap = new Int2ObjectOpenHashMap<>(); - for (String modId : mods) { - String modelsDir = "assets" + "/" + modId + "/" + "models" + "/" + "block"; - String texturesDir = "assets" + "/" + modId + "/" + "textures" + "/" + "blocks"; - Map texturesMap = new ConcurrentHashMap<>(); - // Read models - { - // Read .json - // Find texture file - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - if (entry.isDirectory()) { - continue; - } - String name = entry.getName(); - if (!name.endsWith(".json")) { - continue; - } - Path path = Paths.get(name); - if (path.startsWith(modelsDir)) { - String[] split = path.toString().split("[/|\\\\|\\.]"); - String blockName = getFileName(path.toString()); - // Ignore special models - if (blockName.startsWith("#")) { - continue; - } - try (InputStream is = zipFile.getInputStream(entry)) { //Read from a file, or a HttpRequest, or whatever. - JSONParser parser = new JSONParser(); - JSONObject root = (JSONObject) parser.parse(new InputStreamReader(is, "UTF-8")); - addTextureNames(blockName, root, texturesMap); - } - } - } - } - for (String key : new ArrayList<>(texturesMap.keySet())) { - String value = texturesMap.get(key); - texturesMap.put(alphabetize(key), value); - String[] split = key.split("_"); - if (split.length > 1) { - key = StringMan.join(Arrays.copyOfRange(split, 0, split.length - 1), "_"); - texturesMap.putIfAbsent(key, value); - } - } - Int2ObjectOpenHashMap idMap = new Int2ObjectOpenHashMap<>(); - for (String id : bundled.stateMap.keySet()) { - if (id.startsWith(modId)) { - BaseBlock block = bundled.findByState(id); - id = id.substring(modId.length() + 1).replaceAll(":", "_"); - String texture = texturesMap.remove(id); - if (texture == null) { - texture = texturesMap.remove(alphabetize(id)); - } - if (texture != null) { - idMap.put(block.getCombined(), texture); - } - } - } - { - for (Int2ObjectMap.Entry entry : idMap.int2ObjectEntrySet()) { - int combined = entry.getIntKey(); - String path = texturesDir + "/" + entry.getValue() + ".png"; - ZipEntry textureEntry = zipFile.getEntry(path); - try (InputStream is = zipFile.getInputStream(textureEntry)) { - BufferedImage image = ImageIO.read(is); - int color = getColor(image); - colorMap.put((int) combined, (Integer) color); - } - } - // Load and map the textures - // - } - } - validBlockIds = new int[colorMap.size()]; - validColors = new int[colorMap.size()]; - Arrays.fill(blockColors, 0); - int index = 0; - for (Int2ObjectMap.Entry entry : colorMap.int2ObjectEntrySet()) { - int combinedId = entry.getIntKey(); - int color = entry.getValue(); - blockColors[combinedId] = color; - validBlockIds[index] = combinedId; - validColors[index] = color; - index++; - } - zipFile.close(); - } - } - - public BaseBlock getNearestBlock(int color) { - long min = Long.MAX_VALUE; - int closest = 0; - int red1 = (color >> 16) & 0xFF; - int green1 = (color >> 8) & 0xFF; - int blue1 = (color >> 0) & 0xFF; - int alpha = (color >> 24) & 0xFF; - for (int i = 0; i < validColors.length; i++) { - int other = validColors[i]; - if (((other >> 24) & 0xFF) == alpha) { - long distance = colorDistance(red1, green1, blue1, other); - if (distance < min) { - min = distance; - closest = validBlockIds[i]; - } - } - } - return FaweCache.CACHE_BLOCK[closest]; - } - - public int getColor(BaseBlock block) { - return blockColors[block.getCombined()]; - } - private boolean hasAlpha(int color) { int alpha = (color >> 24) & 0xFF; return alpha != 255; @@ -307,12 +369,4 @@ public class TextureUtil { Color color = new Color((int) (totalRed / a), (int) (totalGreen / a), (int) (totalBlue / a), (int) (totalAlpha / a)); return color.getRGB(); } - -// public Color getColor(BaseBlock block) { -// long r; -// long b; -// long g; -// long a; -// -// } }