Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package org.cyclops.iconexporter.client.gui;

import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.renderer.texture.NativeImage;
import org.cyclops.cyclopscore.datastructure.Wrapper;

import javax.annotation.Nullable;
import java.io.IOException;

/**
* @author rubensworks
*/
public interface IExportTask {

public void run(MatrixStack matrixStack) throws IOException;
public void run(MatrixStack matrixStack, Wrapper<NativeImage> bImage) throws IOException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,54 @@
* @author rubensworks
*/
public class ImageExportUtil {

public static void exportImageFromScreenshot(File dir, String key, int guiWidth, int guiHeight, int scaleImage, int backgroundColor) throws IOException {
// Take a screenshot
public static NativeImage takeScreenshot(int guiWidth, int guiHeight, int scaleImage) {
NativeImage image = ScreenShotHelper.createScreenshot(guiWidth, guiHeight, Minecraft.getInstance().getFramebuffer());
image = getSubImage(image, scaleImage, scaleImage);
return getSubImage(image, scaleImage, scaleImage);
}

// Convert our background color to a fully transparent pixel
// For opaque items/fluids, replaces background color with fully transparent pixels
public static NativeImage adjustImageAlpha(NativeImage image, int bgColor) {
byte alpha = (byte) 256;
alpha %= 0xff;
for (int cx = 0; cx < image.getWidth(); cx++) {
for (int cy = 0; cy < image.getHeight(); cy++) {
int color = image.getPixelRGBA(cx, cy);

if (color == backgroundColor) {
if (color == bgColor) {
color = 0;
int mc = (alpha << 24) | 0x00ffffff;
int newcolor = color & mc;
image.setPixelRGBA(cx, cy, newcolor);
int newColor = color & mc;
image.setPixelRGBA(cx, cy, newColor);
}
}
}
return image;
}

// For non-opaque items/fluids, calculate alpha and adjust image accordingly
public static NativeImage adjustImageAlpha(NativeImage blackImage, NativeImage whiteImage) {
for (int cx = 0; cx < blackImage.getWidth(); cx++) {
for (int cy = 0; cy < blackImage.getHeight(); cy++) {
int blackTinted = blackImage.getPixelRGBA(cx, cy);
int whiteTinted = whiteImage.getPixelRGBA(cx, cy);
short alpha = (short) (255 + (blackTinted & 0xff) - (whiteTinted & 0xff));
if (alpha == 0) {
blackImage.setPixelRGBA(cx, cy, blackTinted & 0x00ffffff);
}
else if (alpha > 0 && alpha < 255) {
int red = (255 * ((blackTinted & 0xff0000) >> 16) / alpha) & 0xff;
int blue = (255 * ((blackTinted & 0xff00) >> 8) / alpha) & 0xff;
int green = (255 * (blackTinted & 0xff) / alpha) & 0xff;
int newColor = (alpha << 24) | (red << 16) | (blue << 8) | (green);
blackImage.setPixelRGBA(cx, cy, newColor);
}
}
}
whiteImage.close();
return blackImage;
}

public static void exportImage(File dir, String key, NativeImage image) throws IOException {
// Write the file
key = key
.replaceAll(":", "__")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeLookup;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.fluid.Fluid;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack;
Expand All @@ -20,10 +24,14 @@
import org.cyclops.cyclopscore.helper.Helpers;
import org.cyclops.iconexporter.GeneralConfig;

import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Queue;
import java.util.function.Predicate;

import static org.cyclops.iconexporter.client.gui.ImageExportUtil.takeScreenshot;

/**
* A temporary gui for exporting icons.
Expand All @@ -34,17 +42,27 @@
*/
public class ScreenIconExporter extends Screen {

private static final int BACKGROUND_COLOR = Helpers.RGBAToInt(1, 0, 0, 255); // -16711680
private static final int BACKGROUND_COLOR_SHIFTED = -16777215; // For some reason, MC shifts around colors internally... (R seems to be moved from the 16th bit to the 0th bit)
private static final int BLACK = Helpers.RGBAToInt(0, 0, 0, 255); // -16777216
private static final int WHITE = Helpers.RGBAToInt(255, 255, 255, 255); // -1
private static final int NOT_BLACK = Helpers.RGBAToInt(1, 0, 0, 255); // -16711680
private static final int NOT_BLACK_SHIFTED = -16777215; // For some reason, MC shifts around colors internally... (R seems to be moved from the 16th bit to the 0th bit)

private final int scaleImage;
private final double scaleGui;
private final Predicate<ResourceLocation> filter;
private final Queue<IExportTask> exportTasks;
private Wrapper<NativeImage> blackImage = new Wrapper<>();

public ScreenIconExporter(int scaleImage, double scaleGui) {
public ScreenIconExporter(int scaleImage, double scaleGui, @Nullable String namespace) {
super(new TranslationTextComponent("gui.itemexporter.name"));
this.scaleImage = scaleImage;
this.scaleGui = scaleGui;
if (namespace == null) {
filter = (key) -> true;
}
else {
filter = (key) -> key.getNamespace().equals(namespace);
}
this.exportTasks = this.createExportTasks();
}

Expand All @@ -58,7 +76,7 @@ public void render(MatrixStack matrixStack, int mouseX, int mouseY, float partia
} else {
IExportTask task = exportTasks.poll();
try {
task.run(matrixStack);
task.run(matrixStack, blackImage);
} catch (IOException e) {
Minecraft.getInstance().player.sendMessage(new TranslationTextComponent("gui.itemexporter.error"), Util.DUMMY_UUID);
e.printStackTrace();
Expand Down Expand Up @@ -89,35 +107,79 @@ public Queue<IExportTask> createExportTasks() {

// Add fluids
for (Map.Entry<RegistryKey<Fluid>, Fluid> fluidEntry : ForgeRegistries.FLUIDS.getEntries()) {
tasks.set(tasks.get() + 1);
String subKey = "fluid:" + fluidEntry.getKey().getLocation();
exportTasks.add((matrixStack) -> {
taskProcessed.set(taskProcessed.get() + 1);
signalStatus(tasks, taskProcessed);
fill(matrixStack, 0, 0, scaleModifiedRounded, scaleModifiedRounded, BACKGROUND_COLOR);
ItemRenderUtil.renderFluid(this, matrixStack, fluidEntry.getValue(), scaleModified);
ImageExportUtil.exportImageFromScreenshot(baseDir, subKey, this.width, this.height, this.scaleImage, BACKGROUND_COLOR_SHIFTED);
});
ResourceLocation location = fluidEntry.getKey().getLocation();
if (filter.test(location)) {
tasks.set(tasks.get() + 1);
String subKey = "fluid:" + location;
//Test if fluid is opaque
if (RenderTypeLookup.canRenderInLayer(fluidEntry.getValue().getDefaultState(), RenderType.getSolid())) {
exportTasks.add((matrixStack, bImage) -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's quite a bit of code duplication in these tasks. Could this be abstracted? Otherwise this significantly complicates maintenance effort in future updates.

taskProcessed.set(taskProcessed.get() + 1);
signalStatus(tasks, taskProcessed);
fill(matrixStack, 0, 0, scaleModifiedRounded, scaleModifiedRounded, NOT_BLACK);
ItemRenderUtil.renderFluid(this, matrixStack, fluidEntry.getValue(), scaleModified);
NativeImage image = ImageExportUtil.takeScreenshot(this.width, this.height, this.scaleImage);
ImageExportUtil.exportImage(baseDir, subKey, ImageExportUtil.adjustImageAlpha(image, NOT_BLACK_SHIFTED));
});
} else {
exportTasks.add((matrixStack, bImage) -> {
fill(matrixStack, 0, 0, scaleModifiedRounded, scaleModifiedRounded, BLACK);
ItemRenderUtil.renderFluid(this, matrixStack, fluidEntry.getValue(), scaleModified);
bImage.set(takeScreenshot(this.width, this.height, this.scaleImage));
});
exportTasks.add((matrixStack, bImage) -> {
taskProcessed.set(taskProcessed.get() + 1);
signalStatus(tasks, taskProcessed);
fill(matrixStack, 0, 0, scaleModifiedRounded, scaleModifiedRounded, WHITE);
ItemRenderUtil.renderFluid(this, matrixStack, fluidEntry.getValue(), scaleModified);
NativeImage wImage = takeScreenshot(this.width, this.height, this.scaleImage);
ImageExportUtil.exportImage(baseDir, subKey, ImageExportUtil.adjustImageAlpha(bImage.get(), wImage));
});
}
}
}

// Add items
for (ResourceLocation key : ForgeRegistries.ITEMS.getKeys()) {
Item value = ForgeRegistries.ITEMS.getValue(key);
NonNullList<ItemStack> subItems = NonNullList.create();
value.fillItemGroup(ItemGroup.SEARCH, subItems);
for (ItemStack subItem : subItems) {
tasks.set(tasks.get() + 1);
String subKey = key + (subItem.hasTag() ? "__" + serializeNbtTag(subItem.getTag()) : "");
exportTasks.add((matrixStack) -> {
taskProcessed.set(taskProcessed.get() + 1);
signalStatus(tasks, taskProcessed);
fill(matrixStack, 0, 0, scaleModifiedRounded, scaleModifiedRounded, BACKGROUND_COLOR);
ItemRenderUtil.renderItem(subItem, scaleModified);
ImageExportUtil.exportImageFromScreenshot(baseDir, subKey, this.width, this.height, this.scaleImage, BACKGROUND_COLOR_SHIFTED);
if (subItem.hasTag() && GeneralConfig.fileNameHashTag) {
ImageExportUtil.exportNbtFile(baseDir, subKey, subItem.getTag());
if (filter.test(key)) {
Item value = ForgeRegistries.ITEMS.getValue(key);
NonNullList<ItemStack> subItems = NonNullList.create();
value.fillItemGroup(ItemGroup.SEARCH, subItems);
for (ItemStack subItem : subItems) {
tasks.set(tasks.get() + 1);
String subKey = key + (subItem.hasTag() ? "__" + serializeNbtTag(subItem.getTag()) : "");
//Test if item is BlockItem and if block is opaque
if ((subItem.getItem() instanceof BlockItem) && RenderTypeLookup.canRenderInLayer(((BlockItem) subItem.getItem()).getBlock().getDefaultState(), RenderType.getSolid())) {
exportTasks.add((matrixStack, bImage) -> {
taskProcessed.set(taskProcessed.get() + 1);
signalStatus(tasks, taskProcessed);
fill(matrixStack, 0, 0, scaleModifiedRounded, scaleModifiedRounded, NOT_BLACK);
ItemRenderUtil.renderItem(subItem, scaleModified);
NativeImage image = ImageExportUtil.takeScreenshot(this.width, this.height, this.scaleImage);
ImageExportUtil.exportImage(baseDir, subKey, ImageExportUtil.adjustImageAlpha(image, NOT_BLACK_SHIFTED));
if (subItem.hasTag() && GeneralConfig.fileNameHashTag) {
ImageExportUtil.exportNbtFile(baseDir, subKey, subItem.getTag());
}
});
} else {
exportTasks.add((matrixStack, bImage) -> {
fill(matrixStack, 0, 0, scaleModifiedRounded, scaleModifiedRounded, BLACK);
ItemRenderUtil.renderItem(subItem, scaleModified);
bImage.set(takeScreenshot(this.width, this.height, this.scaleImage));
});
exportTasks.add((matrixStack, bImage) -> {
taskProcessed.set(taskProcessed.get() + 1);
signalStatus(tasks, taskProcessed);
fill(matrixStack, 0, 0, scaleModifiedRounded, scaleModifiedRounded, WHITE);
ItemRenderUtil.renderItem(subItem, scaleModified);
NativeImage wImage = takeScreenshot(this.width, this.height, this.scaleImage);
ImageExportUtil.exportImage(baseDir, subKey, ImageExportUtil.adjustImageAlpha(bImage.get(), wImage));
if (subItem.hasTag() && GeneralConfig.fileNameHashTag) {
ImageExportUtil.exportNbtFile(baseDir, subKey, subItem.getTag());
}
});
}
});
}
}
}

Expand Down
23 changes: 16 additions & 7 deletions src/main/java/org/cyclops/iconexporter/command/CommandExport.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
Expand All @@ -18,32 +19,40 @@
*/
public class CommandExport implements Command<CommandSource> {

private final boolean param;
private final boolean hasScale;
private final boolean hasNamespace;

public CommandExport(boolean param) {
this.param = param;
public CommandExport(boolean hasScale, boolean hasNamespace) {
this.hasScale = hasScale;
this.hasNamespace = hasNamespace;
}

@Override
public int run(CommandContext<CommandSource> context) throws CommandSyntaxException {
// Determine the scale
int scale = GeneralConfig.defaultScale;
if (param) {
String namespace = null;
if (this.hasScale) {
scale = context.getArgument("scale", Integer.class);
}
if (this.hasNamespace) {
namespace = context.getArgument("namespace", String.class);
}

// Open the gui that will render the icons
ScreenIconExporter exporter = new ScreenIconExporter(scale, Minecraft.getInstance().getMainWindow().getGuiScaleFactor());
ScreenIconExporter exporter = new ScreenIconExporter(scale, Minecraft.getInstance().getMainWindow().getGuiScaleFactor(), namespace);
Minecraft.getInstance().deferTask(() -> Minecraft.getInstance().displayGuiScreen(exporter));

return 0;
}

public static LiteralArgumentBuilder<CommandSource> make() {
return Commands.literal("export")
.executes(new CommandExport(false))
.executes(new CommandExport(false, false))
.then(Commands.argument("scale", IntegerArgumentType.integer(1))
.executes(new CommandExport(true)));
.then(Commands.argument("namespace", StringArgumentType.word())
.executes(new CommandExport(true, true)))
.executes(new CommandExport(true, false)));
}

}