Ports DMItool from bay (#12784)

* Ports DMItool from bay
Coded by GigaNinja32
He's a pretty cool dude and allowed me to blame the bugs in it on him
As far as I can tell there are none
How does it work? I don't know
I do know that it resolves DMI conflicts
if you ever have coded anything with a DMI you'll know that that is literally revolutionary
Why hasn't anyone ported this before it's not even dm code I literally just ctrl+v'd  the folder and slapped it in

* does thing

* okay done
This commit is contained in:
DrCelt
2016-12-22 14:00:31 -08:00
committed by Pieter-Jan Briers
parent 8064f5ce54
commit d0d5d4e94f
21 changed files with 1887 additions and 0 deletions

4
.gitattributes vendored
View File

@@ -3,5 +3,9 @@
*.dmm merge=merge-dmm
* text=auto
# dmi icon merger hook
# needs additional setup, see tools/dmitool/merging.txt
*.dmi merge=merge-dmi
# force changelog merging to use union
html/changelog.html merge=union

View File

@@ -2,3 +2,7 @@
name = mapmerge driver
driver = ./maptools/mapmerge.sh %O %A %B
recursive = text
[merge "merge-dmi"]
name = iconfile merge driver
driver = ./tools/dmitool/dmimerge.sh %O %A %B

5
tools/dmitool/README.txt Normal file
View File

@@ -0,0 +1,5 @@
Uses PNGJ: https://code.google.com/p/pngj/.
For help, use "java -jar dmitool.jar help".
Requires Java 7.

View File

@@ -0,0 +1,16 @@
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile group: 'ar.com.hjg', name: 'pngj', version: '2.1.0'
}
jar {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
manifest {
attributes 'Main-Class': 'dmitool.Main'
}
}

View File

@@ -0,0 +1,8 @@
java -jar tools/dmitool/dmitool.jar merge $1 $2 $3 $2
if [ "$?" -gt 0 ]
then
echo "Unable to automatically resolve all icon_state conflicts, please merge manually."
exit 1
fi
exit 0

BIN
tools/dmitool/dmitool.jar Normal file

Binary file not shown.

110
tools/dmitool/dmitool.py Normal file
View File

@@ -0,0 +1,110 @@
""" Python 2.7 wrapper for dmitool.
"""
import os
from subprocess import Popen, PIPE
_JAVA_PATH = ["java"]
_DMITOOL_CMD = ["-jar", "dmitool.jar"]
def _dmitool_call(*dmitool_args, **popen_args):
return Popen(_JAVA_PATH + _DMITOOL_CMD + [str(arg) for arg in dmitool_args], **popen_args)
def _safe_parse(dict, key, deferred_value):
try:
dict[key] = deferred_value()
except Exception as e:
print "Could not parse property '%s': %s" % (key, e)
return e
return False
def version():
""" Returns the version as a string. """
stdout, stderr = _dmitool_call("version", stdout=PIPE).communicate()
return str(stdout).strip()
def help():
""" Returns the help text as a string. """
stdout, stderr = _dmitool_call("help", stdout=PIPE).communicate()
return str(stdout).strip()
def info(filepath):
""" Totally not a hack that parses the output from dmitool into a dictionary.
May break at any moment.
"""
subproc = _dmitool_call("info", filepath, stdout=PIPE)
stdout, stderr = subproc.communicate()
result = {}
data = stdout.split(os.linesep)[1:]
# for s in data: print s
# parse header line
if len(data) > 0:
header = data.pop(0).split(",")
# don't need to parse states, it's redundant
_safe_parse(result, "images", lambda: int(header[0].split()[0].strip()))
_safe_parse(result, "size", lambda: header[2].split()[1].strip())
# parse state information
states = []
for item in data:
if not len(item):
continue
stateinfo = {}
item = item.split(",", 3)
_safe_parse(stateinfo, "name", lambda: item[0].split()[1].strip(" \""))
_safe_parse(stateinfo, "dirs", lambda: int(item[1].split()[0].strip()))
_safe_parse(stateinfo, "frames", lambda: int(item[2].split()[0].strip()))
if len(item) > 3:
stateinfo["misc"] = item[3]
states.append(stateinfo)
result["states"] = states
return result
def extract_state(input_path, output_path, icon_state, direction=None, frame=None):
""" Extracts an icon state as a png to a given path.
If provided direction should be a string, one of S, N, E, W, SE, SW, NE, NW.
If provided frame should be a frame number or a string of two frame number separated by a dash.
"""
args = ["extract", input_path, icon_state, output_path]
if direction is not None:
args.extend(("direction", str(direction)))
if frame is not None:
args.extend(("frame", str(frame)))
return _dmitool_call(*args)
def import_state(target_path, input_path, icon_state, replace=False, delays=None, rewind=False, loop=None, ismovement=False, direction=None, frame=None):
""" Inserts an input png given by the input_path into the target_path.
"""
args = ["import", target_path, icon_state, input_path]
if replace:
args.append("nodup")
if rewind:
args.append("rewind")
if ismovement:
args.append("movement")
if delays:
args.extend(("delays", ",".join(delays)))
if direction is not None:
args.extend(("direction", direction))
if frame is not None:
args.extend(("frame", frame))
if loop in ("inf", "infinity"):
args.append("loop")
elif loop:
args.extend(("loopn", loop))
return _dmitool_call(*args)

View File

@@ -0,0 +1,6 @@
@echo off
set tab=
echo. >> ../../.git/config
echo [merge "merge-dmi"] >> ../../.git/config
echo %tab%name = iconfile merge driver >> ../../.git/config
echo %tab%driver = ./tools/dmitool/dmimerge.sh %%O %%A %%B >> ../../.git/config

View File

@@ -0,0 +1,6 @@
F="../../.git/config"
echo '' >> $F
echo '[merge "merge-dmi"]' >> $F
echo ' name = iconfile merge driver' >> $F
echo ' driver = ./tools/dmitool/dmimerge.sh %O %A %B' >> $F

14
tools/dmitool/merging.txt Normal file
View File

