/******************************************************************************* * Copyright (c) 2005-2011, G. Weirich and Elexis * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * G. Weirich - initial implementation * *******************************************************************************/ package ch.rgw.compress; /** * Huffmann tree with several creation and persistency options. * @author Gerry */ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.prefs.Preferences; import ch.rgw.tools.BinConverter; import ch.rgw.tools.ExHandler; import ch.rgw.tools.IntTool; import ch.rgw.tools.StringTool; public class HuffmanTree { public static String Version(){ return "1.0.2"; } static final int TABLESIZE = 256; public int[] freq; Node root; public HuffmanTree(){ root = null; } /** * create a Huffman Tree from a given source. Counts the frequency of each byte on the source * and constructs the tree accordingly * * @param source * an arbitrary sequence of bytes. */ public HuffmanTree(byte[] source){ build(constructTable(source)); } /** * create a Huffman Tree from a frequency table (which must be an Integer Array of exactly 255 * elements) * * @param frequencyTable */ public HuffmanTree(int[] frequencyTable){ build(frequencyTable); } /** * Build a tree from a frequency table. Ensures that escape and eof are represented in the tree. * * @param table * a field of TABLESIZE ints indicating the frequency od each byte * @return the root node of the newly created tree */ @SuppressWarnings("unchecked") public Node build(int[] table){ root = null; if ((table == null) || (table.length != TABLESIZE)) { return null; } freq = table; if (freq[Huff.escape] == 0) freq[Huff.escape] = 1; if (freq[Huff.eof] == 0) freq[Huff.eof] = 1; List<Node> nodes = new ArrayList<Node>(freq.length); for (int i = 0; i < freq.length; i++) { if (freq[i] == 0) continue; nodes.add(new Node((byte) i, freq[i])); } Collections.sort(nodes); while (nodes.size() > 1) { Node a = (Node) nodes.remove(0); Node b = (Node) nodes.remove(0); int al = a.ch.length; byte[] bn = new byte[al + b.ch.length]; for (int i = 0; i < al; i++) { bn[i] = a.ch[i]; } for (int i = 0; i < b.ch.length; i++) { bn[i + al] = b.ch[i]; } Arrays.sort(bn); Node n = new Node(bn, a.lfreq + b.lfreq); n.left = a; n.right = b; nodes.add(n); Collections.sort(nodes); } root = (Node) nodes.get(0); return root; } /** * constructs a frequency table from an array of bytes * * @param source * the array to construct the table from */ public static int[] constructTable(byte[] source){ int[] ret = new int[TABLESIZE]; for (int i = 0; i < source.length; i++) { ret[IntTool.ByteToInt(source[i])]++; } return ret; } /** constructs a frequency table from a file */ public static int[] constructTable(RandomAccessFile file){ int[] ret = new int[TABLESIZE]; try { file.seek(0L); long l = file.length(); for (long i = 0; i < l; i++) { int c = file.read(); ret[c]++; } return ret; } catch (Exception ex) { ExHandler.handle(ex); return null; } } /** * Construct a frequency table from an InputStream. This will create a temporary file to copy * the InputStream in. * * @param source * the Input Stream * @return an InputStream which is a copy of the source Stream, provided to re-Read the same * Bytes for the actual compression process. */ public InputStream constructTable(InputStream source, boolean copy){ freq = new int[TABLESIZE]; try { if (copy) { File file = File.createTempFile("huf", "tmp"); file.deleteOnExit(); FileOutputStream fos = new FileOutputStream(file); while (source.available() != 0) { int c = source.read(); freq[c]++; fos.write(c); } source.close(); fos.close(); return new FileInputStream(file); } else { while (source.available() != 0) { int c = source.read(); freq[c]++; } source.close(); return null; } } catch (IOException ex) { ExHandler.handle(ex); return null; } } /** * Creates a compacted form of the actual frequency table and saves it into an OutputStream. * * @return true on success */ public boolean saveTable(OutputStream out){ byte[] tbl = compactTable(freq); try { short l = (short) tbl.length; out.write(l & 0xff); out.write(l >> 8); for (int i = 0; i < tbl.length; i++) { out.write(tbl[i]); } return true; } catch (Exception ex) { ExHandler.handle(ex); return false; } } /** * Reloads a frequency table as saved by saveTable * * @return the table */ public static int[] loadTable(InputStream in){ try { int l = in.read(); l |= (in.read() << 8); byte[] tbl = new byte[l]; for (int i = 0; i < tbl.length; i++) { tbl[i] = (byte) in.read(); } return expandTable(tbl); } catch (Exception ex) { ExHandler.handle(ex); return null; } } /** * imports a compacted predefined Table * * @param in * @return */ public static int[] useTable(byte[] in){ return expandTable(in); } /** * Import a compaced frequency table from the system preferences (In windows from the registry * HKLM/JavaSoft/Prefs/ch/rgw/tools/Compress/StandardTables, in Linux from ~/.prefs) * * @param name * Name of the table in the registry. If no table with this name is found, the * default table (TextDeutsch) is returned. * @return the table */ public static int[] useStandardTable(String name){ Preferences pr = Preferences.userNodeForPackage(Huff.class); Preferences node = pr.node("StandardTables"); byte[] res = node.getByteArray(name, TextDeutsch); return HuffmanTree.expandTable(res); } /* * public static byte[] makeProportional(int[] table) { int max=0; for(int * i=0;i<table.length;i++) { if(table[i]==0) continue; if(table[i]>max) max=table[i]; } //double * min=max*0.1; //max-=min; double prop=127.00/max; byte[] res=new byte[255]; for(int * i=0;i<256;i++) { if(table[i]==0) continue; double proc=table[i]*prop; long * red=Math.round(proc); if(red==0) red=1; if(red>255) red=255; res[i]=(byte)(red); } return * res; * * } */ /** * compute a frequency table from an InputStream and save a compacted representation of this * table in the system preferences. (To be used later by @see #useStandardTable(String) ) * * @param name * name to give the table. * @param in * InputStream * @return true on success * @throws Exception */ public static boolean CreateStandardTableFromStream(String name, InputStream in) throws Exception{ int[] tbl = new int[TABLESIZE]; while (in.available() != 0) { int c = in.read(); tbl[c]++; } byte[] dest = HuffmanTree.compactTable(tbl); Preferences pref = Preferences.userNodeForPackage(Huff.class); Preferences node = pref.node("StandardTables"); node.putByteArray(name, dest); pref.flush(); return true; } public Node getRootNode(){ return root; } public int[] getTable(){ return freq; } public static void dumpTable(int[] table){ for (int i = 0; i < table.length; i++) { if (table[i] == 0) continue; System.out.println(i + " : " + table[i]); } } public static boolean checkCompacter(){ int[] t1 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 12345678, 9876, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 267 }; byte[] t2 = compactTable(t1); int[] t3 = expandTable(t2); if (t1.length != t3.length) return false; for (int i = 0; i < t1.length; i++) { if (t1[i] != t3[i]) return false; } return true; } private static byte[] compactTable(final int[] table){ byte[] ret = new byte[4 * table.length]; for (int i = 0, off = 0; i < table.length; i++, off += 4) { BinConverter.intToByteArray(table[i], ret, off); } return RLL.compress((byte) 0, ret); } private static int[] expandTable(final byte[] source){ byte[] t = RLL.expand(source); int[] ret = new int[t.length >> 2]; for (int i = 0, off = 0; i < ret.length; i++, off += 4) { ret[i] = BinConverter.byteArrayToInt(t, off); } return ret; } class Node implements Comparable { Node left, right; byte[] ch; int lfreq; Node(byte x, int f){ ch = new byte[] { x }; lfreq = f; } Node(byte[] x, int f){ ch = x; lfreq = f; } public int compareTo(Object arg0){ Node other = (Node) arg0; if (lfreq > other.lfreq) return 1; else if (lfreq > other.lfreq) return -1; return 0; } @Override public boolean equals(Object o){ if (o instanceof Node) { Node other = (Node) o; if (StringTool.compare(ch, other.ch) == true) { if (lfreq == other.lfreq) { return true; } } } return false; } } /** Standard table for German text */ public static byte[] TextDeutsch = new byte[] { 7, 7, 36, 0, 61, 7, 3, 0, 9, 3, 7, 10, 0, -12, 2, 7, 50, 0, 1, 7, 23, 0, -23, 36, 0, 0, 5, 7, 3, 0, -18, 7, 3, 0, 1, 7, 7, 0, 35, 7, 3, 0, 43, 7, 3, 0, 12, 7, 3, 0, -123, 7, 3, 0, -119, 7, 11, 0, 51, 2, 0, 0, -2, 7, 3, 0, 71, 3, 0, 0, -8, 7, 3, 0, 48, 1, 0, 0, -82, 7, 3, 0, -87, 7, 3, 0, 79, 7, 3, 0, 58, 7, 3, 0, 73, 7, 3, 0, 59, 7, 3, 0, 56, 7, 3, 0, 45, 7, 3, 0, 36, 7, 3, 0, -5, 1, 0, 0, 35, 7, 3, 0, 38, 2, 0, 0, 117, 7, 3, 0, 38, 2, 0, 0, 13, 7, 3, 0, 25, 7, 3, 0, -64, 1, 0, 0, -42, 7, 3, 0, -66, 7, 3, 0, 9, 1, 0, 0, 62, 1, 0, 0, -36, 7, 3, 0, -24, 7, 3, 0, -61, 7, 3, 0, 41, 1, 0, 0, 36, 7, 3, 0, -57, 7, 3, 0, -109, 7, 3, 0, 126, 1, 0, 0, -110, 7, 3, 0, 59, 7, 3, 0, 65, 1, 0, 0, 25, 7, 3, 0, -77, 7, 3, 0, 85, 2, 0, 0, -61, 7, 3, 0, 84, 7, 3, 0, -112, 7, 3, 0, -84, 7, 3, 0, 40, 7, 3, 0, 15, 7, 3, 0, 88, 7, 19, 0, 23, 1, 7, 6, 0, -61, 12, 0, 0, 21, 4, 0, 0, 70, 6, 0, 0, 33, 10, 0, 0, -41, 37, 0, 0, -39, 3, 0, 0, -16, 6, 0, 0, -87, 9, 0, 0, 116, 19, 0, 0, 81, 7, 3, 0, 23, 3, 0, 0, 117, 9, 0, 0, -55, 5, 0, 0, 45, 24, 0, 0, -53, 5, 0, 0, 72, 2, 0, 0, 28, 7, 3, 0, 49, 17, 0, 0, 85, 14, 0, 0, 17, 15, 0, 0, 84, 8, 0, 0, -81, 1, 0, 0, 49, 3, 0, 0, -81, 7, 3, 0, 94, 7, 3, 0, -87, 2, 0, 0, 1, 7, 7, 0, 1, 7, 27, 0, 8, 7, 51, 0, 1, 7, 3, 0, 4, 7, 3, 0, 8, 7, 11, 0, 1, 7, 91, 0, 4, 7, 91, 0, 11, 7, 71, 0, 8, 7, 23, 0, 28, 7, 31, 0, 54, 1, 7, 70, 0, -37, 7, 23, 0, 110, 1, 7, 14, 0 }; }