package jogamp.opengl.util.pngj.chunks;
// see http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
// http://www.w3.org/TR/PNG/#5Chunk-naming-conventions
// http://www.w3.org/TR/PNG/#table53
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
public class ChunkHelper {
public static final String IHDR = "IHDR";
public static final String PLTE = "PLTE";
public static final String IDAT = "IDAT";
public static final String IEND = "IEND";
public static final byte[] b_IHDR = toBytes(IHDR);
public static final byte[] b_PLTE = toBytes(PLTE);
public static final byte[] b_IDAT = toBytes(IDAT);
public static final byte[] b_IEND = toBytes(IEND);
public static final String cHRM = "cHRM";
public static final String gAMA = "gAMA";
public static final String iCCP = "iCCP";
public static final String sBIT = "sBIT";
public static final String sRGB = "sRGB";
public static final String bKGD = "bKGD";
public static final String hIST = "hIST";
public static final String tRNS = "tRNS";
public static final String pHYs = "pHYs";
public static final String sPLT = "sPLT";
public static final String tIME = "tIME";
public static final String iTXt = "iTXt";
public static final String tEXt = "tEXt";
public static final String zTXt = "zTXt";
private static final ThreadLocal<Inflater> inflaterProvider = new ThreadLocal<Inflater>() {
@Override
protected Inflater initialValue() {
return new Inflater();
}
};
private static final ThreadLocal<Deflater> deflaterProvider = new ThreadLocal<Deflater>() {
@Override
protected Deflater initialValue() {
return new Deflater();
}
};
/*
* static auxiliary buffer. any method that uses this should synchronize against this
*/
private static byte[] tmpbuffer = new byte[4096];
/**
* Converts to bytes using Latin1 (ISO-8859-1)
*/
public static byte[] toBytes(final String x) {
return x.getBytes(PngHelperInternal.charsetLatin1);
}
/**
* Converts to String using Latin1 (ISO-8859-1)
*/
public static String toString(final byte[] x) {
return new String(x, PngHelperInternal.charsetLatin1);
}
/**
* Converts to String using Latin1 (ISO-8859-1)
*/
public static String toString(final byte[] x, final int offset, final int len) {
return new String(x, offset, len, PngHelperInternal.charsetLatin1);
}
/**
* Converts to bytes using UTF-8
*/
public static byte[] toBytesUTF8(final String x) {
return x.getBytes(PngHelperInternal.charsetUTF8);
}
/**
* Converts to string using UTF-8
*/
public static String toStringUTF8(final byte[] x) {
return new String(x, PngHelperInternal.charsetUTF8);
}
/**
* Converts to string using UTF-8
*/
public static String toStringUTF8(final byte[] x, final int offset, final int len) {
return new String(x, offset, len, PngHelperInternal.charsetUTF8);
}
/**
* critical chunk : first letter is uppercase
*/
public static boolean isCritical(final String id) {
return (Character.isUpperCase(id.charAt(0)));
}
/**
* public chunk: second letter is uppercase
*/
public static boolean isPublic(final String id) { //
return (Character.isUpperCase(id.charAt(1)));
}
/**
* Safe to copy chunk: fourth letter is lower case
*/
public static boolean isSafeToCopy(final String id) {
return (!Character.isUpperCase(id.charAt(3)));
}
/**
* "Unknown" just means that our chunk factory (even when it has been
* augmented by client code) did not recognize its id
*/
public static boolean isUnknown(final PngChunk c) {
return c instanceof PngChunkUNKNOWN;
}
/**
* Finds position of null byte in array
*
* @param b
* @return -1 if not found
*/
public static int posNullByte(final byte[] b) {
for (int i = 0; i < b.length; i++)
if (b[i] == 0)
return i;
return -1;
}
/**
* Decides if a chunk should be loaded, according to a ChunkLoadBehaviour
*
* @param id
* @param behav
* @return true/false
*/
public static boolean shouldLoad(final String id, final ChunkLoadBehaviour behav) {
if (isCritical(id))
return true;
final boolean kwown = PngChunk.isKnown(id);
switch (behav) {
case LOAD_CHUNK_ALWAYS:
return true;
case LOAD_CHUNK_IF_SAFE:
return kwown || isSafeToCopy(id);
case LOAD_CHUNK_KNOWN:
return kwown;
case LOAD_CHUNK_NEVER:
return false;
}
return false; // should not reach here
}
public final static byte[] compressBytes(final byte[] ori, final boolean compress) {
return compressBytes(ori, 0, ori.length, compress);
}
public static byte[] compressBytes(final byte[] ori, final int offset, final int len, final boolean compress) {
try {
final ByteArrayInputStream inb = new ByteArrayInputStream(ori, offset, len);
final InputStream in = compress ? inb : new InflaterInputStream(inb, getInflater());
final ByteArrayOutputStream outb = new ByteArrayOutputStream();
final OutputStream out = compress ? new DeflaterOutputStream(outb) : outb;
shovelInToOut(in, out);
in.close();
out.close();
return outb.toByteArray();
} catch (final Exception e) {
throw new PngjException(e);
}
}
/**
* Shovels all data from an input stream to an output stream.
*/
private static void shovelInToOut(final InputStream in, final OutputStream out) throws IOException {
synchronized (tmpbuffer) {
int len;
while ((len = in.read(tmpbuffer)) > 0) {
out.write(tmpbuffer, 0, len);
}
}
}
public static boolean maskMatch(final int v, final int mask) {
return (v & mask) != 0;
}
/**
* Returns only the chunks that "match" the predicate
*
* See also trimList()
*/
public static List<PngChunk> filterList(final List<PngChunk> target, final ChunkPredicate predicateKeep) {
final List<PngChunk> result = new ArrayList<PngChunk>();
for (final PngChunk element : target) {
if (predicateKeep.match(element)) {
result.add(element);
}
}
return result;
}
/**
* Remove (in place) the chunks that "match" the predicate
*
* See also filterList
*/
public static int trimList(final List<PngChunk> target, final ChunkPredicate predicateRemove) {
final Iterator<PngChunk> it = target.iterator();
int cont = 0;
while (it.hasNext()) {
final PngChunk c = it.next();
if (predicateRemove.match(c)) {
it.remove();
cont++;
}
}
return cont;
}
/**
* MY adhoc criteria: two chunks are "equivalent" ("practically equal") if
* they have same id and (perhaps, if multiple are allowed) if the match
* also in some "internal key" (eg: key for string values, palette for sPLT,
* etc)
*
* Notice that the use of this is optional, and that the PNG standard allows
* Text chunks that have same key
*
* @return true if "equivalent"
*/
public static final boolean equivalent(final PngChunk c1, final PngChunk c2) {
if (c1 == c2)
return true;
if (c1 == null || c2 == null || !c1.id.equals(c2.id))
return false;
// same id
if (c1.getClass() != c2.getClass())
return false; // should not happen
if (!c2.allowsMultiple())
return true;
if (c1 instanceof PngChunkTextVar) {
return ((PngChunkTextVar) c1).getKey().equals(((PngChunkTextVar) c2).getKey());
}
if (c1 instanceof PngChunkSPLT) {
return ((PngChunkSPLT) c1).getPalName().equals(((PngChunkSPLT) c2).getPalName());
}
// unknown chunks that allow multiple? consider they don't match
return false;
}
public static boolean isText(final PngChunk c) {
return c instanceof PngChunkTextVar;
}
/**
* thread-local inflater, just reset : this should be only used for short
* individual chunks compression
*/
public static Inflater getInflater() {
final Inflater inflater = inflaterProvider.get();
inflater.reset();
return inflater;
}
/**
* thread-local deflater, just reset : this should be only used for short
* individual chunks decompression
*/
public static Deflater getDeflater() {
final Deflater deflater = deflaterProvider.get();
deflater.reset();
return deflater;
}
}