@@ -0,0 +1,14 @@
1. Install java(http://www.java.com/en/download/index.jsp)
2. Make sure java is in your PATH. To test this, open git bash, and type "java". If it says unknown command, you need to add JAVA/bin to your PATH variable (A guide for this can be found at https://www.java.com/en/download/help/path.xml ).
Merging
The easiest way to do merging is to install the merge driver. For this, open `vgstation/.git/config` in a text editor, and paste the following lines to the end of it:
[merge "merge-dmi"]
name = iconfile merge driver
driver = ./tools/dmitool/dmimerge.sh %O %A %B
You may optionally instead run git_merge_installer.bat or git_merge_installer.sh which should automatically insert these lines for you at the appropriate location.
After this, merging DMI files should happen automagically unless there are conflicts (an icon_state that both you and someone else changed).
If there are conflicts, you will unfortunately still be stuck with opening both versions in the editor, and manually resolving the issues with those states.

View File

@@ -0,0 +1,455 @@
package dmitool;
import ar.com.hjg.pngj.ImageInfo;
import ar.com.hjg.pngj.ImageLineInt;
import ar.com.hjg.pngj.PngReader;
import ar.com.hjg.pngj.PngWriter;
import ar.com.hjg.pngj.PngjInputException;
import ar.com.hjg.pngj.chunks.PngChunkPLTE;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
public class DMI implements Comparator<IconState> {
int w, h;
List<IconState> images;
int totalImages = 0;
RGBA[] palette;
boolean isPaletted;
public DMI(int w, int h) {
this.w = w;
this.h = h;
images = new ArrayList<>();
isPaletted = false;
palette = null;
}
public DMI(String f) throws DMIException, FileNotFoundException {
this(new File(f));
}
public DMI(File f) throws DMIException, FileNotFoundException {
if(f.length() == 0) { // Empty .dmi is empty file
w = 32;
h = 32;
images = new ArrayList<>();
isPaletted = false;
palette = null;
return;
}
InputStream in = new FileInputStream(f);
PngReader pngr;
try {
pngr = new PngReader(in);
} catch(PngjInputException pie) {
throw new DMIException("Bad file format!", pie);
}
String descriptor = pngr.getMetadata().getTxtForKey("Description");
String[] lines = descriptor.split("\n");
if(Main.VERBOSITY > 0) System.out.println("Descriptor has " + lines.length + " lines.");
if(Main.VERBOSITY > 3) {
System.out.println("Descriptor:");
System.out.println(descriptor);
}
/* length 6 is:
# BEGIN DMI
version = 4.0
state = "state"
dirs = 1
frames = 1
# END DMI
*/
if(lines.length < 6) throw new DMIException(null, 0, "Descriptor too short!");
if(!"# BEGIN DMI".equals(lines[0])) throw new DMIException(lines, 0, "Expected '# BEGIN DMI'");
if(!"# END DMI".equals(lines[lines.length-1])) throw new DMIException(lines, lines.length-1, "Expected '# END DMI'");
if(!"version = 4.0".equals(lines[1])) throw new DMIException(lines, 1, "Unknown version, expected 'version = 4.0'");
this.w = 32;
this.h = 32;
int i = 2;
if(lines[i].startsWith("\twidth = ")) {
this.w = Integer.parseInt(lines[2].substring("\twidth = ".length()));
i++;
}
if(lines[i].startsWith("\theight = ")) {
this.h = Integer.parseInt(lines[3].substring("\theight = ".length()));
i++;
}
List<IconState> states = new ArrayList<>();
while(i < lines.length - 1) {
long imagesInState = 1;
if(!lines[i].startsWith("state = \"") || !lines[i].endsWith("\"")) throw new DMIException(lines, i, "Error reading state string");
String stateName = lines[i].substring("state = \"".length(), lines[i].length()-1);
i++;
int dirs = 1;
int frames = 1;
float[] delays = null;
boolean rewind = false;
int loop = -1;
String hotspot = null;
boolean movement = false;
while(lines[i].startsWith("\t")) {
if(lines[i].startsWith("\tdirs = ")) {
dirs = Integer.parseInt(lines[i].substring("\tdirs = ".length()));
imagesInState *= dirs;
i++;
} else if(lines[i].startsWith("\tframes = ")) {
frames = Integer.parseInt(lines[i].substring("\tframes = ".length()));
imagesInState *= frames;
i++;
} else if(lines[i].startsWith("\tdelay = ")) {
String delayString = lines[i].substring("\tdelay = ".length());
String[] delayVals = delayString.split(",");
delays = new float[delayVals.length];
for(int d=0; d<delays.length; d++) {
delays[d] = Float.parseFloat(delayVals[d]);
}
i++;
} else if(lines[i].equals("\trewind = 1")) {
rewind = true;
i++;
} else if(lines[i].startsWith("\tloop = ")) {
loop = Integer.parseInt(lines[i].substring("\tloop = ".length()));
i++;
} else if(lines[i].startsWith("\thotspot = ")) {
hotspot = lines[i].substring("\thotspot = ".length());
i++;
} else if(lines[i].equals("\tmovement = 1")) {
movement = true;
i++;
} else {
System.out.println("Unknown line '" + lines[i] + "' in state '" + stateName + "'!");
i++;
}
}
if(delays != null) {
if((Main.STRICT && delays.length != frames) || delays.length < frames) {
throw new DMIException(null, 0, "Frames must be equal to delays (" + stateName + "; " + frames + " frames, " + delays.length + " delays)!");
}
}
IconState is = new IconState(stateName, dirs, frames, null, delays, rewind, loop, hotspot, movement);
totalImages += imagesInState;
states.add(is);
}
images = states;
PngChunkPLTE pal = (PngChunkPLTE)pngr.getChunksList().getById1("PLTE");
isPaletted = pal != null;
if(isPaletted) {
if(Main.VERBOSITY > 0) System.out.println(pal.getNentries() + " palette entries");
palette = new RGBA[pal.getNentries()];
int[] rgb = new int[3];
for(int q=0; q<pal.getNentries(); q++) {
pal.getEntryRgb(q, rgb);
palette[q] = new RGBA(rgb[0], rgb[1], rgb[2], q==0 ? 0 : 255);
}
} else {
if(Main.VERBOSITY > 0) System.out.println("Non-paletted image");
}
int iw = pngr.imgInfo.cols;
int ih = pngr.imgInfo.rows;
if(totalImages > iw * ih)
throw new DMIException(null, 0, "Impossible number of images!");
if(Main.VERBOSITY > 0) System.out.println("Image size " + iw+"x"+ih);
int[][] px = new int[ih][];
for(int y=0; y<ih; y++) {
ImageLineInt ili = (ImageLineInt)pngr.readRow();
int[] sl = ili.getScanline();
if(sl.length != (isPaletted ? iw : iw*4))
throw new DMIException(null, 0, "Error processing image!");
px[y] = sl.clone();
}
int statesX = iw / w;
int statesY = ih / h;
int x=0, y=0;
for(IconState is: states) {
int numImages = is.dirs * is.frames;
Image[] img = new Image[numImages];
for(int q=0; q<numImages; q++) {
if(isPaletted) {
int[][] idat = new int[h][w];
for(int sy = 0; sy < h; sy++) {
for(int sx = 0; sx < w; sx++) {
idat[sy][sx] = px[y*h + sy][x*w + sx];
}
}
img[q] = new PalettedImage(w, h, idat, palette);
} else {
RGBA[][] idat = new RGBA[h][w];
for(int sy = 0; sy < h; sy++) {
for(int sx = 0; sx < w; sx++) {
idat[sy][sx] = new RGBA(px[y*h + sy][x*4*w + 4*sx], px[y*h + sy][x*4*w + 4*sx + 1], px[y*h + sy][x*4*w + 4*sx + 2], px[y*h + sy][x*4*w + 4*sx + 3]);
}
}
img[q] = new NonPalettedImage(w, h, idat);
}
x++;
if(x == statesX) {
x = 0;
y++;
if(y > statesY)
// this should NEVER happen, we pre-check it
throw new DMIException(null, 0, "CRITICAL: End of image reached with states to go!");
}
}
if(is.delays != null) {
if((Main.STRICT && is.delays.length*is.dirs != img.length) || is.delays.length*is.dirs < img.length)
throw new DMIException(null, 0, "Delay array size mismatch: " + is.delays.length*is.dirs + " vs " + img.length + "!");
}
is.images = img;
}
}
public IconState getIconState(String name) {
for(IconState is: images) {
if(is.name.equals(name)) {
return is;
}
}
return null;
}
/**
* Makes a copy, unless name is null.
*/
public void addIconState(String name, IconState is) {
if(name == null) {
images.add(is);
totalImages += is.dirs * is.frames;
} else {
IconState newState = (IconState)is.clone();
newState.name = name;
images.add(newState);
totalImages += is.dirs * is.frames;
}
}
public boolean removeIconState(String name) {
for(IconState is: images) {
if(is.name.equals(name)) {
images.remove(is);
totalImages -= is.dirs * is.frames;
return true;
}
}
return false;
}
public boolean setIconState(IconState is) {
for(int i=0; i<images.size(); i++) {
IconState ic = images.get(i);
if(ic.name.equals(is.name)) {
totalImages -= ic.dirs * ic.frames;
totalImages += is.dirs * is.frames;
images.set(i, is);
return true;
}
}
return false;
}
private static final int IEND = 0x49454e44;
private static final int zTXt = 0x7a545874;
private static final int IHDR = 0x49484452;
private static void fixChunks(DataInputStream in, DataOutputStream out) throws IOException {
if(Main.VERBOSITY > 0) System.out.println("Fixing PNG chunks...");
out.writeInt(in.readInt());
out.writeInt(in.readInt());
Deque<PNGChunk> notZTXT = new ArrayDeque<>();
PNGChunk c = null;
while(c == null || c.type != IEND) {
c = new PNGChunk(in);
if(c.type == zTXt && notZTXT != null) {
PNGChunk cc = null;
while(cc == null || cc.type != IHDR) {
cc = notZTXT.pop();
cc.write(out);
}
c.write(out);
while(notZTXT.size() != 0) {
PNGChunk pc = notZTXT.pop();
pc.write(out);
}
notZTXT = null;
} else if(notZTXT != null) {
notZTXT.add(c);
} else {
c.write(out);
}
}
if(Main.VERBOSITY > 0) System.out.println("Chunks fixed.");
}
@Override public int compare(IconState arg0, IconState arg1) {
return arg0.name.compareTo(arg1.name);
}
public void writeDMI(OutputStream os) throws IOException {
writeDMI(os, false);
}
public void writeDMI(OutputStream os, boolean sortStates) throws IOException {
if(totalImages == 0) { // Empty .dmis are empty files
os.close();
return;
}
// Setup chunk-fix buffer
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if(sortStates) {
Collections.sort(images, this);
}
// Write the dmi into the buffer
int sx = (int)Math.ceil(Math.sqrt(totalImages));
int sy = totalImages / sx;
if(sx*sy < totalImages) {
sy++;
}
if(Main.VERBOSITY > 0) System.out.println("Image size: " + w + "x" + h + "; number of images " + sx + "x" + sy + " (" + totalImages + ")");
int ix = sx * w;
int iy = sy * h;
ImageInfo ii = new ImageInfo(ix, iy, 8, true);
PngWriter out = new PngWriter(baos, ii);
out.setCompLevel(9); // Maximum compression
String description = getDescriptor();
if(Main.VERBOSITY > 0) System.out.println("Descriptor has " + (description.split("\n").length) + " lines.");
out.getMetadata().setText("Description", description, true, true);
Image[][] img = new Image[sx][sy];
{
int k = 0;
int r = 0;
for(IconState is: images) {
for(Image i: is.images) {
img[k++][r] = i;
if(k == sx) {
k = 0;
r++;
}
}
}
}
for(int irow=0; irow<iy; irow++) {
ImageLineInt ili = new ImageLineInt(ii);
int[] buf = ili.getScanline();
for(int icol=0; icol<ix; icol++) {
int imageX = icol / w;
int pixelX = icol % w;
int imageY = irow / h;
int pixelY = irow % h;
Image i = img[imageX][imageY];
if(i != null) {
RGBA c = i.getPixel(pixelX, pixelY);
buf[icol*4 ] = c.r;
buf[icol*4 + 1] = c.g;
buf[icol*4 + 2] = c.b;
buf[icol*4 + 3] = c.a;
} else {
buf[icol*4 ] = 0;
buf[icol*4 + 1] = 0;
buf[icol*4 + 2] = 0;
buf[icol*4 + 3] = 0;
}
}
out.writeRow(ili);
}
out.end();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
fixChunks(new DataInputStream(bais), new DataOutputStream(os));
}
private String getDescriptor() {
String s = "";
String n = "\n";
String q = "\"";
s += "# BEGIN DMI\n";
s += "version = 4.0\n";
s += " width = " + w + n;
s += " height = " + h + n;
for(IconState is: images) {
s += is.getDescriptorFragment();
}
s += "# END DMI\n";
return s;
}
public void printInfo() {
System.out.println(totalImages + " images, " + images.size() + " states, size "+w+"x"+h);
}
public void printStateList() {
for(IconState s: images) {
System.out.println(s.getInfoLine());
}
}
@Override public boolean equals(Object obj) {
if(obj == this) return true;
if(!(obj instanceof DMI)) return false;
DMI dmi = (DMI)obj;
// try to find a simple difference before we dive into icon_state comparisons
if(dmi.w != w || dmi.h != h) return false;
if(dmi.isPaletted != isPaletted) return false;
if(dmi.totalImages != totalImages) return false;
if(dmi.images.size() != images.size()) return false;
HashMap<String, IconState> myIS = new HashMap<>();
HashMap<String, IconState> dmiIS = new HashMap<>();
for(IconState is: images) {
myIS.put(is.name, is);
}
for(IconState is: dmi.images) {
dmiIS.put(is.name, is);
}
if(!myIS.keySet().equals(dmiIS.keySet())) return false;
for(String s: myIS.keySet()) {
if(!myIS.get(s).equals(dmiIS.get(s))) return false;
}
return true;
}
}

View File

@@ -0,0 +1,194 @@
package dmitool;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class DMIDiff {
Map<String, IconState> newIconStates;
Map<String, IconStateDiff> modifiedIconStates = new HashMap<>();
Set<String> removedIconStates;
DMIDiff() {
newIconStates = new HashMap<>();
removedIconStates = new HashSet<>();
}
public DMIDiff(DMI base, DMI mod) {
if(base.h != mod.h || base.w != mod.w) throw new IllegalArgumentException("Cannot compare non-identically-sized DMIs!");
HashMap<String, IconState> baseIS = new HashMap<>();
for(IconState is: base.images) {
baseIS.put(is.name, is);
}
HashMap<String, IconState> modIS = new HashMap<>();
for(IconState is: mod.images) {
modIS.put(is.name, is);
}
newIconStates = ((HashMap<String, IconState>)modIS.clone());
for(String s: baseIS.keySet()) {
newIconStates.remove(s);
}
removedIconStates = new HashSet<>();
removedIconStates.addAll(baseIS.keySet());
removedIconStates.removeAll(modIS.keySet());
Set<String> retainedStates = new HashSet<>();
retainedStates.addAll(baseIS.keySet());
retainedStates.retainAll(modIS.keySet());
for(String s: retainedStates) {
if(!baseIS.get(s).equals(modIS.get(s))) {
modifiedIconStates.put(s, new IconStateDiff(baseIS.get(s), modIS.get(s)));
}
}
}
/**
* ASSUMES NO MERGE CONFLICTS - MERGE DIFFS FIRST.
*/
public void applyToDMI(DMI dmi) {
for(String s: removedIconStates) {
dmi.removeIconState(s);
}
for(String s: modifiedIconStates.keySet()) {
dmi.setIconState(modifiedIconStates.get(s).newState);
}
for(String s: newIconStates.keySet()) {
dmi.addIconState(null, newIconStates.get(s));
}
}
/**
* @param other The diff to merge with
* @param conflictDMI A DMI to add conflicted icon_states to
* @param merged An empty DMIDiff to merge into
* @param aName The log name for this diff
* @param bName The log name for {@code other}
* @return A Set<String> containing all icon_states which conflicted, along with what was done in each diff, in the format "icon_state: here|there"; here and there are one of "added", "modified", and "removed"
*/
public Set<String> mergeDiff(DMIDiff other, DMI conflictDMI, DMIDiff merged, String aName, String bName) {
HashSet<String> myTouched = new HashSet<>();
myTouched.addAll(removedIconStates);
myTouched.addAll(newIconStates.keySet());
myTouched.addAll(modifiedIconStates.keySet());
HashSet<String> otherTouched = new HashSet<>();
otherTouched.addAll(other.removedIconStates);
otherTouched.addAll(other.newIconStates.keySet());
otherTouched.addAll(other.modifiedIconStates.keySet());
HashSet<String> bothTouched = (HashSet<String>)myTouched.clone();
bothTouched.retainAll(otherTouched); // this set now contains the list of icon_states that *both* diffs modified, which we'll put in conflictDMI for manual merge (unless they were deletions
if(Main.VERBOSITY > 0) {
System.out.println("a: " + Arrays.toString(myTouched.toArray()));
System.out.println("b: " + Arrays.toString(otherTouched.toArray()));
System.out.println("both: " + Arrays.toString(bothTouched.toArray()));
}
HashSet<String> whatHappened = new HashSet<>();
for(String s: bothTouched) {
String here, there;
if(removedIconStates.contains(s)) {
here = "removed";
} else if(newIconStates.containsKey(s)) {
here = "added";
} else if(modifiedIconStates.containsKey(s)) {
here = "modified";
} else {
System.out.println("Unknown error; state="+s);
here = "???";
}
if(other.removedIconStates.contains(s)) {
there = "removed";
} else if(other.newIconStates.containsKey(s)) {
there = "added";
} else if(other.modifiedIconStates.containsKey(s)) {
there = "modified";
} else {
System.out.println("Unknown error; state="+s);
there = "???";
}
whatHappened.add(s + ": " + here + "|" + there);
}
// Removals
for(String s: removedIconStates) {
if(!bothTouched.contains(s)) {
merged.removedIconStates.add(s);
}
}
for(String s: other.removedIconStates) {
if(!bothTouched.contains(s)) {
merged.removedIconStates.add(s);
}
}
// Modifications
for(String s: modifiedIconStates.keySet()) {
if(!bothTouched.contains(s)) {
merged.modifiedIconStates.put(s, modifiedIconStates.get(s));
} else {
conflictDMI.addIconState(aName + "|" + s, modifiedIconStates.get(s).newState);
}
}
for(String s: other.modifiedIconStates.keySet()) {
if(!bothTouched.contains(s)) {
merged.modifiedIconStates.put(s, other.modifiedIconStates.get(s));
} else {
conflictDMI.addIconState(bName + "|" + s, other.modifiedIconStates.get(s).newState);
}
}
// Additions
for(String s: newIconStates.keySet()) {
if(!bothTouched.contains(s)) {
merged.newIconStates.put(s, newIconStates.get(s));
} else {
conflictDMI.addIconState(aName + s, newIconStates.get(s));
}
}
for(String s: other.newIconStates.keySet()) {
if(!bothTouched.contains(s)) {
merged.newIconStates.put(s, other.newIconStates.get(s));
} else {
conflictDMI.addIconState(bName + s, other.newIconStates.get(s));
}
}
return whatHappened;
}
@Override public String toString() {
String s = "";
String t = "\t";
String q = "\"";
String n = "\n";
if(!removedIconStates.isEmpty()) {
s += "Removed:\n";
for(String state: removedIconStates)
s += t + q + state + q + n;
}
if(!modifiedIconStates.isEmpty()) {
s += "Modified:\n";
for(String state: modifiedIconStates.keySet())
s += t + q + state + q + " [" + modifiedIconStates.get(state).toString() + "]\n";
}
if(!newIconStates.isEmpty()) {
s += "Added:\n";
for(String state: newIconStates.keySet())
s += t + q + state + q + " " + newIconStates.get(state).infoStr() + n;
}
if("".equals(s))
return "No changes";
return s;
}
}

View File

@@ -0,0 +1,24 @@
package dmitool;
public class DMIException extends Exception {
String[] desc = null;
int line = 0;
public DMIException(String[] descriptor, int line, String what) {
super(what);
desc = descriptor;
this.line = line;
}
public DMIException(String what) {
super(what);
}
public DMIException(String what, Exception cause) {
super(what, cause);
}
@Override public String getMessage() {
if(desc != null)
return "\"" + desc[line] + "\" - " + super.getMessage();
return super.getMessage();
}
}

View File

@@ -0,0 +1,280 @@
package dmitool;
import java.util.Arrays;
import ar.com.hjg.pngj.ImageInfo;
import ar.com.hjg.pngj.ImageLineInt;
import ar.com.hjg.pngj.PngWriter;
import ar.com.hjg.pngj.PngReader;
import ar.com.hjg.pngj.PngjInputException;
import java.io.InputStream;
import java.io.OutputStream;
public class IconState {
String name;
int dirs;
int frames;
float[] delays;
Image[] images; // dirs come first
boolean rewind;
int loop;
String hotspot;
boolean movement;
public String getInfoLine() {
String extraInfo = "";
if(rewind) extraInfo += " rewind";
if(frames != 1) {
extraInfo += " loop(" + (loop==-1 ? "infinite" : loop) + ")";
}
if(hotspot != null) extraInfo += " hotspot('" + hotspot + "')";
if(movement) extraInfo += " movement";
if(extraInfo.equals("")) {
return String.format("state \"%s\", %d dir(s), %d frame(s)", name, dirs, frames);
} else {
return String.format("state \"%s\", %d dir(s), %d frame(s),%s", name, dirs, frames, extraInfo);
}
}
@Override public IconState clone() {
IconState is = new IconState(name, dirs, frames, images.clone(), delays==null ? null : delays.clone(), rewind, loop, hotspot, movement);
is.delays = delays != null ? delays.clone() : null;
is.rewind = rewind;
return is;
}
public IconState(String name, int dirs, int frames, Image[] images, float[] delays, boolean rewind, int loop, String hotspot, boolean movement) {
if(delays != null) {
if(Main.STRICT && delays.length != frames) {
throw new IllegalArgumentException("Delays and frames must be the same length!");
}
}
this.name = name;
this.dirs = dirs;
this.frames = frames;
this.images = images;
this.rewind = rewind;
this.loop = loop;
this.hotspot = hotspot;
this.delays = delays;
this.movement = movement;
}
void setDelays(float[] delays) {
this.delays = delays;
}
void setRewind(boolean b) {
rewind = b;
}
@Override public boolean equals(Object obj) {
if(obj == this) return true;
if(!(obj instanceof IconState)) return false;
IconState is = (IconState)obj;
if(!is.name.equals(name)) return false;
if(is.dirs != dirs) return false;
if(is.frames != frames) return false;
if(!Arrays.equals(images, is.images)) return false;
if(is.rewind != rewind) return false;
if(is.loop != loop) return false;
if(!Arrays.equals(delays, is.delays)) return false;
if(!(is.hotspot == null ? hotspot == null : is.hotspot.equals(hotspot))) return false;
if(is.movement != movement) return false;
return true;
}
public String infoStr() {
return "[" + frames + " frame(s), " + dirs + " dir(s)]";
}
public String getDescriptorFragment() {
String s = "";
String q = "\"";
String n = "\n";
s += "state = " + q + name + q + n;
s += "\tdirs = " + dirs + n;
s += "\tframes = " + frames + n;
if(delays != null) {
s += "\tdelay = " + delayArrayToString(delays) + n;
}
if(rewind) {
s += "\trewind = 1\n";
}
if(loop != -1) {
s += "\tloop = " + loop + n;
}
if(hotspot != null) {
s += "\thotspot = " + hotspot + n;
}
if(movement) {
s += "\tmovement = 1\n";
}
return s;
}
private static String delayArrayToString(float[] d) {
String s = "";
for(float f: d) {
s += ","+f;
}
return s.substring(1);
}
/**
* Dump the state to the given OutputStream in PNG format. Frames will be dumped along the X axis of the image, and directions will be dumped along the Y.
*/
public void dumpToPNG(OutputStream outS, int minDir, int maxDir, int minFrame, int maxFrame) {
int totalDirs = maxDir - minDir + 1;
int totalFrames = maxFrame - minFrame + 1;
int w = images[minDir + minFrame * this.dirs].w;
int h = images[minDir + minFrame * this.dirs].h;
if(Main.VERBOSITY > 0) System.out.println("Writing " + totalDirs + " dir(s), " + totalFrames + " frame(s), " + totalDirs*totalFrames + " image(s) total.");
ImageInfo ii = new ImageInfo(totalFrames * w, totalDirs * h, 8, true);
PngWriter out = new PngWriter(outS, ii);
out.setCompLevel(9);
Image[][] img = new Image[totalFrames][totalDirs];
{
for(int i=0; i<totalFrames; i++) {
for(int j=0; j<totalDirs; j++) {
img[i][j] = images[(minDir+j) + (minFrame+i) * this.dirs];
}
}
}
for(int imY=0; imY<totalDirs; imY++) {
for(int pxY=0; pxY<h; pxY++) {
ImageLineInt ili = new ImageLineInt(ii);
int[] buf = ili.getScanline();
for(int imX=0; imX<totalFrames; imX++) {
Image i = img[imX][imY];
for(int pxX=0; pxX<w; pxX++) {
RGBA c = i.getPixel(pxX, pxY);
buf[(imX*w + pxX)*4 ] = c.r;
buf[(imX*w + pxX)*4 + 1] = c.g;
buf[(imX*w + pxX)*4 + 2] = c.b;
buf[(imX*w + pxX)*4 + 3] = c.a;
}
}
out.writeRow(ili);
}
}
out.end();
}
public static IconState importFromPNG(DMI dmi, InputStream inS, String name, float[] delays, boolean rewind, int loop, String hotspot, boolean movement) throws DMIException {
int w = dmi.w;
int h = dmi.h;
PngReader in;
try {
in = new PngReader(inS);
} catch(PngjInputException pie) {
throw new DMIException("Bad file format!", pie);
}
int pxW = in.imgInfo.cols;
int pxH = in.imgInfo.rows;
int frames = pxW / w; //frames are read along the X axis, dirs along the Y, much like export.
int dirs = pxH / h;
// make sure the size is an integer multiple
if(frames * w != pxW || frames==0) throw new DMIException("Illegal image size!");
if(dirs * h != pxH || dirs==0) throw new DMIException("Illegal image size!");
int[][] px = new int[pxH][];
for(int i=0; i<pxH; i++) {
ImageLineInt ili = (ImageLineInt)in.readRow();
int[] sl = ili.getScanline();
px[i] = sl.clone();
}
Image[] images = new Image[frames*dirs];
for(int imageY=0; imageY<dirs; imageY++) {
for(int imageX=0; imageX<frames; imageX++) {
RGBA[][] pixels = new RGBA[h][w];
for(int pixelY=0; pixelY<h; pixelY++) {
for(int pixelX=0; pixelX<w; pixelX++) {
int bY = imageY*h + pixelY;
int bX = imageX*4*w + 4*pixelX;
pixels[pixelY][pixelX] = new RGBA(px[bY][bX ],
px[bY][bX + 1],
px[bY][bX + 2],
px[bY][bX + 3]);
}
}
images[_getIndex(imageY, imageX, dirs)] = new NonPalettedImage(w, h, pixels);
}
}
//public IconState(String name, int dirs, int frames, Image[] images, float[] delays, boolean rewind, int loop, String hotspot, boolean movement) {
return new IconState(name, dirs, frames, images, delays, rewind, loop, hotspot, movement);
}
//Converts a desired dir and frame to an index into the images array.
public int getIndex(int dir, int frame) {
return _getIndex(dir, frame, dirs);
}
private static int _getIndex(int dir, int frame, int totalDirs) {
return dir + frame*totalDirs;
}
public void insertDir(int dir, Image[] splice) {
int maxFrame = frames < splice.length? frames: splice.length;
for(int frameIdx = 0; frameIdx < maxFrame; frameIdx++) {
insertImage(dir, frameIdx, splice[frameIdx]);
}
}
public void insertFrame(int frame, Image[] splice) {
int maxDir = dirs < splice.length? dirs: splice.length;
for(int dirIdx = 0; dirIdx < maxDir; dirIdx++) {
insertImage(dirIdx, frame, splice[dirIdx]);
}
}
public void insertImage(int dir, int frame, Image splice) {
if(frame < 0 || frame >= frames)
throw new IllegalArgumentException("Provided frame is out of range: " + frame);
if(dir < 0 || dir >= dirs)
throw new IllegalArgumentException("Provided dir is out of range: " + dir);
images[getIndex(dir, frame)] = splice;
}
}

View File

@@ -0,0 +1,126 @@
package dmitool;
import java.util.HashMap;
import java.util.HashSet;
public class IconStateDiff {
static class ISAddress {
int dir;
int frame;
public ISAddress(int dir, int frame) {
this.dir = dir;
this.frame = frame;
}
public String infoStr(int maxDir, int maxFrame) {
if(maxDir == 1 && maxFrame == 1) {
return "";
} else if(maxDir == 1) {
return "{" + frame + "}";
} else if(maxFrame == 1) {
return "{" + Main.dirs[dir] + "}";
} else {
return "{" + Main.dirs[dir] + " " + frame + "}";
}
}
}
int oldFrameCount = 0;
int oldDirectionCount = 0;
boolean oldRewind = false;
int oldLoop = -1;
String oldHotspot = null;
int newFrameCount = 0;
int newDirectionCount = 0;
boolean newRewind = false;
int newLoop = -1;
String newHotspot = null;
IconState newState;
HashMap<ISAddress, Image> modifiedFrames = new HashMap<>();
HashMap<ISAddress, Image> newFrames = new HashMap<>();
HashSet<ISAddress> removedFrames = new HashSet<>();
public IconStateDiff(IconState base, IconState mod) {
int maxDir = Math.max(base.dirs, mod.dirs);
int maxFrame = Math.max(base.frames, mod.frames);
oldFrameCount = base.frames;
oldDirectionCount = base.dirs;
oldRewind = base.rewind;
oldLoop = base.loop;
oldHotspot = base.hotspot;
newFrameCount = mod.frames;
newDirectionCount = mod.dirs;
newRewind = mod.rewind;
newLoop = mod.loop;
newHotspot = mod.hotspot;
newState = mod;
Image baseI, modI;
for(int d=0; d<maxDir; d++) {
for(int f=0; f<maxFrame; f++) {
if(base.dirs > d && base.frames > f) {
baseI = base.images[f * base.dirs + d];
} else baseI = null;
if(mod.dirs > d && mod.frames > f) {
modI = mod.images[f * mod.dirs + d];
} else modI = null;
if(baseI == null && modI == null) continue;
if(baseI == null) newFrames.put(new ISAddress(d, f), modI);
else if(modI == null) removedFrames.add(new ISAddress(d, f));
else if(!baseI.equals(modI)) {
modifiedFrames.put(new ISAddress(d, f), modI);
}
}
}
}
@Override public String toString() {
String s = "";
String tmp;
if(newDirectionCount != oldDirectionCount)
s += " | dirs " + oldDirectionCount + "->" + newDirectionCount;
if(newFrameCount != oldFrameCount)
s += " | frames " + oldFrameCount + "->" + newFrameCount;
if(newRewind != oldRewind) {
s += " | rewind " + oldRewind + "->" + newRewind;
}
if(newLoop != oldLoop) {
s += " | loop " + oldLoop + "->" + newLoop;
}
if(newHotspot == null ? oldHotspot != null : !newHotspot.equals(oldHotspot)) {
s += " | hotspot " + oldHotspot + "->" + newHotspot;
}
if(!modifiedFrames.isEmpty()) {
int total_frames = Math.min(oldFrameCount, newFrameCount) * Math.min(oldDirectionCount, newDirectionCount);
tmp = "";
for(ISAddress isa: modifiedFrames.keySet()) {
String str = isa.infoStr(oldDirectionCount, oldFrameCount);
if(!"".equals(str)) {
tmp += ", " + str;
}
}
if(!"".equals(tmp)) {
s += " | modified " + modifiedFrames.size() + " of " + total_frames + ": " + tmp.substring(1);
} else {
s += " | modified " + modifiedFrames.size() + " of " + total_frames;
}
}
if("".equals(s))
return "No change";
return s.substring(3);
}
}

View File

@@ -0,0 +1,32 @@
package dmitool;
import java.io.IOException;
import java.io.OutputStream;
public abstract class Image {
int w, h;
abstract RGBA getPixel(int x, int y);
public Image(int w, int h) {
this.w = w;
this.h = h;
}
@Override public boolean equals(Object obj) {
if(obj == this) return true;
if(!(obj instanceof Image)) return false;
Image im = (Image) obj;
if(w != im.w || h != im.h) return false;
for(int i=0; i<w; i++) {
for(int j=0; j<h; j++) {
if(!getPixel(i, j).equals(im.getPixel(i, j))) return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,513 @@
package dmitool;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Set;
public class Main {
public static int VERBOSITY = 0;
public static boolean STRICT = false;
public static final String VERSION = "v0.6 (7 Jan 2015)";
public static final String[] dirs = new String[] {
"S", "N", "E", "W", "SE", "SW", "NE", "NW"
};
public static final String helpStr =
"help\n" +
"\tthis text\n" +
"version\n" +
"\tprint version and exit\n" +
"verify [file]\n" +
"\tattempt to load the given file to check format\n" +
"info [file]\n" +
"\tprint information about [file], including a list of states\n" +
"diff [file1] [file2]\n" +
"\tdiff between [file1] and [file2]\n" +
"sort [file]\n" +
"\tsort the icon_states in [file] into ASCIIbetical order\n" +
"merge [base] [file1] [file2] [out]\n" +
"\tmerge [file1] and [file2]'s changes from a common ancestor [base], saving the result in [out]\n" +
"\tconflicts will be placed in [out].conflict.dmi\n" +
"extract [file] [state] [out] {args}\n"+
"\textract [state] from [file] in PNG format to [out]\n" +
"\targs specify direction and frame; input 'f' followed by a frame specifier, and/or 'd' followed by a direction specifier\n" +
"\tframe specifier can be a single number or number-number for a range\n" +
"\tdirection specifier can be a single direction, or direction-direction\n" +
"\tdirection can be 0-7 or S, N, E, W, SE, SW, NE, NW (non-case-sensitive)\n" +
"import [file] [state] [in] [options]\n" +
"\timport a PNG image from [in] into [file], with the name [state]\n" +
"\tinput should be in the same format given by the 'extract' command with no direction or frame arguments\n" +
"\t(i.e. frames should be on the x-axis, and directions on the y)\n" +
"\tpossible options:\n" +
"\t nodup | nd | n : if the state [state] already exists in [file], replace it instead of append\n" +
"\t rewind | rw | r : if there is more than one frame, the animation should be played forwards-backwards-forwards-[...]\n" +
"\t loop | lp | l : loop the animation infinitely; equivalent to \"loopn -1\"\n" +
"\t loopn N | lpn N | ln N : loop the animation N times; for infinite animations, use 'loop' or N = -1\n" +
"\t movement | move | mov | m : [state] should be marked as a movement state\n" +
"\t delays L | delay L | del L | d L : use the list L as a comma-separated list of delays (e.g. '1,1,2,2,1')\n" +
"\t hotspot H | hs H | h H : use H as the hotspot for this state\n" +
"\t direction D | dir D : replaces D with the image from [in], instead of the entire state. D can be 0-7 or S, N, E, etc. If the state does not already exist, this is ignored\n" +
"";
public static void main(String[] args) throws FileNotFoundException, IOException, DMIException {
Deque<String> argq = new ArrayDeque<>();
for(String s: args) {
argq.addLast(s);
}
if(argq.size() == 0) {
System.out.println("No command found; use 'help' for help");
return;
}
String switches = argq.peekFirst();
if(switches.startsWith("-")) {
for(char c: switches.substring(1).toCharArray()) {
switch(c) {
case 'v': VERBOSITY++; break;
case 'q': VERBOSITY--; break;
case 'S': STRICT = true; break;
}
}
argq.pollFirst();
}
String op = argq.pollFirst();
switch(op) {
case "diff": {
if(argq.size() < 2) {
System.out.println("Insufficient arguments for command!");
System.out.println(helpStr);
return;
}
String a = argq.pollFirst();
String b = argq.pollFirst();
if(VERBOSITY >= 0) System.out.println("Loading " + a);
DMI dmi = doDMILoad(a);
if(VERBOSITY >= 0) dmi.printInfo();
if(VERBOSITY >= 0) System.out.println("Loading " + b);
DMI dmi2 = doDMILoad(b);
if(VERBOSITY >= 0) dmi2.printInfo();
DMIDiff dmid = new DMIDiff(dmi, dmi2);
System.out.println(dmid);
break;
}
case "sort": {
if(argq.size() < 1) {
System.out.println("Insufficient arguments for command!");
System.out.println(helpStr);
return;
}
String f = argq.pollFirst();
if(VERBOSITY >= 0) System.out.println("Loading " + f);
DMI dmi = doDMILoad(f);
if(VERBOSITY >= 0) dmi.printInfo();
if(VERBOSITY >= 0) System.out.println("Saving " + f);
dmi.writeDMI(new FileOutputStream(f), true);
break;
}
case "merge": {
if(argq.size() < 4) {
System.out.println("Insufficient arguments for command!");
System.out.println(helpStr);
return;
}
String baseF = argq.pollFirst(),
aF = argq.pollFirst(),
bF = argq.pollFirst(),
mergedF = argq.pollFirst();
if(VERBOSITY >= 0) System.out.println("Loading " + baseF);
DMI base = doDMILoad(baseF);
if(VERBOSITY >= 0) base.printInfo();
if(VERBOSITY >= 0) System.out.println("Loading " + aF);
DMI aDMI = doDMILoad(aF);
if(VERBOSITY >= 0) aDMI.printInfo();
if(VERBOSITY >= 0) System.out.println("Loading " + bF);
DMI bDMI = doDMILoad(bF);
if(VERBOSITY >= 0) bDMI.printInfo();
DMIDiff aDiff = new DMIDiff(base, aDMI);
DMIDiff bDiff = new DMIDiff(base, bDMI);
DMIDiff mergedDiff = new DMIDiff();
DMI conflictDMI = new DMI(32, 32);
Set<String> cf = aDiff.mergeDiff(bDiff, conflictDMI, mergedDiff, aF, bF);
mergedDiff.applyToDMI(base);
base.writeDMI(new FileOutputStream(mergedF));
if(!cf.isEmpty()) {
if(VERBOSITY >= 0) for(String s: cf) {
System.out.println(s);
}
conflictDMI.writeDMI(new FileOutputStream(mergedF + ".conflict.dmi"), true);
System.out.println("Add/modify conflicts placed in '" + mergedF + ".conflict.dmi'");
System.exit(1); // Git expects non-zero on merge conflict
} else {
System.out.println("No conflicts");
System.exit(0);
}
break;
}
case "extract": {
if(argq.size() < 3) {
System.out.println("Insufficient arguments for command!");
System.out.println(helpStr);
return;
}
String file = argq.pollFirst(),
state = argq.pollFirst(),
outFile = argq.pollFirst();
DMI dmi = doDMILoad(file);
if(VERBOSITY >= 0) dmi.printInfo();
IconState is = dmi.getIconState(state);
if(is == null) {
System.out.println("icon_state '"+state+"' does not exist!");
return;
}
// minDir, Maxdir, minFrame, Maxframe
int mDir=0, Mdir=is.dirs-1;
int mFrame=0, Mframe=is.frames-1;
while(argq.size() > 1) {
String arg = argq.pollFirst();
switch(arg) {
case "d":
case "dir":
case "dirs":
case "direction":
case "directions":
String dString = argq.pollFirst();
if(dString.contains("-")) {
String[] splitD = dString.split("-");
if(splitD.length == 2) {
mDir = parseDir(splitD[0], is);
Mdir = parseDir(splitD[1], is);
} else {
System.out.println("Illegal dir string: '" + dString + "'!");
return;
}
} else {
mDir = parseDir(dString, is);
Mdir = mDir;
}
// Invalid value check, warnings are printed in parseDir()
if(mDir == -1 || Mdir == -1) return;
if(Mdir < mDir) {
System.out.println("Maximum dir greater than minimum dir!");
System.out.println("Textual direction order is S, N, E, W, SE, SW, NE, NW increasing 0 (S) to 7 (NW)");
return;
}
break;
case "f":
case "frame":
case "frames":
String fString = argq.pollFirst();
if(fString.contains("-")) {
String[] splitF = fString.split("-");
if(splitF.length == 2) {
mFrame = parseFrame(splitF[0], is);
Mframe = parseFrame(splitF[1], is);
} else {
System.out.println("Illegal frame string: '" + fString + "'!");
return;
}
} else {
mFrame = parseFrame(fString, is);
Mframe = mFrame;
}
// Invalid value check, warnings are printed in parseFrame()
if(mFrame == -1 || Mframe == -1) return;
if(Mframe < mFrame) {
System.out.println("Maximum frame greater than minimum frame!");
return;
}
break;
default:
System.out.println("Unknown argument '" + arg + "' detected, ignoring.");
}
}
if(!argq.isEmpty()) {
System.out.println("Extra argument '" + argq.pollFirst() + "' detected, ignoring.");
}
is.dumpToPNG(new FileOutputStream(outFile), mDir, Mdir, mFrame, Mframe);
break;
}
case "import": {
if(argq.size() < 3) {
System.out.println("Insufficient arguments for command!");
System.out.println(helpStr);
return;
}
String dmiFile = argq.pollFirst(),
stateName = argq.pollFirst(),
pngFile = argq.pollFirst();
boolean noDup = false;
boolean rewind = false;
int loop = 0;
boolean movement = false;
String hotspot = null;
float[] delays = null;
String replaceDir = null;
String replaceFrame = null;
while(!argq.isEmpty()) {
String s = argq.pollFirst();
switch(s.toLowerCase()) {
case "nodup":
case "nd":
case "n":
noDup = true;
break;
case "rewind":
case "rw":
case "r":
rewind = true;
break;
case "loop":
case "lp":
case "l":
loop = -1;
break;
case "loopn":
case "lpn":
case "ln":
if(!argq.isEmpty()) {
String loopTimes = argq.pollFirst();
try {
loop = Integer.parseInt(loopTimes);
} catch(NumberFormatException nfe) {
System.out.println("Illegal number '" + loopTimes + "' as argument to '" + s + "'!");
return;
}
} else {
System.out.println("Argument '" + s + "' requires a numeric argument following it!");
return;
}
break;
case "movement":
case "move":
case "mov":
case "m":
movement = true;
break;
case "delays":
case "delay":
case "del":
case "d":
if(!argq.isEmpty()) {
String delaysString = argq.pollFirst();
String[] delaysSplit = delaysString.split(",");
delays = new float[delaysSplit.length];
for(int i=0; i<delaysSplit.length; i++) {
try {
delays[i] = Integer.parseInt(delaysSplit[i]);
} catch(NumberFormatException nfe) {
System.out.println("Illegal number '" + delaysSplit[i] + "' as argument to '" + s + "'!");
return;
}
}
} else {
System.out.println("Argument '" + s + "' requires a list of delays (in the format 'a,b,c,d,[...]') following it!");
return;
}
break;
case "hotspot":
case "hs":
case "h":
if(!argq.isEmpty()) {
hotspot = argq.pollFirst();
} else {
System.out.println("Argument '" + s + "' requires a hotspot string following it!");
return;
}
break;
case "dir":
case "direction":
if(!argq.isEmpty()) {
replaceDir = argq.pollFirst();
} else {
System.out.println("Argument '" + s + "' requires a direction argument following it!");
return;
}
break;
case "f":
case "frame":
if(!argq.isEmpty()) {
replaceFrame = argq.pollFirst();
} else {
System.out.println("Argument '" + s + "' requires a frame argument following it!");
return;
}
break;
default:
System.out.println("Unknown import argument '" + s + "', ignoring.");
break;
}
}
if(VERBOSITY >= 0) System.out.println("Loading " + dmiFile);
DMI toImportTo = doDMILoad(dmiFile);
if(VERBOSITY >= 0) toImportTo.printInfo();
IconState is = IconState.importFromPNG(toImportTo, new FileInputStream(pngFile), stateName, delays, rewind, loop, hotspot, movement);
//image insertion
if(replaceDir != null || replaceFrame != null) {
IconState targetIs = toImportTo.getIconState(stateName);
if(targetIs == null) {
System.out.println("'direction' or 'frame' specified and no icon state '" + stateName + "' found, aborting!");
return;
}
if(is.images.length == 0) {
System.out.println("'direction' or 'frame' specified and imported is empty, aborting!");
return;
}
if(!noDup) targetIs = targetIs.clone();
int dirToReplace, frameToReplace;
if(replaceDir != null && replaceFrame != null) {
frameToReplace = parseFrame(replaceFrame, targetIs);
dirToReplace = parseDir(replaceDir, targetIs);
targetIs.insertImage(dirToReplace, frameToReplace, is.images[0]);
}
else if(replaceDir != null) {
dirToReplace = parseDir(replaceDir, targetIs);
targetIs.insertDir(dirToReplace, is.images);
}
else if(replaceFrame != null) {
frameToReplace = parseFrame(replaceFrame, targetIs);
targetIs.insertFrame(frameToReplace, is.images);
}
if(!noDup) toImportTo.addIconState(null, targetIs);
}
else {
if(noDup) {
if(!toImportTo.setIconState(is)) {
toImportTo.addIconState(null, is);
}
} else {
toImportTo.addIconState(null, is);
}
}
if(VERBOSITY >= 0) toImportTo.printInfo();
if(VERBOSITY >= 0) System.out.println("Saving " + dmiFile);
toImportTo.writeDMI(new FileOutputStream(dmiFile));
break;
}
case "verify": {
if(argq.size() < 1) {
System.out.println("Insufficient arguments for command!");
System.out.println(helpStr);
return;
}
String vF = argq.pollFirst();
if(VERBOSITY >= 0) System.out.println("Loading " + vF);
DMI v = doDMILoad(vF);
if(VERBOSITY >= 0) v.printInfo();
break;
}
case "info": {
if(argq.size() < 1) {
System.out.println("Insufficient arguments for command!");
System.out.println(helpStr);
return;
}
String infoFile = argq.pollFirst();
if(VERBOSITY >= 0) System.out.println("Loading " + infoFile);
DMI info = doDMILoad(infoFile);
info.printInfo();
info.printStateList();
break;
}
case "version":
System.out.println(VERSION);
return;
default:
System.out.println("Command '" + op + "' not found!");
case "help":
System.out.println(helpStr);
break;
}
}
static int parseDir(String s, IconState is) {
try {
int i = Integer.parseInt(s);
if(0 <= i && i < is.dirs) {
return i;
} else {
System.out.println("Direction not in valid range [0, "+(is.dirs-1)+"]!");
return -1;
}
} catch(NumberFormatException nfe) {
for(int q=0; q<dirs.length && q < is.dirs; q++) {
if(dirs[q].equalsIgnoreCase(s)) {
return q;
}
}
String dSummary = "";
for(int i=0; i<is.dirs; i++) {
dSummary += ", " + dirs[i];
}
dSummary = dSummary.substring(2);
System.out.println("Unknown or non-existent direction '" + s + "'!");
System.out.println("Valid range: [0, "+(is.dirs-1)+"], or " + dSummary);
return -1;
}
}
static int parseFrame(String s, IconState is) {
try {
int i = Integer.parseInt(s);
if(0 <= i && i < is.frames) {
return i;
} else {
System.out.println("Frame not in valid range [0, "+(is.frames-1)+"]!");
return -1;
}
} catch(NumberFormatException nfe) {
System.out.println("Failed to parse frame number: '" + s + "'!");
return -1;
}
}
static DMI doDMILoad(String file) {
try {
DMI dmi = new DMI(file);
return dmi;
} catch(DMIException dmie) {
System.out.println("Failed to load " + file + ": " + dmie.getMessage());
} catch(FileNotFoundException fnfe) {
System.out.println("File not found: " + file);
}
System.exit(3);
return null;
}
}

View File

@@ -0,0 +1,14 @@
package dmitool;
public class NonPalettedImage extends Image {
RGBA[][] pixels;
public NonPalettedImage(int w, int h, RGBA[][] pixels) {
super(w, h);
this.pixels = pixels;
}
RGBA getPixel(int x, int y) {
return pixels[y][x];
}
}

View File

@@ -0,0 +1,27 @@
package dmitool;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class PNGChunk {
int len;
int type;
byte[] b;
int crc;
public PNGChunk(DataInputStream in) throws IOException {
len = in.readInt();
type = in.readInt();
b = new byte[len];
in.read(b);
crc = in.readInt();
}
void write(DataOutputStream out) throws IOException {
out.writeInt(len);
out.writeInt(type);
out.write(b);
out.writeInt(crc);
}
}

View File

@@ -0,0 +1,16 @@
package dmitool;
public class PalettedImage extends Image {
int[][] pixels;
RGBA[] pal;
public PalettedImage(int w, int h, int[][] pixels, RGBA[] palette) {
super(w, h);
this.pixels = pixels;
this.pal = palette;
}
RGBA getPixel(int x, int y) {
return pal[pixels[y][x]];
}
}

View File

@@ -0,0 +1,33 @@
package dmitool;
public class RGBA {
int r, g, b, a;
public RGBA(int r, int g, int b, int a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
@Override
public String toString() {
String s = Long.toString(toRGBA8888());
while(s.length() < 8)
s = "0" + s;
return "#" + s;
}
@Override public boolean equals(Object obj) {
if(obj == this) return true;
if(!(obj instanceof RGBA)) return false;
RGBA o = (RGBA) obj;
return r==o.r && g==o.g && b==o.b && a==o.a;
}
public long toRGBA8888() {
return (r<<24) | (g<<16) | (b<<8) | a;
}
}