fixed PNG loader to support indexed color

This commit is contained in:
LAX1DUDE 2022-03-21 01:26:04 -07:00
parent 3e17d57250
commit 94a8c3f6e9
15 changed files with 512 additions and 485 deletions

View File

@ -26,7 +26,6 @@ public class Chunk {
return output; return output;
} }
protected Chunk(byte[] length, byte[] type, byte[] data, byte[] crc) { protected Chunk(byte[] length, byte[] type, byte[] data, byte[] crc) {
this.length = ByteHandler.byteToLong(length); this.length = ByteHandler.byteToLong(length);
this.data = data; this.data = data;

View File

@ -14,6 +14,12 @@ public enum ChunkType {
png.setIhdr(new IHDR(length, type, data, crc)); png.setIhdr(new IHDR(length, type, data, crc));
} }
}, },
tRNS {
@Override
public void apply(PNG png, byte[] length, byte[] type, byte[] data, byte[] crc) throws DecodeException {
png.setTrns(new tRNS(length, type, data, crc));
}
},
PLTE { PLTE {
@Override @Override
public void apply(PNG png, byte[] length, byte[] type, byte[] data, byte[] crc) throws DecodeException { public void apply(PNG png, byte[] length, byte[] type, byte[] data, byte[] crc) throws DecodeException {

View File

@ -11,14 +11,11 @@ public class IHDR extends Chunk {
private int bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod; private int bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod;
final private static int[] colorTypeValid = { 0, 2, 3, 4, 6 }; final private static int[] colorTypeValid = { 0, 2, 3, 4, 6 };
final private static int[][] mapColorBitDepth = { final private static int[][] mapColorBitDepth = { { 1, 2, 4, 8, 16 }, // color type = 0
{1, 2, 4, 8, 16}, // color type = 0 {}, { 8, 16 }, // color type = 2
{},
{8, 16}, // color type = 2
{ 1, 2, 4, 8 }, // color type = 3 { 1, 2, 4, 8 }, // color type = 3
{ 8, 16 }, // color type = 4 { 8, 16 }, // color type = 4
{}, {}, { 8, 16 } // color type = 6
{8, 16} // color type = 6
}; };
// the number of bytes per complete pixel, rounding up to one // the number of bytes per complete pixel, rounding up to one
@ -44,17 +41,23 @@ public class IHDR extends Chunk {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("width=");sb.append(width); sb.append("width=");
sb.append("height=");sb.append(height); sb.append(width);
sb.append("bitDepth=");sb.append(bitDepth); sb.append("height=");
sb.append("colorType=");sb.append(colorType); sb.append(height);
sb.append("compressionMethod=");sb.append(compressionMethod); sb.append("bitDepth=");
sb.append("filterMethod=");sb.append(filterMethod); sb.append(bitDepth);
sb.append("interlaceMethod=");sb.append(interlaceMethod); sb.append("colorType=");
sb.append(colorType);
sb.append("compressionMethod=");
sb.append(compressionMethod);
sb.append("filterMethod=");
sb.append(filterMethod);
sb.append("interlaceMethod=");
sb.append(interlaceMethod);
return sb.toString(); return sb.toString();
} }
private void build() { private void build() {
this.width = ByteHandler.byteToLong(data); this.width = ByteHandler.byteToLong(data);
this.height = ByteHandler.byteToLong(data, 4); this.height = ByteHandler.byteToLong(data, 4);
@ -65,7 +68,6 @@ public class IHDR extends Chunk {
this.interlaceMethod = ((int) data[12]) & 0xFF; this.interlaceMethod = ((int) data[12]) & 0xFF;
} }
private void checkLegal() throws DecodeException { private void checkLegal() throws DecodeException {
boolean legal = false; boolean legal = false;
for (int c : colorTypeValid) { for (int c : colorTypeValid) {
@ -82,10 +84,10 @@ public class IHDR extends Chunk {
return; return;
} }
} }
throw new DecodeException("Initialzie IHDR : bit depth " + bitDepth + " not valid matching color type " + colorType); throw new DecodeException(
"Initialzie IHDR : bit depth " + bitDepth + " not valid matching color type " + colorType);
} }
public long getWidth() { public long getWidth() {
return this.width; return this.width;
} }
@ -114,5 +116,4 @@ public class IHDR extends Chunk {
return interlaceMethod; return interlaceMethod;
} }
} }

View File

@ -2,7 +2,6 @@ package com.baislsl.png.chunk;
import com.baislsl.png.decode.DecodeException; import com.baislsl.png.decode.DecodeException;
/** /**
* Created by baislsl on 17-7-9. * Created by baislsl on 17-7-9.
*/ */
@ -14,15 +13,14 @@ public class PLTE extends Chunk {
build(); build();
} }
private void build() throws DecodeException { private void build() throws DecodeException {
if (this.length % 3 != 0) if (this.length % 3 != 0)
throw new DecodeException("PLTE length can not be divide by 3"); throw new DecodeException("PLTE length can not be divide by 3");
int size = (int) length / 3; int size = (int) length / 3;
color = new int[size]; color = new int[size];
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
color[i] = (((int)data[i*3]) & 0xFF) << 16 | (((int)data[i*3 + 1]) & 0xFF) << 8 | (((int)data[i*3 + 2]) & 0xFF); color[i] = (((int) data[i * 3]) & 0xFF) << 16 | (((int) data[i * 3 + 1]) & 0xFF) << 8
| (((int) data[i * 3 + 2]) & 0xFF) | 0xFF000000;
} }
} }
@ -34,5 +32,4 @@ public class PLTE extends Chunk {
return color.length; return color.length;
} }
} }

