Skip to content

Commit

Permalink
Added dithering, version 1.0!
Browse files Browse the repository at this point in the history
  • Loading branch information
aws404 committed Jun 7, 2021
1 parent 8990a3a commit 880e4d5
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 7 deletions.
4 changes: 4 additions & 0 deletions src/main/java/io/github/aws404/easypainter/EasyPainter.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package io.github.aws404.easypainter;

import fr.catcore.server.translations.api.ServerTranslations;
import io.github.aws404.easypainter.command.EasyPainterCommand;
import io.github.aws404.easypainter.custom.CustomFrameEntity;
import io.github.aws404.easypainter.custom.CustomMotivesManager;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder;
Expand Down Expand Up @@ -47,6 +49,8 @@ public class EasyPainter implements ModInitializer {

@Override
public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> EasyPainterCommand.register(dispatcher));

ServerWorldEvents.LOAD.register((server, world) -> {
if (world.getRegistryKey() == World.OVERWORLD) {
EasyPainter.customMotivesManager = new CustomMotivesManager(world.getPersistentStateManager());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.github.aws404.easypainter.command;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import io.github.aws404.easypainter.custom.MotiveCacheState;
import net.minecraft.command.CommandException;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.command.SetBlockCommand;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Identifier;
import net.minecraft.world.PersistentStateManager;

public class EasyPainterCommand {

private static final SimpleCommandExceptionType NOT_PREPARED_EXCEPTION = new SimpleCommandExceptionType(new TranslatableText("command.easy_painter.clearcache.not_prepared"));

private static boolean prepared = false;

public static void register(CommandDispatcher<ServerCommandSource> source) {
source.register(CommandManager.literal("easy_painter")
.requires(serverCommandSource -> serverCommandSource.hasPermissionLevel(3))
.then(CommandManager.literal("clearcache")
.executes(context -> {
EasyPainterCommand.checkPrepared();

context.getSource().getMinecraftServer().save(false, true, true);
context.getSource().getMinecraftServer().getPlayerManager().saveAllPlayerData();
context.getSource().getMinecraftServer().stop(false);

PersistentStateManager manager = context.getSource().getMinecraftServer().getOverworld().getPersistentStateManager();
MotiveCacheState cache = MotiveCacheState.getOrCreate(manager);

for (Identifier key : cache.getKeys()) {
cache.removeEntry(manager, key);
}

manager.save();

return 1;
})
)
);
}

private static void checkPrepared() throws CommandSyntaxException {
if (!prepared) {
prepared = true;
throw NOT_PREPARED_EXCEPTION.create();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.github.aws404.easypainter.mixin.MapStateAccessor;
import net.minecraft.block.MapColor;
import net.minecraft.item.map.MapState;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.PersistentStateManager;
import net.minecraft.world.World;

Expand All @@ -20,7 +21,7 @@
public class ImageRenderer {
private static final double[] shadeCoeffs = {0.71, 0.86, 1.0, 0.53};

public static int renderImageToMap(BufferedImage image, PersistentStateManager stateManager) {
public static int renderImageToMap(BufferedImage image, DitherMode mode, PersistentStateManager stateManager) {
MapState state = MapStateAccessor.createMapState(0, 0, (byte) 3, false, false, true, World.OVERWORLD);

Image resizedImage = image.getScaledInstance(128, 128, Image.SCALE_DEFAULT);
Expand All @@ -35,6 +36,10 @@ public static int renderImageToMap(BufferedImage image, PersistentStateManager s
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
imageColor = new Color(pixels[j][i], true);
if (mode.equals(DitherMode.FLOYD))
state.colors[i + j * width] = (byte) floydDither(mapColors, pixels, i, j, imageColor);
else
state.colors[i + j * width] = (byte) nearestColor(mapColors, imageColor);
state.colors[i + j * width] = (byte) nearestColor(mapColors, imageColor);
}
}
Expand All @@ -57,6 +62,63 @@ private static double[] applyShade(double[] color, int ind) {
private static int getNextPaintingId(PersistentStateManager stateManager) {
return MotiveCacheState.getOrCreate(stateManager).getNextMapId();
}
private static Color mapColorToRGBColor(MapColor[] colors, int color) {
Color mcColor = new Color(colors[color >> 2].color);
double[] mcColorVec = {
(double) mcColor.getRed(),
(double) mcColor.getGreen(),
(double) mcColor.getBlue()
};
double coeff = shadeCoeffs[color & 3];
return new Color((int)(mcColorVec[0] * coeff), (int)(mcColorVec[1] * coeff), (int)(mcColorVec[2] * coeff));
}

private static class NegatableColor {
public final int r;
public final int g;
public final int b;
public NegatableColor(int r, int g, int b) {
this.r = r;
this.g = g;
this.b = b;
}
}
private static int floydDither(MapColor[] mapColors, int[][] pixels, int x, int y, Color imageColor) {
// double[] imageVec = { (double) imageColor.getRed() / 255.0, (double) imageColor.getGreen() / 255.0,
// (double) imageColor.getBlue() / 255.0 };
int colorIndex = nearestColor(mapColors, imageColor);
Color palletedColor = mapColorToRGBColor(mapColors, colorIndex);
NegatableColor error = new NegatableColor(
imageColor.getRed() - palletedColor.getRed(),
imageColor.getGreen() - palletedColor.getGreen(),
imageColor.getBlue() - palletedColor.getBlue());
if (pixels[0].length > x + 1) {
Color pixelColor = new Color(pixels[y][x + 1], true);
pixels[y][x + 1] = applyError(pixelColor, error, 7.0 / 16.0);
}
if (pixels.length > y + 1) {
if (x > 0) {
Color pixelColor = new Color(pixels[y + 1][x - 1], true);
pixels[y + 1][x - 1] = applyError(pixelColor, error, 3.0 / 16.0);
}
Color pixelColor = new Color(pixels[y + 1][x], true);
pixels[y + 1][x] = applyError(pixelColor, error, 5.0 / 16.0);
if (pixels[0].length > x + 1) {
pixelColor = new Color(pixels[y + 1][x + 1], true);
pixels[y + 1][x + 1] = applyError(pixelColor, error, 1.0 / 16.0);
}
}


return colorIndex;
}

private static int applyError(Color pixelColor, NegatableColor error, double quantConst) {
int pR = MathHelper.clamp(pixelColor.getRed() + (int)((double)error.r * quantConst), 0, 255);
int pG = MathHelper.clamp(pixelColor.getGreen() + (int)((double)error.g * quantConst), 0, 255);
int pB = MathHelper.clamp(pixelColor.getBlue() + (int)((double)error.b * quantConst), 0, 255);
return new Color(pR, pG, pB, pixelColor.getAlpha()).getRGB();
}

private static int nearestColor(MapColor[] colors, Color imageColor) {
double[] imageVec = { (double) imageColor.getRed() / 255.0, (double) imageColor.getGreen() / 255.0,
Expand All @@ -83,6 +145,7 @@ private static int nearestColor(MapColor[] colors, Color imageColor) {
}

private static int[][] convertPixelArray(BufferedImage image) {

final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
final int width = image.getWidth();
final int height = image.getHeight();
Expand Down Expand Up @@ -114,4 +177,17 @@ private static BufferedImage convertToBufferedImage(Image image) {
g.dispose();
return newImage;
}

public enum DitherMode {
NONE,
FLOYD;

public static DitherMode fromString(String string) {
if (string.equalsIgnoreCase("NONE"))
return DitherMode.NONE;
else if (string.equalsIgnoreCase("DITHER") || string.equalsIgnoreCase("FLOYD"))
return DitherMode.FLOYD;
throw new IllegalArgumentException("invalid dither mode");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

public class MotiveCacheState extends PersistentState {
Expand All @@ -35,7 +37,8 @@ public MotiveCacheState(HashMap<Identifier, Entry> entries, int currentMapId) {
public NbtCompound writeNbt(NbtCompound nbt) {
nbt.putInt("currentMapId", currentMapId.get());
for (Entry entry : entries.values()) {
nbt.put(entry.id.toString(), entry.writeNbt(new NbtCompound()));
if (entry != null)
nbt.put(entry.id.toString(), entry.writeNbt(new NbtCompound()));
}
return nbt;
}
Expand All @@ -44,13 +47,41 @@ public int getNextMapId() {
return currentMapId.getAndAdd(1);
}

public Set<Identifier> getKeys() {
return this.entries.keySet();
}

public Entry getEntry(Identifier id) {
return this.entries.get(id);
}

public void removeEntry(Identifier id) {
/**
* Server must be stopped for this or it will crash
*/
public void removeEntry(PersistentStateManager stateManager, Identifier id) {
for (int[] mapId : entries.get(id).mapIds) {
for (int i : mapId) {
stateManager.set("map_" + i, new PersistentState() {

{
this.markDirty();
}

@Override
public NbtCompound writeNbt(NbtCompound nbt) {
return null;
}

@Override
public void save(File file) {
file.delete();
}
});
}
}

this.entries.put(id, null);
this.markDirty();
this.entries.remove(id);
}

public Entry getOrCreateEntry(Identifier resource, Gson gson, ResourceManager manager, PersistentStateManager stateManager) {
Expand All @@ -67,7 +98,8 @@ public Entry getOrCreateEntry(Identifier resource, Gson gson, ResourceManager ma

int blockWidth = data.get("blockWidth").getAsInt();
int blockHeight = data.get("blockHeight").getAsInt();
String imageName = data.has("image") ? "paintings/" + data.get("image").getAsString() + ".png" : resource.getPath().substring(0, resource.getPath().indexOf(".json")) + "_image.png";
String imageName = data.has("image") ? "painting/" + data.get("image").getAsString() + ".png" : resource.getPath().substring(0, resource.getPath().indexOf(".json")) + "_image.png";
ImageRenderer.DitherMode ditherMode = data.has("ditherMode") ? ImageRenderer.DitherMode.fromString(data.get("ditherMode").getAsString()) : ImageRenderer.DitherMode.NONE;

BufferedImage image = ImageIO.read(manager.getResource(new Identifier(resource.getNamespace(), imageName)).getInputStream());

Expand All @@ -82,7 +114,7 @@ public Entry getOrCreateEntry(Identifier resource, Gson gson, ResourceManager ma
for (int bH = 0; bH < blockHeight; bH++) {
BufferedImage outputImage = new BufferedImage(128, 128, BufferedImage.TYPE_INT_RGB);
outputImage.getGraphics().drawImage(resultingImage, 0, 0, 128, 128, bW * 128, bH * 128, (bW + 1) * 128, (bH + 1) * 128, null);
mapIds[bW][bH] = ImageRenderer.renderImageToMap(outputImage, stateManager);
mapIds[bW][bH] = ImageRenderer.renderImageToMap(outputImage, ditherMode, stateManager);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/data/easy_painter/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"screen.easy_painter.size.data": "%sx%s",
"screen.easy_painter.currently_selected": "Currently Selected",

"commands.easy_painter.clear.confirmation_required": "Are you sure you want to clear the painting cache? This will stop the server so the painting cache can be cleared. To confirm, run the command again."
"command.easy_painter.clearcache.not_prepared": "Are you sure you want to clear the painting cache? This will stop the server so the painting cache can be cleared. To confirm, run the command again."
}

0 comments on commit 880e4d5

Please sign in to comment.