initial commit - cross reference with 5th port - obviously has compile errors

This commit is contained in:
LetterJay
2016-07-03 02:17:19 -05:00
commit 35a1723e98
4355 changed files with 2221257 additions and 0 deletions
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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];
}
}
@@ -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);
}
}
@@ -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]];
}
}
@@ -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;
}
}