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. *

* 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 filterList(List target, ChunkPredicate predicateKeep) { List result = new ArrayList(); 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 target, ChunkPredicate predicateRemove) { Iterator 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; } }