/*
* Copyright © 2009-2011 Rebecca G. Bettencourt / Kreative Software
* <p>
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* <a href="http://www.mozilla.org/MPL/">http://www.mozilla.org/MPL/</a>
* <p>
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* <p>
* Alternatively, the contents of this file may be used under the terms
* of the GNU Lesser General Public License (the "LGPL License"), in which
* case the provisions of LGPL License are applicable instead of those
* above. If you wish to allow use of your version of this file only
* under the terms of the LGPL License and not to allow others to use
* your version of this file under the MPL, indicate your decision by
* deleting the provisions above and replace them with the notice and
* other provisions required by the LGPL License. If you do not delete
* the provisions above, a recipient may use your version of this file
* under either the MPL or the LGPL License.
* @since PowerPaint 1.0
* @author Rebecca G. Bettencourt, Kreative Software
*/
package com.kreative.paint.format;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import com.kreative.paint.Canvas;
import com.kreative.paint.form.Form;
import com.kreative.paint.io.Monitor;
public class WOBAFormat implements Format {
public String getName() { return "WOBA"; }
public String getExpandedName() { return "Wrath of Bill Atkinson"; }
public String getExtension() { return "woba"; }
public int getMacFileType() { return 0x574F4241; }
public int getMacResourceType() { return 0x574F4241; }
public long getDFFType() { return 0x496D6720574F4241L; }
public MediaType getMediaType() { return MediaType.IMAGE; }
public GraphicType getGraphicType() { return GraphicType.BITMAP; }
public SizeType getSizeType() { return SizeType.ARBITRARY; }
public ColorType getColorType() { return ColorType.BLACK_AND_WHITE; }
public AlphaType getAlphaType() { return AlphaType.OPAQUE_AND_TRANSPARENT; }
public LayerType getLayerType() { return LayerType.FLAT; }
public boolean onlyUponRequest() { return false; }
public int usesMagic() { return 24; }
public boolean acceptsMagic(byte[] start, long length) {
try {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(start));
if (in.readInt() < 64) return false;
if (in.readInt() != 0x424D4150) return false;
in.readInt();
if (in.readInt() != 0) return false;
if (in.readShort() != 0) return false;
if (in.readShort() != 0) return false;
if (in.readShort() != 1) return false;
if (in.readShort() != 0) return false;
in.close();
return true;
} catch (IOException ioe) {
return false;
}
}
public boolean acceptsExtension(String ext) { return ext.equalsIgnoreCase("woba"); }
public boolean acceptsMacFileType(int type) { return type == 0x574F4241; }
public boolean acceptsMacResourceType(int type) { return type == 0x574F4241; }
public boolean acceptsDFFType(long type) { return type == 0x496D6720574F4241L; }
public boolean supportsRead() { return true; }
public boolean usesReadOptionForm() { return false; }
public Form getReadOptionForm() { return null; }
public Canvas read(DataInputStream in, Monitor m) throws IOException {
if (in.readInt() < 64) throw new NotThisFormatException();
if (in.readInt() != 0x424D4150) throw new NotThisFormatException();
in.readInt();
if (in.readInt() != 0) throw new NotThisFormatException();
if (in.readShort() != 0) throw new NotThisFormatException();
if (in.readShort() != 0) throw new NotThisFormatException();
if (in.readShort() != 1) throw new NotThisFormatException();
if (in.readShort() != 0) throw new NotThisFormatException();
short bt = in.readShort();
short bl = in.readShort();
short bb = in.readShort();
short br = in.readShort();
Rectangle boundRect = new Rectangle(bl, bt, br-bl, bb-bt);
short mt = in.readShort();
short ml = in.readShort();
short mb = in.readShort();
short mr = in.readShort();
Rectangle maskRect = new Rectangle(ml, mt, mr-ml, mb-mt);
short it = in.readShort();
short il = in.readShort();
short ib = in.readShort();
short ir = in.readShort();
Rectangle imageRect = new Rectangle(il, it, ir-il, ib-it);
in.readInt();
in.readInt();
int msize = in.readInt();
int isize = in.readInt();
byte[] mdata = new byte[msize];
in.readFully(mdata);
byte[] idata = new byte[isize];
in.readFully(idata);
byte[] mbytes = decodeWOBA(boundRect, maskRect, mdata, 0, msize);
byte[] ibytes = decodeWOBA(boundRect, imageRect, idata, 0, isize);
int[] pixels = new int[ibytes.length*8];
for (int ii=0, mi=0, pi=0; ii<ibytes.length && mi<mbytes.length && pi<pixels.length; ii++, mi++, pi+=8) {
byte ibt = ibytes[ii];
byte mbt = mbytes[mi];
pixels[pi+0] = ((ibt & 0x80)>0)?0xFF000000:((mbt & 0x80)>0)?0xFFFFFFFF:0;
pixels[pi+1] = ((ibt & 0x40)>0)?0xFF000000:((mbt & 0x40)>0)?0xFFFFFFFF:0;
pixels[pi+2] = ((ibt & 0x20)>0)?0xFF000000:((mbt & 0x20)>0)?0xFFFFFFFF:0;
pixels[pi+3] = ((ibt & 0x10)>0)?0xFF000000:((mbt & 0x10)>0)?0xFFFFFFFF:0;
pixels[pi+4] = ((ibt & 0x08)>0)?0xFF000000:((mbt & 0x08)>0)?0xFFFFFFFF:0;
pixels[pi+5] = ((ibt & 0x04)>0)?0xFF000000:((mbt & 0x04)>0)?0xFFFFFFFF:0;
pixels[pi+6] = ((ibt & 0x02)>0)?0xFF000000:((mbt & 0x02)>0)?0xFFFFFFFF:0;
pixels[pi+7] = ((ibt & 0x01)>0)?0xFF000000:((mbt & 0x01)>0)?0xFFFFFFFF:0;
}
Canvas c = new Canvas(boundRect.width, boundRect.height);
c.get(0).setRGB(0, 0, boundRect.width, boundRect.height, pixels, 0, snap32(boundRect).width);
return c;
}
public boolean supportsWrite() { return true; }
public boolean usesWriteOptionForm() { return false; }
public Form getWriteOptionForm() { return null; }
public int approximateFileSize(Canvas c) {
return c.getWidth()*c.getHeight()/8;
}
public void write(Canvas c, DataOutputStream out, Monitor m) throws IOException {
int w = c.getWidth();
int h = c.getHeight();
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
c.paint(g);
g.dispose();
Rectangle boundRect = new Rectangle(0, 0, w, h);
int sw = snap32(boundRect).width;
int[] pixels = new int[sw*h];
bi.getRGB(0, 0, w, h, pixels, 0, sw);
byte[] mbytes = new byte[pixels.length >>> 3];
byte[] ibytes = new byte[pixels.length >>> 3];
for (int ii=0, mi=0, pi=0; ii<ibytes.length && mi<mbytes.length && pi<pixels.length; ii++, mi++, pi+=8) {
mbytes[mi] = 0;
ibytes[ii] = 0;
for (int p=0, pp=pi; p<8 && pp<pixels.length; p++, pp++) {
mbytes[mi] <<= 1;
ibytes[ii] <<= 1;
if (isOpaque(pixels[pp])) {
mbytes[mi] |= 1;
if (isBlack(pixels[pp])) {
ibytes[ii] |= 1;
}
}
}
}
byte[] mdata = encodeWOBA(boundRect, mbytes);
byte[] idata = encodeWOBA(boundRect, ibytes);
out.writeInt(64+mdata.length+idata.length);
out.writeInt(0x424D4150);
out.writeInt(0x0000126B);
out.writeInt(0);
out.writeShort(0);
out.writeShort(0);
out.writeShort(1);
out.writeShort(0);
out.writeShort(boundRect.y);
out.writeShort(boundRect.x);
out.writeShort(boundRect.y+boundRect.height);
out.writeShort(boundRect.x+boundRect.width);
out.writeShort(boundRect.y);
out.writeShort(boundRect.x);
out.writeShort(boundRect.y+boundRect.height);
out.writeShort(boundRect.x+boundRect.width);
out.writeShort(boundRect.y);
out.writeShort(boundRect.x);
out.writeShort(boundRect.y+boundRect.height);
out.writeShort(boundRect.x+boundRect.width);
out.writeInt(0);
out.writeInt(0);
out.writeInt(mdata.length);
out.writeInt(idata.length);
out.write(mdata);
out.write(idata);
}
private static Rectangle snap32(Rectangle r) {
int left = r.x & ~0x1F;
int right = r.x+r.width;
if ((right & 0x1F) != 0) {
right |= 0x1F;
right++;
}
return new Rectangle(left, r.y, right-left, r.height);
}
private static byte[] decodeWOBA(Rectangle totr, Rectangle r, byte[] data, int offset, int l) {
Rectangle tr = snap32(totr);
int trw = tr.width >> 3;
Rectangle rf = snap32(r);
int rw = rf.width >> 3;
byte[] stuff = new byte[trw*tr.height];
try {
if (l == 0) {
if (r.width > 0 && r.height > 0) {
int sbyte = r.x >> 3;
int sbit = r.x & 0x7;
int ebyte = (r.x+r.width) >> 3;
int ebit = (r.x+r.width) & 0x7;
int base = trw*r.y;
for (int y=r.y; y<r.y+r.height; y++) {
stuff[base+sbyte] = (byte)(0xFF >> sbit);
for (int x=sbyte+1; x<ebyte; x++) {
stuff[base+x] = (byte)0xFF;
}
if (ebit>0) stuff[base+ebyte] = (byte)(0xFF << (8-ebit));
base += trw;
}
}
} else {
int p = offset;
int y = rf.y-tr.y;
int base = trw*y + ((rf.x-tr.x) >> 3);
int pp = base;
int repeat = 1;
int dh = 0, dv = 0;
byte[] patt = new byte[]{
(byte)0xAA, (byte)0x55, (byte)0xAA, (byte)0x55,
(byte)0xAA, (byte)0x55, (byte)0xAA, (byte)0x55
};
while (y<rf.y-tr.y+rf.height && p<data.length) {
byte opcode = data[p++];
if ((opcode & 0x80) == 0) {
int d = (opcode & 0x70) >> 4;
int z = opcode & 0x0F;
byte[] dat = new byte[d];
for (int i=0; i<d; i++) dat[i] = data[p++];
while ((repeat--) > 0) {
pp += z;
for (int i=0; i<d; i++) stuff[pp++] = dat[i];
}
} else if ((opcode & 0xE0) == 0xA0) {
repeat = (opcode & 0x1F);
continue;
} else if ((opcode & 0xE0) == 0xC0) {
int d = (opcode & 0x1F) << 3;
byte[] dat = new byte[d];
for (int i=0; i<d; i++) dat[i] = data[p++];
while ((repeat--) > 0) {
for (int i=0; i<d; i++) stuff[pp++] = dat[i];
}
} else if ((opcode & 0xE0) == 0xE0) {
pp += ((opcode & 0x1F) << 4)*repeat;
} else {
switch (opcode) {
case (byte)0x80: {
byte[] dat = new byte[rw];
for (int i=0; i<rw; i++) dat[i] = data[p++];
while ((repeat--) > 0) {
for (int i=0; i<rw; i++) stuff[pp++] = dat[i];
y++;
base += trw;
pp = base;
}
repeat = 1;
}
break;
case (byte)0x81: {
y += repeat;
base += trw*repeat;
pp = base;
repeat = 1;
}
break;
case (byte)0x82: {
while ((repeat--) > 0) {
for (int i=0; i<rw; i++) stuff[pp++] = -1;
y++;
base += trw;
pp = base;
}
repeat = 1;
}
break;
case (byte)0x83: {
byte pb = data[p++];
while ((repeat--) > 0) {
patt[y & 0x7] = pb;
for (int i=0; i<rw; i++) stuff[pp++] = pb;
y++;
base += trw;
pp = base;
}
repeat = 1;
}
break;
case (byte)0x84: {
while ((repeat--) > 0) {
byte pb = patt[y & 0x7];
for (int i=0; i<rw; i++) stuff[pp++] = pb;
y++;
base += trw;
pp = base;
}
repeat = 1;
}
break;
case (byte)0x85: {
while ((repeat--) > 0) {
for (int i=0; i<rw; i++) {
stuff[pp] = stuff[pp-trw];
pp++;
}
y++;
base += trw;
pp = base;
}
repeat = 1;
}
break;
case (byte)0x86: {
while ((repeat--) > 0) {
for (int i=0; i<rw; i++) {
stuff[pp] = stuff[pp-(trw*2)];
pp++;
}
y++;
base += trw;
pp = base;
}
repeat = 1;
}
break;
case (byte)0x87: {
while ((repeat--) > 0) {
for (int i=0; i<rw; i++) {
stuff[pp] = stuff[pp-(trw*3)];
pp++;
}
y++;
base += trw;
pp = base;
}
repeat = 1;
}
break;
case (byte)0x88: dh = 16; dv = 0; break;
case (byte)0x89: dh = 0; dv = 0; break;
case (byte)0x8A: dh = 0; dv = 1; break;
case (byte)0x8B: dh = 0; dv = 2; break;
case (byte)0x8C: dh = 1; dv = 0; break;
case (byte)0x8D: dh = 1; dv = 1; break;
case (byte)0x8E: dh = 2; dv = 2; break;
case (byte)0x8F: dh = 8; dv = 0; break;
}
continue;
}
repeat = 1;
if (pp >= base+rw) {
if (dh != 0) {
byte[] row = new byte[rw];
for (int i=0; i<rw; i++) row[i] = stuff[base+i];
int numshifts = (rw << 3)/dh;
while ((numshifts--) > 0) {
int acc = 0;
for (int i=0; i<rw; i+=4) {
int tmp = ((row[i] & 0xFF) << 24) | ((row[i+1] & 0xFF) << 16) | ((row[i+2] & 0xFF) << 8) | (row[i+3] & 0xFF);
int rowi = acc | (tmp >>> dh);
row[i] = (byte)((rowi >>> 24) & 0xFF);
row[i+1] = (byte)((rowi >>> 16) & 0xFF);
row[i+2] = (byte)((rowi >>> 8) & 0xFF);
row[i+3] = (byte)(rowi & 0xFF);
acc = tmp << (32-dh);
}
for (int i=0; i<rw; i++) stuff[base+i] ^= row[i];
}
}
if (dv != 0 && y-dv >= 0) {
for (int i=0; i<rw; i++) stuff[base+i] = (byte)(stuff[base+i] ^ stuff[(base-(trw*dv))+i]);
}
y++;
base += trw;
pp = base;
}
}
}
} catch (ArrayIndexOutOfBoundsException e) {
//Malformed data did this to us.
//HyperCard would just crashy crashy with malformed WOBA data;
//we'll be better than that.
}
return stuff;
}
private static boolean isBlack(int pixel) {
int r = ((pixel >>> 16) & 0xFF);
int g = ((pixel >>> 8) & 0xFF);
int b = ((pixel >>> 0) & 0xFF);
int k = (30*r + 59*g + 11*b) / 100;
return (k < 0x80);
}
private static boolean isOpaque(int pixel) {
int a = ((pixel >>> 24) & 0xFF);
return (a >= 0x80);
}
private static byte[] encodeWOBA(Rectangle totr, byte[] stuff) throws IOException {
/*
* NOTE - For encoding, we do not currently implement all the features
* necessary to get the smallest possible compression. We just implement
* a smaller subset of possible operations.
*/
int bpr = snap32(totr).width >> 3;
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] patt = new byte[]{
(byte)0xAA, (byte)0x55, (byte)0xAA, (byte)0x55,
(byte)0xAA, (byte)0x55, (byte)0xAA, (byte)0x55
};
byte[] prevprevrow = null;
byte[] prevrow = null;
for (int y = 0, sy = 0; y < totr.height && sy < stuff.length; y++, sy += bpr) {
byte[] row = new byte[bpr];
byte npatt = stuff[sy];
boolean isBlack = true;
boolean isNewPattern = true;
boolean isOldPattern = true;
boolean isWhite = true;
boolean isPrevRow = true;
boolean isPrevPrevRow = true;
for (int x = 0, sx = sy; x < bpr && sx < stuff.length; x++, sx++) {
row[x] = stuff[sx];
if (row[x] != (byte)0xFF) isBlack = false;
if (row[x] != npatt) isNewPattern = false;
if (row[x] != patt[y & 7]) isOldPattern = false;
if (row[x] != (byte)0x00) isWhite = false;
if (prevrow == null || row[x] != prevrow[x]) isPrevRow = false;
if (prevprevrow == null || row[x] != prevprevrow[x]) isPrevPrevRow = false;
}
if (isWhite) out.write(0x81);
else if (isBlack) out.write(0x82);
else if (isPrevRow) out.write(0x85);
else if (isPrevPrevRow) out.write(0x86);
else if (isOldPattern) out.write(0x84);
else if (isNewPattern) {
out.write(0x83);
out.write(npatt);
patt[y & 7] = npatt;
}
else {
out.write(0x80);
out.write(row);
}
prevprevrow = prevrow;
prevrow = row;
}
return out.toByteArray();
}
}