diff --git a/common/addons/library-image/build.gradle.kts b/common/addons/library-image/build.gradle.kts new file mode 100644 index 000000000..6f5d40074 --- /dev/null +++ b/common/addons/library-image/build.gradle.kts @@ -0,0 +1,8 @@ +version = version("1.0.0") + +dependencies { + compileOnlyApi(project(":common:addons:manifest-addon-loader")) + + implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) + testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java new file mode 100644 index 000000000..020740059 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java @@ -0,0 +1,49 @@ +package com.dfsek.terra.addons.image; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.awt.image.BufferedImage; +import java.util.function.Supplier; + +import com.dfsek.terra.addons.image.config.BufferedImageLoader; +import com.dfsek.terra.addons.image.config.picker.SingleColorPickerTemplate; +import com.dfsek.terra.addons.image.config.picker.TileColorPickerTemplate; +import com.dfsek.terra.addons.image.picker.ColorPicker; +import com.dfsek.terra.addons.manifest.api.AddonInitializer; +import com.dfsek.terra.api.Platform; +import com.dfsek.terra.api.addon.BaseAddon; +import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; +import com.dfsek.terra.api.event.functional.FunctionalEventHandler; +import com.dfsek.terra.api.inject.annotations.Inject; +import com.dfsek.terra.api.registry.CheckedRegistry; +import com.dfsek.terra.api.util.reflection.TypeKey; + + +public class ImageLibraryAddon implements AddonInitializer { + + public static final TypeKey>> COLOR_PICKER_REGISTRY_KEY = new TypeKey<>() { + }; + + @Inject + private Platform platform; + + @Inject + private BaseAddon addon; + + @Override + public void initialize() { + platform.getEventManager() + .getHandler(FunctionalEventHandler.class) + .register(addon, ConfigPackPreLoadEvent.class) + .priority(10) + .then(event -> { + event.getPack().applyLoader(BufferedImage.class, new BufferedImageLoader(event.getPack().getLoader())); + }) + .then(event -> { + CheckedRegistry>> colorPickerRegistry = event.getPack().getOrCreateRegistry( + COLOR_PICKER_REGISTRY_KEY); + colorPickerRegistry.register(addon.key("SINGLE"), SingleColorPickerTemplate::new); + colorPickerRegistry.register(addon.key("TILED"), TileColorPickerTemplate::new); + }); + } +} diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/config/BufferedImageLoader.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/BufferedImageLoader.java similarity index 58% rename from common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/config/BufferedImageLoader.java rename to common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/BufferedImageLoader.java index 98f24842c..d31bce66d 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/config/loaders/config/BufferedImageLoader.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/BufferedImageLoader.java @@ -1,21 +1,4 @@ -/* - * This file is part of Terra. - * - * Terra 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 3 of the License, or - * (at your option) any later version. - * - * Terra 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 Terra. If not, see . - */ - -package com.dfsek.terra.config.loaders.config; +package com.dfsek.terra.addons.image.config; import com.dfsek.tectonic.api.depth.DepthTracker; import com.dfsek.tectonic.api.exception.LoadException; diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ClosestColorConverterTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ClosestColorConverterTemplate.java new file mode 100644 index 000000000..61aca4b79 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ClosestColorConverterTemplate.java @@ -0,0 +1,16 @@ +package com.dfsek.terra.addons.image.config.converter; + +import com.dfsek.terra.addons.image.converter.ClosestMatchColorConverter; +import com.dfsek.terra.addons.image.converter.ColorConverter; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; + + +public abstract class ClosestColorConverterTemplate implements ColorConverterTemplate { + + protected abstract ColorMapping getMapping(); + + @Override + public ColorConverter get() { + return new ClosestMatchColorConverter(getMapping().get()); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ColorConverterTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ColorConverterTemplate.java new file mode 100644 index 000000000..c0cf7a7d5 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ColorConverterTemplate.java @@ -0,0 +1,9 @@ +package com.dfsek.terra.addons.image.config.converter; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.image.converter.ColorConverter; + + +public interface ColorConverterTemplate extends ObjectTemplate> { +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ExactColorConverterTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ExactColorConverterTemplate.java new file mode 100644 index 000000000..6c0a8df28 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/converter/ExactColorConverterTemplate.java @@ -0,0 +1,18 @@ +package com.dfsek.terra.addons.image.config.converter; + +import com.dfsek.terra.addons.image.converter.ColorConverter; +import com.dfsek.terra.addons.image.converter.ExactColorConverter; +import com.dfsek.terra.addons.image.converter.mapping.ColorMapping; + + +public abstract class ExactColorConverterTemplate implements ColorConverterTemplate { + + protected abstract ColorMapping getMapping(); + + protected abstract T getFallback(); + + @Override + public ColorConverter get() { + return new ExactColorConverter(getMapping().get(), getFallback()); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/picker/ColorPickerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/picker/ColorPickerTemplate.java new file mode 100644 index 000000000..d0ff0940a --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/picker/ColorPickerTemplate.java @@ -0,0 +1,17 @@ +package com.dfsek.terra.addons.image.config.picker; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.image.picker.ColorPicker; +import com.dfsek.terra.addons.image.picker.transform.Alignment; + + +public abstract class ColorPickerTemplate implements ObjectTemplate { + + @Value("align") + @Default + protected Alignment alignment = Alignment.NONE; + +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/picker/SingleColorPickerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/picker/SingleColorPickerTemplate.java new file mode 100644 index 000000000..1fb10e5b9 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/picker/SingleColorPickerTemplate.java @@ -0,0 +1,18 @@ +package com.dfsek.terra.addons.image.config.picker; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.image.picker.ColorPicker; +import com.dfsek.terra.addons.image.picker.SimpleColorPicker; + + +public class SingleColorPickerTemplate extends ColorPickerTemplate { + + @Value("fallback") + private int fallback; + + @Override + public ColorPicker get() { + return new SimpleColorPicker(fallback, alignment); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/picker/TileColorPickerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/picker/TileColorPickerTemplate.java new file mode 100644 index 000000000..1ee59b32e --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/picker/TileColorPickerTemplate.java @@ -0,0 +1,13 @@ +package com.dfsek.terra.addons.image.config.picker; + +import com.dfsek.terra.addons.image.picker.ColorPicker; +import com.dfsek.terra.addons.image.picker.TileColorPicker; + + +public class TileColorPickerTemplate extends ColorPickerTemplate { + + @Override + public ColorPicker get() { + return new TileColorPicker(alignment); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ClosestMatchColorConverter.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ClosestMatchColorConverter.java new file mode 100644 index 000000000..80bf1cee4 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ClosestMatchColorConverter.java @@ -0,0 +1,39 @@ +package com.dfsek.terra.addons.image.converter; + +import java.util.Map; + +import com.dfsek.terra.addons.image.util.ColorUtil; + +public class ClosestMatchColorConverter implements ColorConverter { + + private final Map map; + + private final Integer[] colors; + + public ClosestMatchColorConverter(Map map) { + this.map = map; + this.colors = map.keySet().toArray(new Integer[0]); + } + + @Override + public T apply(Integer color) { + int closest = 0; + int smallestDistance = Integer.MAX_VALUE; + for(int compare : colors) { + int distance = ColorUtil.distance(color, compare); + if(distance == 0) { + closest = compare; + break; + } else if(distance < smallestDistance) { + smallestDistance = distance; + closest = compare; + } + } + return map.get(closest); + } + + @Override + public Iterable getEntries() { + return map.values(); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ColorConverter.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ColorConverter.java new file mode 100644 index 000000000..bb5a87613 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ColorConverter.java @@ -0,0 +1,8 @@ +package com.dfsek.terra.addons.image.converter; + +import java.util.function.Function; + + +public interface ColorConverter extends Function { + Iterable getEntries(); +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ExactColorConverter.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ExactColorConverter.java new file mode 100644 index 000000000..1258a762b --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/ExactColorConverter.java @@ -0,0 +1,31 @@ +package com.dfsek.terra.addons.image.converter; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + + +public class ExactColorConverter implements ColorConverter { + private final Map map; + + private final T fallback; + + public ExactColorConverter(Map map, T fallback) { + this.map = map; + this.fallback = fallback; + } + + @Override + public T apply(Integer color) { + T lookup = map.get(color); + if(lookup != null) return lookup; + return fallback; + } + + @Override + public Iterable getEntries() { + Set entries = new HashSet<>(map.values()); + entries.add(fallback); + return entries; + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/BiomeDefinedColorMapping.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/BiomeDefinedColorMapping.java new file mode 100644 index 000000000..469596577 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/BiomeDefinedColorMapping.java @@ -0,0 +1,38 @@ +package com.dfsek.terra.addons.image.converter.mapping; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.dfsek.terra.api.registry.Registry; +import com.dfsek.terra.api.world.biome.Biome; + + +public class BiomeDefinedColorMapping implements ColorMapping { + + Registry biomeRegistry; + + Function converter; + + public BiomeDefinedColorMapping(Registry biomeRegistry, Function converter) { + this.biomeRegistry = biomeRegistry; + this.converter = converter; + } + + @Override + public Map get() { + Map colorMap = new HashSet<>(biomeRegistry.entries()).stream().collect(Collectors.toMap(b -> b, Biome::getColor)); + Map output = new HashMap<>(); + colorMap.forEach(((biome, color) -> { + if(!output.containsKey(color)) { + output.put(color, biome); + } else { + throw new IllegalArgumentException(String.format("Biome %s has same color as %s: %x", biome.getID(), output.get(color).getID(), color)); + } + })); + return output.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> converter.apply(e.getValue()))); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/ColorMapping.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/ColorMapping.java new file mode 100644 index 000000000..6fc586a84 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/converter/mapping/ColorMapping.java @@ -0,0 +1,8 @@ +package com.dfsek.terra.addons.image.converter.mapping; + +import java.util.Map; +import java.util.function.Supplier; + + +public interface ColorMapping extends Supplier> { +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/ColorPicker.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/ColorPicker.java new file mode 100644 index 000000000..7db4ef6f4 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/ColorPicker.java @@ -0,0 +1,15 @@ +package com.dfsek.terra.addons.image.picker; + +import java.awt.image.BufferedImage; + +@FunctionalInterface +public interface ColorPicker { + + /** + * @param image Lookup image + * @param x World x coordinate + * @param z World z coordinate + * @return Integer representing a web color + */ + Integer apply(BufferedImage image, int x, int z); +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/SimpleColorPicker.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/SimpleColorPicker.java new file mode 100644 index 000000000..66b75bb37 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/SimpleColorPicker.java @@ -0,0 +1,26 @@ +package com.dfsek.terra.addons.image.picker; + +import java.awt.image.BufferedImage; + +import com.dfsek.terra.addons.image.picker.transform.ImageTransformation; + + +public class SimpleColorPicker implements ColorPicker { + + private final Integer fallback; + + private final ImageTransformation transformation; + + public SimpleColorPicker(int fallbackColor, ImageTransformation transformation) { + this.fallback = fallbackColor; + this.transformation = transformation; + } + + @Override + public Integer apply(BufferedImage image, int x, int z) { + x = transformation.transformX(image, x); + z = transformation.transformZ(image, z); + if(x < 0 || z < 0 || x >= image.getWidth() || z >= image.getHeight()) return fallback; + return image.getRGB(x, z); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/TileColorPicker.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/TileColorPicker.java new file mode 100644 index 000000000..a70c03869 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/TileColorPicker.java @@ -0,0 +1,24 @@ +package com.dfsek.terra.addons.image.picker; + +import net.jafama.FastMath; + +import java.awt.image.BufferedImage; + +import com.dfsek.terra.addons.image.picker.transform.ImageTransformation; + + +public class TileColorPicker implements ColorPicker { + + private final ImageTransformation transformation; + + public TileColorPicker(ImageTransformation transformation) { + this.transformation = transformation; + } + + @Override + public Integer apply(BufferedImage image, int x, int z) { + x = transformation.transformX(image, x); + z = transformation.transformZ(image, z); + return image.getRGB(FastMath.floorMod(x, image.getWidth()), FastMath.floorMod(z, image.getHeight())); + } +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/transform/Alignment.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/transform/Alignment.java new file mode 100644 index 000000000..1441f3479 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/transform/Alignment.java @@ -0,0 +1,30 @@ +package com.dfsek.terra.addons.image.picker.transform; + +import java.awt.image.BufferedImage; + + +public enum Alignment implements ImageTransformation { + + NONE() { + @Override + public int transformX(BufferedImage image, int x) { + return x; + } + + @Override + public int transformZ(BufferedImage image, int z) { + return z; + } + }, + CENTER { + @Override + public int transformX(BufferedImage image, int x) { + return x + image.getWidth() / 2; + } + + @Override + public int transformZ(BufferedImage image, int z) { + return z + image.getHeight() / 2; + } + }; +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/transform/ImageTransformation.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/transform/ImageTransformation.java new file mode 100644 index 000000000..45a17acb7 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/picker/transform/ImageTransformation.java @@ -0,0 +1,11 @@ +package com.dfsek.terra.addons.image.picker.transform; + +import java.awt.image.BufferedImage; + + +public interface ImageTransformation { + + int transformX(BufferedImage image, int x); + + int transformZ(BufferedImage image, int z); +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/ColorUtil.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/ColorUtil.java new file mode 100644 index 000000000..202231c24 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/ColorUtil.java @@ -0,0 +1,30 @@ +package com.dfsek.terra.addons.image.util; + +import net.jafama.FastMath; + + +public class ColorUtil { + + private ColorUtil() {} + + public static int distance(int a, int b) { + return FastMath.abs(getRed(a) - getRed(b)) + FastMath.abs(getGreen(a) - getGreen(b)) + FastMath.abs(getBlue(a) - getBlue(b)); + } + + public static int getRed(int rgb) { + return rgb >> 16 & 255; + } + + public static int getGreen(int rgb) { + return rgb >> 8 & 255; + } + + public static int getBlue(int rgb) { + return rgb >> 0 & 255; + } + + public static int getAlpha(int rgb) { + return rgb >> 24 & 255; + } + +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/MapUtil.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/MapUtil.java new file mode 100644 index 000000000..03421f527 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/MapUtil.java @@ -0,0 +1,23 @@ +package com.dfsek.terra.addons.image.util; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + + +public class MapUtil { + + private MapUtil() {} + + /** + * Utility method for converting config keys into integers, + * used because Tectonic does not accept non string keys. + */ + public static Map convertKeysToInt(Map map) { + return map.entrySet().stream() + .collect(Collectors.toMap( + e -> Integer.decode(e.getKey()), + Entry::getValue + )); + } +} diff --git a/common/addons/library-image/src/main/resources/terra.addon.yml b/common/addons/library-image/src/main/resources/terra.addon.yml new file mode 100644 index 000000000..c664c3df4 --- /dev/null +++ b/common/addons/library-image/src/main/resources/terra.addon.yml @@ -0,0 +1,12 @@ +schema-version: 1 +contributors: + - Terra contributors +id: library-image +version: @VERSION@ +entrypoints: + - "com.dfsek.terra.addons.image.ImageLibraryAddon" +website: + issues: https://github.com/PolyhedralDev/Terra/issues + source: https://github.com/PolyhedralDev/Terra + docs: https://terra.polydev.org +license: MIT License diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java b/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java index 84f2fb382..3bcc1682e 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/config/pack/ConfigPackImpl.java @@ -32,7 +32,6 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -82,7 +81,6 @@ import com.dfsek.terra.api.world.chunk.generation.util.provider.ChunkGeneratorPr import com.dfsek.terra.config.fileloaders.FolderLoader; import com.dfsek.terra.config.fileloaders.ZIPLoader; import com.dfsek.terra.config.loaders.GenericTemplateSupplierLoader; -import com.dfsek.terra.config.loaders.config.BufferedImageLoader; import com.dfsek.terra.config.preprocessor.MetaListLikePreprocessor; import com.dfsek.terra.config.preprocessor.MetaMapPreprocessor; import com.dfsek.terra.config.preprocessor.MetaNumberPreprocessor; @@ -282,8 +280,7 @@ public class ConfigPackImpl implements ConfigPack { @Override public void register(TypeRegistry registry) { - registry.registerLoader(ConfigType.class, configTypeRegistry) - .registerLoader(BufferedImage.class, new BufferedImageLoader(loader)); + registry.registerLoader(ConfigType.class, configTypeRegistry); registryMap.forEach(registry::registerLoader); shortcuts.forEach(registry::registerLoader); // overwrite with delegated shortcuts if present }