Implement PNG to/from EBP converter

This commit is contained in:
lax1dude 2024-06-16 23:51:30 -07:00
parent 62e8cd6618
commit 475df84405
4 changed files with 273 additions and 1 deletions

View File

@ -0,0 +1,116 @@
package net.lax1dude.eaglercraft.bintools;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
/**
* Copyright (c) 2023-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EBPFileDecoder {
private static int getFromBits(int idxx, int bits, byte[] bytes) {
int startByte = idxx >> 3;
int endByte = (idxx + bits - 1) >> 3;
if(startByte == endByte) {
return (((int)bytes[startByte] & 0xff) >> (8 - (idxx & 7) - bits)) & ((1 << bits) - 1);
}else {
return (((((int)bytes[startByte] & 0xff) << 8) | ((int)bytes[endByte] & 0xff)) >> (16 - (idxx & 7) - bits)) & ((1 << bits) - 1);
}
}
public static void _main(String[] args) throws IOException {
if(args.length == 1) {
File input = new File(args[0]);
if(!input.isDirectory()) {
System.err.println("Error: Not a directory: " + input.getAbsolutePath());
System.exit(-1);
return;
}
File[] f = input.listFiles();
for(int i = 0; i < f.length; ++i) {
String name = f[i].getAbsolutePath();
if(!name.toLowerCase().endsWith(".ebp")) {
continue;
}
File ff = new File(name.substring(0, name.length() - 3) + "png");
System.out.println(f[i].getName());
BufferedImage img;
try(InputStream is = new FileInputStream(f[i])) {
img = read(is);
}
ImageIO.write(img, "PNG", ff);
}
}else if(args.length == 2) {
System.out.println("Reading input file...");
BufferedImage img;
try(InputStream is = new FileInputStream(new File(args[0]))) {
img = read(is);
}
File output = new File(args[1]);
System.out.println("Writing PNG: " + output.getAbsolutePath());
ImageIO.write(img, "PNG", output);
}else {
System.out.println("Usage: ebp-decode <input file> <output file>");
System.out.println(" ebp-decode <directory>");
}
}
public static BufferedImage read(InputStream is) throws IOException {
if(is.read() != '%' || is.read() != 'E' || is.read() != 'B' || is.read() != 'P') {
throw new IOException("Not an EBP file!");
}
int v = is.read();
if(v != 1) {
throw new IOException("Unknown EBP version: " + v);
}
v = is.read();
if(v != 3) {
throw new IOException("Invalid component count: " + v);
}
int w = is.read() | (is.read() << 8);
int h = is.read() | (is.read() << 8);
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
v = is.read();
if(v == 0) {
for(int i = 0, l = w * h; i < l; ++i) {
img.setRGB(i % w, i / w, (is.read() << 16) | (is.read() << 8) | is.read() | 0xFF000000);
}
}else if(v == 1) {
int paletteSize = is.read();
int[] palette = new int[paletteSize + 1];
palette[0] = 0xFF000000;
for(int i = 0; i < paletteSize; ++i) {
palette[i + 1] = (is.read() << 16) | (is.read() << 8) | is.read() | 0xFF000000;
}
int bpp = is.read();
byte[] readSet = new byte[is.read() | (is.read() << 8) | (is.read() << 16)];
is.read(readSet);
for(int i = 0, l = w * h; i < l; ++i) {
img.setRGB(i % w, i / w, palette[getFromBits(i * bpp, bpp, readSet)]);
}
}else {
throw new IOException("Unknown EBP storage type: " + v);
}
if(is.read() != ':' || is.read() != '>') {
throw new IOException("Invalid footer! (:>)");
}
return img;
}
}

View File

@ -0,0 +1,144 @@
package net.lax1dude.eaglercraft.bintools;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.imageio.ImageIO;
/**
* Copyright (c) 2023-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EBPFileEncoder {
private static final int[] paletteHelper = new int[0xFFFFFF + 1];
private static void setBit(int idx, boolean v, byte[] bytes) {
int idx2 = idx >> 3;
if(v) {
bytes[idx2] |= (1 << (7 - (idx & 7)));
}else {
bytes[idx2] &= (-1 ^ (1 << (7 - (idx & 7))));
}
}
public static void _main(String[] args) throws IOException {
if(args.length == 1) {
File input = new File(args[0]);
if(!input.isDirectory()) {
System.err.println("Error: Not a directory: " + input.getAbsolutePath());
System.exit(-1);
return;
}
File[] f = input.listFiles();
for(int i = 0; i < f.length; ++i) {
String name = f[i].getAbsolutePath();
if(!name.toLowerCase().endsWith(".png")) {
continue;
}
File ff = new File(name.substring(0, name.length() - 3) + "ebp");
System.out.println(f[i].getName());
try(OutputStream os = new FileOutputStream(ff)) {
write(ImageIO.read(f[i]), os);
}
}
}else if(args.length == 2) {
System.out.println("Reading input file...");
BufferedImage img = ImageIO.read(new File(args[0]));
File output = new File(args[1]);
System.out.println("Encoding EBP: " + output.getAbsolutePath());
try(OutputStream os = new FileOutputStream(output)) {
write(img, os);
}
}else {
System.out.println("Usage: ebp-encode <input file> <output file>");
System.out.println(" ebp-encode <directory>");
}
}
public static void write(BufferedImage img, OutputStream fos) throws IOException {
fos.write(new byte[] { '%', 'E', 'B', 'P'});
fos.write(1); // v1
fos.write(3); // 3 component
int w = img.getWidth();
int h = img.getHeight();
fos.write(w & 0xFF);
fos.write((w >> 8) & 0xFF);
fos.write(h & 0xFF);
fos.write((h >> 8) & 0xFF);
int[] pixels = img.getRGB(0, 0, w, h, null, 0, w);
Set<Integer> colorPalette = new HashSet();
for(int i = 0; i < pixels.length; ++i) {
if((pixels[i] & 0xFF000000) == 0) {
pixels[i] = 0;
}else {
pixels[i] &= 0xFFFFFF;
}
if(pixels[i] == 0) {
continue;
}
colorPalette.add(pixels[i]);
}
if(colorPalette.size() > 255) {
fos.write(0); // type is no palette
for(int i = 0; i < pixels.length; ++i) {
fos.write((pixels[i] >> 16) & 0xFF);
fos.write((pixels[i] >> 8) & 0xFF);
fos.write(pixels[i] & 0xFF);
}
}else {
fos.write(1); // type is palette
fos.write(colorPalette.size()); // write palette size
Iterator<Integer> paletteItr = colorPalette.iterator();
int paletteIdx = 0;
paletteHelper[0] = 0;
while(paletteItr.hasNext()) {
int j = paletteItr.next().intValue();
paletteHelper[j] = ++paletteIdx;
fos.write((j >> 16) & 0xFF);
fos.write((j >> 8) & 0xFF);
fos.write(j & 0xFF);
}
int bpp = 1;
while((paletteIdx >> bpp) != 0) {
++bpp;
}
fos.write(bpp); // write bpp
int totalBits = pixels.length * bpp;
byte[] completedBitSet = new byte[(totalBits & 7) == 0 ? (totalBits >> 3) : ((totalBits >> 3) + 1)];
int bsi = 0;
for(int i = 0; i < pixels.length; ++i) {
int wr = paletteHelper[pixels[i]];
for(int j = bpp - 1; j >= 0; --j) {
setBit(bsi++, ((wr >> j) & 1) != 0, completedBitSet);
}
}
fos.write(completedBitSet.length & 0xFF); // write expect length (as 24 bits not 32)
fos.write((completedBitSet.length >> 8) & 0xFF);
fos.write((completedBitSet.length >> 16) & 0xFF);
fos.write(completedBitSet); // write the bits
}
fos.write(new byte[] { ':', '>' });
}
}

View File

@ -26,10 +26,13 @@ public class EaglerBinaryTools {
System.arraycopy(args, 1, argz, 0, argz.length);
switch(args[0].toLowerCase()) {
case "epkcompiler":
case "epkcompile":
EPKCompiler._main(argz);
return;
case "legacy-epkcompiler":
case "legacyepkcompiler":
case "legacy-epkcompile":
case "legacyepkcompile":
EPKCompilerLegacy._main(argz);
return;
case "epkdecompiler":
@ -46,13 +49,22 @@ public class EaglerBinaryTools {
return;
case "ebp-encode":
case "ebpencode":
case "ebp-encoder":
case "ebpencoder":
EBPFileEncoder._main(argz);
return;
case "ebp-decode":
case "ebpdecode":
case "ebp-decoder":
case "ebpdecoder":
EBPFileDecoder._main(argz);
return;
case "skybox-gen":
case "skyboxgen":
return;
case "light-mesh-gen":
case "lightmeshgen":
return;
case "eagler-moon-gen":
case "eaglermoongen":
return;

View File

@ -46,7 +46,7 @@ public class OBJConverter {
}
}
File output = new File(args[1]);
System.out.println("Exporting MDL: " + output.getAbsolutePath());
System.out.println("Exporting " + (v1_8 ? "1.8" : "1.5") + " MDL: " + output.getAbsolutePath());
boolean tex = args[2].equalsIgnoreCase("true") || args[2].equals("1");
try(FileOutputStream fs = new FileOutputStream(output)) {
convertModel(lns, fs, tex, v1_8);