WarpPI/desktop/src/main/java/ar/com/hjg/pngj/chunks/ChunkHelper.java

297 lines
7.9 KiB
Java

package ar.com.hjg.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.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import ar.com.hjg.pngj.PngHelperInternal;
import ar.com.hjg.pngj.PngjException;
/**
* Helper methods and constants related to Chunk processing.
* <p>
* This should only be of interest to developers doing special chunk processing
* or extending the ChunkFactory
*/
public class ChunkHelper {
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 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";
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);
/*
* 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(String x) {
try {
return x.getBytes(PngHelperInternal.charsetLatin1name);
} catch (UnsupportedEncodingException e) {
throw new PngBadCharsetException(e);
}
}
/**
* Converts to String using Latin1 (ISO-8859-1)
*/
public static String toString(byte[] x) {
try {
return new String(x, PngHelperInternal.charsetLatin1name);
} catch (UnsupportedEncodingException e) {
throw new PngBadCharsetException(e);
}
}
/**
* Converts to String using Latin1 (ISO-8859-1)
*/
public static String toString(byte[] x, int offset, int len) {
try {
return new String(x, offset, len, PngHelperInternal.charsetLatin1name);
} catch (UnsupportedEncodingException e) {
throw new PngBadCharsetException(e);
}
}
/**
* Converts to bytes using UTF-8
*/
public static byte[] toBytesUTF8(String x) {
try {
return x.getBytes(PngHelperInternal.charsetUTF8name);
} catch (UnsupportedEncodingException e) {
throw new PngBadCharsetException(e);
}
}
/**
* Converts to string using UTF-8
*/
public static String toStringUTF8(byte[] x) {
try {
return new String(x, PngHelperInternal.charsetUTF8name);
} catch (UnsupportedEncodingException e) {
throw new PngBadCharsetException(e);
}
}
/**
* Converts to string using UTF-8
*/
public static String toStringUTF8(byte[] x, int offset, int len) {
try {
return new String(x, offset, len, PngHelperInternal.charsetUTF8name);
} catch (UnsupportedEncodingException e) {
throw new PngBadCharsetException(e);
}
}
/**
* critical chunk : first letter is uppercase
*/
public static boolean isCritical(String id) {
return (Character.isUpperCase(id.charAt(0)));
}
/**
* public chunk: second letter is uppercase
*/
public static boolean isPublic(String id) { //
return (Character.isUpperCase(id.charAt(1)));
}
/**
* Safe to copy chunk: fourth letter is lower case
*/
public static boolean isSafeToCopy(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(PngChunk c) {
return c instanceof PngChunkUNKNOWN;
}
/**
* Finds position of null byte in array
*
* @param b
* @return -1 if not found
*/
public static int posNullByte(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(String id, ChunkLoadBehaviour behav) {
if (isCritical(id))
return true;
switch (behav) {
case LOAD_CHUNK_ALWAYS:
return true;
case LOAD_CHUNK_IF_SAFE:
return isSafeToCopy(id);
case LOAD_CHUNK_NEVER:
return false;
case LOAD_CHUNK_MOST_IMPORTANT:
return id.equals(PngChunkTRNS.ID);
}
return false; // should not reach here
}
public final static byte[] compressBytes(byte[] ori, boolean compress) {
return compressBytes(ori, 0, ori.length, compress);
}
public static byte[] compressBytes(byte[] ori, int offset, int len, boolean compress) {
try {
ByteArrayInputStream inb = new ByteArrayInputStream(ori, offset, len);
InputStream in = compress ? inb : new InflaterInputStream(inb);
ByteArrayOutputStream outb = new ByteArrayOutputStream();
OutputStream out = compress ? new DeflaterOutputStream(outb) : outb;
shovelInToOut(in, out);
in.close();
out.close();
return outb.toByteArray();
} catch (Exception e) {
throw new PngjException(e);
}
}
/**
* Shovels all data from an input stream to an output stream.
*/
private static void shovelInToOut(InputStream in, OutputStream out) throws IOException {
synchronized (tmpbuffer) {
int len;
while ((len = in.read(tmpbuffer)) > 0) {
out.write(tmpbuffer, 0, len);
}
}
}
/**
* Returns only the chunks that "match" the predicate
*
* See also trimList()
*/
public static List<PngChunk> filterList(List<PngChunk> target, ChunkPredicate predicateKeep) {
List<PngChunk> result = new ArrayList<PngChunk>();
for (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(List<PngChunk> target, ChunkPredicate predicateRemove) {
Iterator<PngChunk> it = target.iterator();
int cont = 0;
while (it.hasNext()) {
PngChunk c = it.next();
if (predicateRemove.match(c)) {
it.remove();
cont++;
}
}
return cont;
}
/**
* Adhoc criteria: two ancillary chunks are "equivalent" ("practically same
* type") 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)
*
* When we use this method, we implicitly assume that we don't allow/expect
* two "equivalent" chunks in a single PNG
*
* Notice that the use of this is optional, and that the PNG standard
* actually allows text chunks that have same key
*
* @return true if "equivalent"
*/
public static final boolean equivalent(PngChunk c1, PngChunk c2) {
if (c1 == c2)
return true;
if (c1 == null || c2 == null || !c1.id.equals(c2.id))
return false;
if (c1.crit)
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(PngChunk c) {
return c instanceof PngChunkTextVar;
}
}