View File

@ -0,0 +1,18 @@
package com.baislsl.png.chunk;
import com.baislsl.png.decode.DecodeException;
/**
* Created by baislsl on 17-7-9.
*/
public class tRNS extends Chunk {
public tRNS(byte[] length, byte[] type, byte[] data, byte[] crc) throws DecodeException {
super(length, type, data, crc);
}
public int getAlpha() {
return (int)data[0] & 0xFF;
}
}

View File

@ -5,7 +5,8 @@ package com.baislsl.png.decode;
*/ */
public class DecodeException extends Exception { public class DecodeException extends Exception {
public DecodeException() {} public DecodeException() {
}
public DecodeException(String message) { public DecodeException(String message) {
super(message); super(message);

View File

@ -16,9 +16,7 @@ public class Decoder {
// private final static Logger LOG = LoggerFactory.getLogger(Decoder.class); // private final static Logger LOG = LoggerFactory.getLogger(Decoder.class);
private final InputStream in; private final InputStream in;
private final static char[] head = { private final static char[] head = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
};
public Decoder(InputStream in) { public Decoder(InputStream in) {
this.in = in; this.in = in;
@ -33,11 +31,10 @@ public class Decoder {
// LOG.info(ByteHandler.byteToString(header)); // LOG.info(ByteHandler.byteToString(header));
} }
private boolean readChunk(PNG png, String chunkName, private boolean readChunk(PNG png, String chunkName, byte[] length, byte[] type, byte[] data, byte[] crc)
byte[] length, byte[] type, throws IOException, DecodeException {
byte[] data, byte[] crc) throws IOException, DecodeException {
for (ChunkType chunkType : ChunkType.values()) { for (ChunkType chunkType : ChunkType.values()) {
if (chunkType.name().equals(chunkName)) { if (chunkType.name().equalsIgnoreCase(chunkName)) {
chunkType.apply(png, length, type, data, crc); chunkType.apply(png, length, type, data, crc);
return true; return true;
} }
@ -88,13 +85,12 @@ public class Decoder {
return png; return png;
} }
private byte[] readBytes(int size) throws IOException { private byte[] readBytes(int size) throws IOException {
byte[] result = new byte[size]; byte[] result = new byte[size];
int ret = in.read(result, 0, size); int ret = in.read(result, 0, size);
if (ret == -1) return null; if (ret == -1)
return null;
return result; return result;
} }
} }

View File

@ -15,16 +15,14 @@ public class PNG {
public IHDR ihdr; public IHDR ihdr;
public IDATManager idats = new IDATManager(); public IDATManager idats = new IDATManager();
public PLTE plte; public PLTE plte;
public tRNS trns;
public IEND iend; public IEND iend;
public PNG() { public PNG() {
} }
public PNG(IHDR ihdr, IDATManager idats, PLTE plte, IEND iend) { public boolean isAlpha() {
this.ihdr = ihdr; return this.trns != null || ihdr.getBpp() == 4;
this.idats = idats;
this.plte = plte;
this.iend = iend;
} }
public int[] getColor() throws DecodeException { public int[] getColor() throws DecodeException {
@ -48,14 +46,16 @@ public class PNG {
switch (colorType) { switch (colorType) {
case 2: case 2:
if (bitDepth == 8) { // bpp = 3 if (bitDepth == 8) { // bpp = 3
colors[idx] = ((int)data[i][bpp * j] & 0xff) << 16 | ((int)data[i][bpp * j + 1] & 0xff) << 8 | ((int)data[i][bpp * j + 2] & 0xff); colors[idx] = ((int) data[i][bpp * j] & 0xff) << 16 | ((int) data[i][bpp * j + 1] & 0xff) << 8
| ((int) data[i][bpp * j + 2] & 0xff);
} else { } else {
throw new DecodeException("not supported"); throw new DecodeException("not supported");
} }
break; break;
case 6: case 6:
if (bitDepth == 8) { // bpp = 4 if (bitDepth == 8) { // bpp = 4
colors[idx] = ((int)data[i][bpp * j] & 0xff) << 16 | ((int)data[i][bpp * j + 1] & 0xff) << 8 | ((int)data[i][bpp * j + 2] & 0xff) | ((int)data[i][bpp * j + 3] & 0xff) << 24; colors[idx] = ((int) data[i][bpp * j] & 0xff) << 16 | ((int) data[i][bpp * j + 1] & 0xff) << 8
| ((int) data[i][bpp * j + 2] & 0xff) | ((int) data[i][bpp * j + 3] & 0xff) << 24;
} else { } else {
throw new DecodeException("not supported"); throw new DecodeException("not supported");
} }
@ -63,7 +63,13 @@ public class PNG {
case 3: case 3:
int gap = 8 / bitDepth; int gap = 8 / bitDepth;
int a = (1 << bitDepth) - 1; int a = (1 << bitDepth) - 1;
colors[idx] = plte.getColor(data[i][j / gap] & a); int b = gap - (j % gap) - 1;
int pi = (data[i][j / gap] >> (b * bitDepth)) & a;
if (trns != null && trns.getAlpha() == pi) {
colors[idx] = 0;
}else {
colors[idx] = plte.getColor(pi);
}
break; break;
default: default:
throw new DecodeException("Do not support color type " + colorType); throw new DecodeException("Do not support color type " + colorType);
@ -103,6 +109,10 @@ public class PNG {
this.plte = plte; this.plte = plte;
} }
public void setTrns(tRNS trns) {
this.trns = trns;
}
public void setIend(IEND iend) { public void setIend(IEND iend) {
this.iend = iend; this.iend = iend;
} }

View File

@ -22,7 +22,6 @@ public class ByteHandler {
return byteToLong(data, 0, 4); return byteToLong(data, 0, 4);
} }
public static String byteToString(byte[] data) { public static String byteToString(byte[] data) {
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
for (byte b : data) { for (byte b : data) {

View File

@ -20,7 +20,6 @@ public class CRC {
} }
} }
private static long updateCrc(long crc, byte[] buf, int size) { private static long updateCrc(long crc, byte[] buf, int size) {
long ans = crc; long ans = crc;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {

View File

@ -1,15 +1,16 @@
package com.baislsl.png.util; package com.baislsl.png.util;
public class ReverseFilter { public class ReverseFilter {
private ReverseFilter(){} private ReverseFilter() {
}
private static int paethPredictor(int a, int b, int c) { private static int paethPredictor(int a, int b, int c) {
int p = a + b - c; int p = a + b - c;
int pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c); int pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c);
if (pa <= pb && pa <= pc) return a; if (pa <= pb && pa <= pc)
if (pb <= pc) return b; return a;
if (pb <= pc)
return b;
return c; return c;
} }

View File

@ -39,7 +39,7 @@ public class EaglerImage {
public static final EaglerImage loadImage(byte[] file) { public static final EaglerImage loadImage(byte[] file) {
try { try {
PNG p = (new Decoder(new ByteArrayInputStream(file))).readInPNG(); PNG p = (new Decoder(new ByteArrayInputStream(file))).readInPNG();
return new EaglerImage(p.getColor(), (int)p.getWidth(), (int)p.getHeight(), p.ihdr.getBpp() == 4); return new EaglerImage(p.getColor(), (int)p.getWidth(), (int)p.getHeight(), p.isAlpha());
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
return null; return null;