diff --git a/icons/mob/species/unathi/tail.dmi b/icons/mob/species/unathi/tail.dmi index 97ae78393af..eba37dba6aa 100644 Binary files a/icons/mob/species/unathi/tail.dmi and b/icons/mob/species/unathi/tail.dmi differ diff --git a/tools/dmitool/dmitool.jar b/tools/dmitool/dmitool.jar index 34bdb5bdd45..c7df4d8b7fd 100644 Binary files a/tools/dmitool/dmitool.jar and b/tools/dmitool/dmitool.jar differ diff --git a/tools/dmitool/dmitool.py b/tools/dmitool/dmitool.py new file mode 100644 index 00000000000..390f0d745ff --- /dev/null +++ b/tools/dmitool/dmitool.py @@ -0,0 +1,94 @@ +""" 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) diff --git a/tools/dmitool/src/main/java/dmitool/IconState.java b/tools/dmitool/src/main/java/dmitool/IconState.java index c3eb2cff21f..2a2202c71cd 100644 --- a/tools/dmitool/src/main/java/dmitool/IconState.java +++ b/tools/dmitool/src/main/java/dmitool/IconState.java @@ -175,7 +175,7 @@ public class IconState { } int pxW = in.imgInfo.cols; int pxH = in.imgInfo.rows; - int frames = pxW / w; + 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 @@ -203,7 +203,7 @@ public class IconState { px[bY][bX + 3]); } } - images[imageY + imageX*dirs] = new NonPalettedImage(w, h, pixels); + images[_getIndex(imageY, imageX, dirs)] = new NonPalettedImage(w, h, pixels); } } @@ -211,6 +211,38 @@ public class IconState { 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; + } } @@ -244,6 +276,5 @@ public class IconState { - diff --git a/tools/dmitool/src/main/java/dmitool/Main.java b/tools/dmitool/src/main/java/dmitool/Main.java index 493d7b58fe2..fe6048ca3b0 100644 --- a/tools/dmitool/src/main/java/dmitool/Main.java +++ b/tools/dmitool/src/main/java/dmitool/Main.java @@ -59,6 +59,7 @@ public class Main { "\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 { @@ -270,6 +271,8 @@ public class Main { 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()) { @@ -341,6 +344,24 @@ public class Main { 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; @@ -352,12 +373,46 @@ public class Main { if(VERBOSITY >= 0) toImportTo.printInfo(); IconState is = IconState.importFromPNG(toImportTo, new FileInputStream(pngFile), stateName, delays, rewind, loop, hotspot, movement); - if(noDup) { - if(!toImportTo.setIconState(is)) { + //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); } - } else { - toImportTo.addIconState(null, is); } if(VERBOSITY >= 0) toImportTo.printInfo();