You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by le...@apache.org on 2017/10/16 18:09:16 UTC

[3/8] pdfbox-jbig2 git commit: initial commit of the JBig2 ImageIO plugin

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/segments/Table.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/Table.java b/src/main/java/org/apache/pdfbox/jbig2/segments/Table.java
new file mode 100644
index 0000000..43924e2
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/Table.java
@@ -0,0 +1,97 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.jbig2.SegmentData;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+
+/**
+ * This class represents a "Table" segment. It handles custom tables, see Annex B.
+ */
+public class Table implements SegmentData {
+
+  private SubInputStream subInputStream;
+
+  /** Code table flags, B.2.1, page 87 */
+  private int htOutOfBand;
+  private int htPS;
+  private int htRS;
+
+  /** Code table lowest value, B.2.2, page 87 */
+  private int htLow;
+
+  /** Code table highest value, B.2.3, page 87 */
+  private int htHigh;
+
+  private void parseHeader() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    int bit;
+
+    /* Bit 7 */
+    if ((bit = subInputStream.readBit()) == 1) {
+      throw new InvalidHeaderValueException("B.2.1 Code table flags: Bit 7 must be zero, but was " + bit);
+    }
+
+    /* Bit 4-6 */
+    htRS = (int) ((subInputStream.readBits(3) + 1) & 0xf);
+
+    /* Bit 1-3 */
+    htPS = (int) ((subInputStream.readBits(3) + 1) & 0xf);
+
+    /* Bit 0 */
+    htOutOfBand = (int) subInputStream.readBit();
+
+    htLow = (int) subInputStream.readBits(32); // & 0xffffffff);
+    htHigh = (int) subInputStream.readBits(32); // & 0xffffffff);
+  }
+
+  public void init(SegmentHeader header, SubInputStream sis) throws InvalidHeaderValueException, IOException,
+      IntegerMaxValueException {
+    subInputStream = sis;
+
+    parseHeader();
+  }
+
+  public int getHtOOB() {
+    return htOutOfBand;
+  }
+
+  public int getHtPS() {
+    return htPS;
+  }
+
+  public int getHtRS() {
+    return htRS;
+  }
+
+  public int getHtLow() {
+    return htLow;
+  }
+
+  public int getHtHigh() {
+    return htHigh;
+  }
+
+  public SubInputStream getSubInputStream() {
+    return subInputStream;
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/segments/TextRegion.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/TextRegion.java b/src/main/java/org/apache/pdfbox/jbig2/segments/TextRegion.java
new file mode 100644
index 0000000..797729c
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/TextRegion.java
@@ -0,0 +1,973 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2ImageReader;
+import org.apache.pdfbox.jbig2.Region;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticIntegerDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.CX;
+import org.apache.pdfbox.jbig2.decoder.huffman.EncodedTable;
+import org.apache.pdfbox.jbig2.decoder.huffman.FixedSizeTable;
+import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable;
+import org.apache.pdfbox.jbig2.decoder.huffman.StandardTables;
+import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable.Code;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represented the segment type "Text region", 7.4.3, page 56.
+ */
+public class TextRegion implements Region {
+
+  private final Logger log = LoggerFactory.getLogger(TextRegion.class);
+
+  private SubInputStream subInputStream;
+
+  /** Region segment information field, 7.4.1 */
+  private RegionSegmentInformation regionInfo;
+
+  /** Text region segment flags, 7.4.3.1.1 */
+  private short sbrTemplate;
+  private short sbdsOffset; /* 6.4.8 */
+  private short defaultPixel;
+  private CombinationOperator combinationOperator;
+  private short isTransposed;
+  private short referenceCorner;
+  private short logSBStrips;
+  private boolean useRefinement;
+  private boolean isHuffmanEncoded;
+
+  /** Text region segment Huffman flags, 7.4.3.1.2 */
+  private short sbHuffRSize;
+  private short sbHuffRDY;
+  private short sbHuffRDX;
+  private short sbHuffRDHeight;
+  private short sbHuffRDWidth;
+  private short sbHuffDT;
+  private short sbHuffDS;
+  private short sbHuffFS;
+
+  /** Text region refinement AT flags, 7.4.3.1.3 */
+  private short sbrATX[];
+  private short sbrATY[];
+
+  /** Number of symbol instances, 7.4.3.1.4 */
+  private long amountOfSymbolInstances;
+
+  /** Further parameters */
+  private long currentS;
+  private int sbStrips;
+  private int amountOfSymbols;
+
+  private Bitmap regionBitmap;
+  private ArrayList<Bitmap> symbols = new ArrayList<Bitmap>();
+
+  private ArithmeticDecoder arithmeticDecoder;
+  private ArithmeticIntegerDecoder integerDecoder;
+  private GenericRefinementRegion genericRefinementRegion;
+
+  private CX cxIADT;
+  private CX cxIAFS;
+  private CX cxIADS;
+  private CX cxIAIT;
+  private CX cxIARI;
+  private CX cxIARDW;
+  private CX cxIARDH;
+  private CX cxIAID;
+  private CX cxIARDX;
+  private CX cxIARDY;
+  private CX cx;
+
+  /** codeTable including a code to each symbol used in that region */
+  private int symbolCodeLength;
+  private FixedSizeTable symbolCodeTable;
+  private SegmentHeader segmentHeader;
+
+  /** User-supplied tables * */
+  private HuffmanTable fsTable;
+  private HuffmanTable dsTable;
+  private HuffmanTable table;
+  private HuffmanTable rdwTable;
+  private HuffmanTable rdhTable;
+  private HuffmanTable rdxTable;
+  private HuffmanTable rdyTable;
+  private HuffmanTable rSizeTable;
+
+  public TextRegion() {
+  }
+
+  public TextRegion(SubInputStream subInputStream, SegmentHeader segmentHeader) {
+    this.subInputStream = subInputStream;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+    this.segmentHeader = segmentHeader;
+  }
+
+  private void parseHeader() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+
+    regionInfo.parseHeader();
+
+    readRegionFlags();
+
+    if (isHuffmanEncoded) {
+      readHuffmanFlags();
+    }
+
+    readUseRefinement();
+
+    readAmountOfSymbolInstances();
+
+    /* 7.4.3.1.7 */
+    getSymbols();
+
+    computeSymbolCodeLength();
+
+    this.checkInput();
+  }
+
+  private void readRegionFlags() throws IOException {
+    /* Bit 15 */
+    sbrTemplate = (short) subInputStream.readBit();
+
+    /* Bit 10-14 */
+    sbdsOffset = (short) (subInputStream.readBits(5));
+    if (sbdsOffset > 0x0f) {
+      sbdsOffset -= 0x20;
+    }
+
+    /* Bit 9 */
+    defaultPixel = (short) subInputStream.readBit();
+
+    /* Bit 7-8 */
+    combinationOperator = CombinationOperator.translateOperatorCodeToEnum((short) (subInputStream.readBits(2) & 0x3));
+
+    /* Bit 6 */
+    isTransposed = (short) subInputStream.readBit();
+
+    /* Bit 4-5 */
+    referenceCorner = (short) (subInputStream.readBits(2) & 0x3);
+
+    /* Bit 2-3 */
+    logSBStrips = (short) (subInputStream.readBits(2) & 0x3);
+    sbStrips = (1 << logSBStrips);
+
+    /* Bit 1 */
+    if (subInputStream.readBit() == 1) {
+      useRefinement = true;
+    }
+
+    /* Bit 0 */
+    if (subInputStream.readBit() == 1) {
+      isHuffmanEncoded = true;
+    }
+  }
+
+  private void readHuffmanFlags() throws IOException {
+    /* Bit 15 */
+    subInputStream.readBit(); // Dirty read...
+
+    /* Bit 14 */
+    sbHuffRSize = (short) subInputStream.readBit();
+
+    /* Bit 12-13 */
+    sbHuffRDY = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 10-11 */
+    sbHuffRDX = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 8-9 */
+    sbHuffRDHeight = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 6-7 */
+    sbHuffRDWidth = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 4-5 */
+    sbHuffDT = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 2-3 */
+    sbHuffDS = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 0-1 */
+    sbHuffFS = (short) (subInputStream.readBits(2) & 0xf);
+  }
+
+  private void readUseRefinement() throws IOException {
+    if (useRefinement && sbrTemplate == 0) {
+      sbrATX = new short[2];
+      sbrATY = new short[2];
+
+      /* Byte 0 */
+      sbrATX[0] = subInputStream.readByte();
+
+      /* Byte 1 */
+      sbrATY[0] = subInputStream.readByte();
+
+      /* Byte 2 */
+      sbrATX[1] = subInputStream.readByte();
+
+      /* Byte 3 */
+      sbrATY[1] = subInputStream.readByte();
+    }
+  }
+
+  private void readAmountOfSymbolInstances() throws IOException {
+    amountOfSymbolInstances = subInputStream.readBits(32) & 0xffffffff;
+  }
+
+  private void getSymbols() throws IOException, IntegerMaxValueException, InvalidHeaderValueException {
+    if (segmentHeader.getRtSegments() != null) {
+      initSymbols();
+    }
+  }
+
+  private void computeSymbolCodeLength() throws IOException {
+    if (isHuffmanEncoded) {
+      symbolIDCodeLengths();
+    } else {
+      symbolCodeLength = (int) Math.ceil((Math.log(amountOfSymbols) / Math.log(2)));
+    }
+  }
+
+  private void checkInput() throws InvalidHeaderValueException {
+
+    if (!useRefinement) {
+      if (sbrTemplate != 0) {
+        log.info("sbrTemplate should be 0");
+        sbrTemplate = 0;
+      }
+    }
+
+    if (sbHuffFS == 2 || sbHuffRDWidth == 2 || sbHuffRDHeight == 2 || sbHuffRDX == 2 || sbHuffRDY == 2) {
+      throw new InvalidHeaderValueException("Huffman flag value of text region segment is not permitted");
+    }
+
+    if (!useRefinement) {
+      if (sbHuffRSize != 0) {
+        log.info("sbHuffRSize should be 0");
+        sbHuffRSize = 0;
+      }
+      if (sbHuffRDY != 0) {
+        log.info("sbHuffRDY should be 0");
+        sbHuffRDY = 0;
+      }
+      if (sbHuffRDX != 0) {
+        log.info("sbHuffRDX should be 0");
+        sbHuffRDX = 0;
+      }
+      if (sbHuffRDWidth != 0) {
+        log.info("sbHuffRDWidth should be 0");
+        sbHuffRDWidth = 0;
+      }
+      if (sbHuffRDHeight != 0) {
+        log.info("sbHuffRDHeight should be 0");
+        sbHuffRDHeight = 0;
+      }
+    }
+  }
+
+  public Bitmap getRegionBitmap() throws IOException, IntegerMaxValueException, InvalidHeaderValueException {
+
+    if (!isHuffmanEncoded) {
+      setCodingStatistics();
+    }
+
+    createRegionBitmap();
+    decodeSymbolInstances();
+
+    /* 4) */
+    return regionBitmap;
+  }
+
+  private void setCodingStatistics() throws IOException {
+    if (cxIADT == null)
+      cxIADT = new CX(512, 1);
+
+    if (cxIAFS == null)
+      cxIAFS = new CX(512, 1);
+
+    if (cxIADS == null)
+      cxIADS = new CX(512, 1);
+
+    if (cxIAIT == null)
+      cxIAIT = new CX(512, 1);
+
+    if (cxIARI == null)
+      cxIARI = new CX(512, 1);
+
+    if (cxIARDW == null)
+      cxIARDW = new CX(512, 1);
+
+    if (cxIARDH == null)
+      cxIARDH = new CX(512, 1);
+
+    if (cxIAID == null)
+      cxIAID = new CX(1 << symbolCodeLength, 1);
+
+    if (cxIARDX == null)
+      cxIARDX = new CX(512, 1);
+
+    if (cxIARDY == null)
+      cxIARDY = new CX(512, 1);
+
+    if (arithmeticDecoder == null)
+      arithmeticDecoder = new ArithmeticDecoder(subInputStream);
+
+    if (integerDecoder == null)
+      integerDecoder = new ArithmeticIntegerDecoder(arithmeticDecoder);
+  }
+
+  private void createRegionBitmap() {
+
+    /* 6.4.5 */
+    final int width = regionInfo.getBitmapWidth();
+    final int height = regionInfo.getBitmapHeight();
+    regionBitmap = new Bitmap(width, height);
+
+    /* 1) */
+    if (defaultPixel != 0) {
+      Arrays.fill(regionBitmap.getByteArray(), (byte) 0xff);
+    }
+  }
+
+  private final long decodeStripT() throws IOException, InvalidHeaderValueException {
+
+    long stripT = 0;
+    /* 2) */
+    if (isHuffmanEncoded) {
+      /* 6.4.6 */
+      if (sbHuffDT == 3) {
+        // TODO test user-specified table
+        if (table == null) {
+          int dtNr = 0;
+
+          if (sbHuffFS == 3) {
+            dtNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            dtNr++;
+          }
+
+          table = getUserTable(dtNr);
+        }
+        stripT = table.decode(subInputStream);
+      } else {
+        stripT = StandardTables.getTable(11 + sbHuffDT).decode(subInputStream);
+      }
+    } else {
+      stripT = integerDecoder.decode(cxIADT);
+    }
+
+    return stripT * -(sbStrips);
+  }
+
+  private void decodeSymbolInstances() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+
+    long stripT = decodeStripT();
+
+    /* Last two sentences of 6.4.5 2) */
+    long firstS = 0;
+    int instanceCounter = 0;
+
+    /* 6.4.5 3 a) */
+    while (instanceCounter < amountOfSymbolInstances) {
+
+      final long dT = decodeDT();
+      stripT += dT;
+      long dfS = 0;
+
+      /* 3 c) symbol instances in the strip */
+      boolean first = true;
+      currentS = 0;
+
+      // do until OOB
+      for (;;) {
+        /* 3 c) i) - first symbol instance in the strip */
+        if (first) {
+          /* 6.4.7 */
+          dfS = decodeDfS();
+          firstS += dfS;
+          currentS = firstS;
+          first = false;
+          /* 3 c) ii) - the remaining symbol instances in the strip */
+        } else {
+          /* 6.4.8 */
+          final long idS = decodeIdS();
+          /*
+           * If result is OOB, then all the symbol instances in this strip have been decoded;
+           * proceed to step 3 d) respectively 3 b)
+           */
+          if (idS == Long.MAX_VALUE)
+            break;
+
+          currentS += (idS + sbdsOffset);
+        }
+
+        /* 3 c) iii) */
+        final long currentT = decodeCurrentT();
+        final long t = stripT + currentT;
+
+        /* 3 c) iv) */
+        final long id = decodeID();
+
+        /* 3 c) v) */
+        final long r = decodeRI();
+        /* 6.4.11 */
+        final Bitmap ib = decodeIb(r, id);
+
+        /* vi) */
+        blit(ib, t);
+
+        instanceCounter++;
+      }
+    }
+  }
+
+  private final long decodeDT() throws IOException {
+    /* 3) b) */
+    /* 6.4.6 */
+    long dT;
+    if (isHuffmanEncoded) {
+      if (sbHuffDT == 3) {
+        dT = table.decode(subInputStream);
+      } else {
+        dT = StandardTables.getTable(11 + sbHuffDT).decode(subInputStream);
+      }
+    } else {
+      dT = integerDecoder.decode(cxIADT);
+    }
+
+    return (dT * sbStrips);
+  }
+
+  private final long decodeDfS() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffFS == 3) {
+        if (fsTable == null) {
+          fsTable = getUserTable(0);
+        }
+        return fsTable.decode(subInputStream);
+      } else {
+        return StandardTables.getTable(6 + sbHuffFS).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIAFS);
+    }
+  }
+
+  private final long decodeIdS() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffDS == 3) {
+        // TODO test user-specified table
+        if (dsTable == null) {
+          int dsNr = 0;
+          if (sbHuffFS == 3) {
+            dsNr++;
+          }
+
+          dsTable = getUserTable(dsNr);
+        }
+        return dsTable.decode(subInputStream);
+
+      } else {
+        return StandardTables.getTable(8 + sbHuffDS).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIADS);
+    }
+  }
+
+  private final long decodeCurrentT() throws IOException {
+    if (sbStrips != 1) {
+      if (isHuffmanEncoded) {
+        return subInputStream.readBits(logSBStrips);
+      } else {
+        return integerDecoder.decode(cxIAIT);
+      }
+    }
+
+    return 0;
+  }
+
+  private final long decodeID() throws IOException {
+    if (isHuffmanEncoded) {
+      if (symbolCodeTable == null) {
+        return subInputStream.readBits(symbolCodeLength);
+      }
+
+      return symbolCodeTable.decode(subInputStream);
+    } else {
+      return integerDecoder.decodeIAID(cxIAID, symbolCodeLength);
+    }
+  }
+
+  private final long decodeRI() throws IOException {
+    if (useRefinement) {
+      if (isHuffmanEncoded) {
+        return subInputStream.readBit();
+      } else {
+        return integerDecoder.decode(cxIARI);
+      }
+    }
+    return 0;
+  }
+
+  private final Bitmap decodeIb(long r, long id) throws IOException, InvalidHeaderValueException,
+      IntegerMaxValueException {
+    Bitmap ib;
+
+    if (r == 0) {
+      ib = symbols.get((int) id);
+    } else {
+      /* 1) - 4) */
+      final long rdw = decodeRdw();
+      final long rdh = decodeRdh();
+      final long rdx = decodeRdx();
+      final long rdy = decodeRdy();
+
+      /* 5) */
+      /* long symInRefSize = 0; */
+      if (isHuffmanEncoded) {
+        /* symInRefSize = */decodeSymInRefSize();
+        subInputStream.skipBits();
+      }
+
+      /* 6) */
+      final Bitmap ibo = symbols.get((int) id);
+      final int wo = ibo.getWidth();
+      final int ho = ibo.getHeight();
+
+      final int genericRegionReferenceDX = (int) ((rdw >> 1) + rdx);
+      final int genericRegionReferenceDY = (int) ((rdh >> 1) + rdy);
+
+      if (genericRefinementRegion == null) {
+        genericRefinementRegion = new GenericRefinementRegion(subInputStream);
+      }
+
+      genericRefinementRegion.setParameters(cx, arithmeticDecoder, sbrTemplate, (int) (wo + rdw), (int) (ho + rdh),
+          ibo, genericRegionReferenceDX, genericRegionReferenceDY, false, sbrATX, sbrATY);
+
+      ib = genericRefinementRegion.getRegionBitmap();
+
+      /* 7 */
+      if (isHuffmanEncoded) {
+        subInputStream.skipBits();
+      }
+    }
+    return ib;
+  }
+
+  private final long decodeRdw() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffRDWidth == 3) {
+        // TODO test user-specified table
+        if (rdwTable == null) {
+          int rdwNr = 0;
+          if (sbHuffFS == 3) {
+            rdwNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            rdwNr++;
+          }
+
+          if (sbHuffDT == 3) {
+            rdwNr++;
+          }
+
+          rdwTable = getUserTable(rdwNr);
+        }
+        return rdwTable.decode(subInputStream);
+
+      } else {
+        return StandardTables.getTable(14 + sbHuffRDWidth).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIARDW);
+    }
+  }
+
+  private final long decodeRdh() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffRDHeight == 3) {
+        if (rdhTable == null) {
+          int rdhNr = 0;
+
+          if (sbHuffFS == 3) {
+            rdhNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            rdhNr++;
+          }
+
+          if (sbHuffDT == 3) {
+            rdhNr++;
+          }
+
+          if (sbHuffRDWidth == 3) {
+            rdhNr++;
+          }
+
+          rdhTable = getUserTable(rdhNr);
+        }
+        return rdhTable.decode(subInputStream);
+      } else {
+        return StandardTables.getTable(14 + sbHuffRDHeight).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIARDH);
+    }
+  }
+
+  private final long decodeRdx() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffRDX == 3) {
+        if (rdxTable == null) {
+          int rdxNr = 0;
+          if (sbHuffFS == 3) {
+            rdxNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            rdxNr++;
+          }
+
+          if (sbHuffDT == 3) {
+            rdxNr++;
+          }
+
+          if (sbHuffRDWidth == 3) {
+            rdxNr++;
+          }
+
+          if (sbHuffRDHeight == 3) {
+            rdxNr++;
+          }
+
+          rdxTable = getUserTable(rdxNr);
+        }
+        return rdxTable.decode(subInputStream);
+      } else {
+        return StandardTables.getTable(14 + sbHuffRDX).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIARDX);
+    }
+  }
+
+  private final long decodeRdy() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffRDY == 3) {
+        if (rdyTable == null) {
+          int rdyNr = 0;
+          if (sbHuffFS == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffDT == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffRDWidth == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffRDHeight == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffRDX == 3) {
+            rdyNr++;
+          }
+
+          rdyTable = getUserTable(rdyNr);
+        }
+        return rdyTable.decode(subInputStream);
+      } else {
+        return StandardTables.getTable(14 + sbHuffRDY).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIARDY);
+    }
+  }
+
+  private final long decodeSymInRefSize() throws IOException, InvalidHeaderValueException {
+    if (sbHuffRSize == 0) {
+      return StandardTables.getTable(1).decode(subInputStream);
+    } else {
+      if (rSizeTable == null) {
+        int rSizeNr = 0;
+
+        if (sbHuffFS == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffDS == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffDT == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffRDWidth == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffRDHeight == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffRDX == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffRDY == 3) {
+          rSizeNr++;
+        }
+
+        rSizeTable = getUserTable(rSizeNr);
+      }
+      return rSizeTable.decode(subInputStream);
+    }
+
+  }
+
+  private final void blit(Bitmap ib, long t) {
+    if (isTransposed == 0 && (referenceCorner == 2 || referenceCorner == 3)) {
+      currentS += ib.getWidth() - 1;
+    } else if (isTransposed == 1 && (referenceCorner == 0 || referenceCorner == 2)) {
+      currentS += ib.getHeight() - 1;
+    }
+
+    /* vii) */
+    long s = currentS;
+
+    /* viii) */
+    if (isTransposed == 1) {
+      final long swap = t;
+      t = s;
+      s = swap;
+    }
+
+    if (referenceCorner != 1) {
+      if (referenceCorner == 0) {
+        // BL
+        t -= ib.getHeight() - 1;
+      } else if (referenceCorner == 2) {
+        // BR
+        t -= ib.getHeight() - 1;
+        s -= ib.getWidth() - 1;
+      } else if (referenceCorner == 3) {
+        // TR
+        s -= ib.getWidth() - 1;
+      }
+    }
+
+    Bitmaps.blit(ib, regionBitmap, (int) s, (int) t, combinationOperator);
+
+    /* x) */
+    if (isTransposed == 0 && (referenceCorner == 0 || referenceCorner == 1)) {
+      currentS += ib.getWidth() - 1;
+    }
+
+    if (isTransposed == 1 && (referenceCorner == 1 || referenceCorner == 3)) {
+      currentS += ib.getHeight() - 1;
+    }
+
+  }
+
+  private void initSymbols() throws IOException, IntegerMaxValueException, InvalidHeaderValueException {
+    for (final SegmentHeader segment : segmentHeader.getRtSegments()) {
+      if (segment.getSegmentType() == 0) {
+        final SymbolDictionary sd = (SymbolDictionary) segment.getSegmentData();
+
+        sd.cxIAID = cxIAID;
+        symbols.addAll(sd.getDictionary());
+      }
+    }
+    amountOfSymbols = symbols.size();
+  }
+
+  private HuffmanTable getUserTable(final int tablePosition) throws InvalidHeaderValueException, IOException {
+    int tableCounter = 0;
+
+    for (final SegmentHeader referredToSegmentHeader : segmentHeader.getRtSegments()) {
+      if (referredToSegmentHeader.getSegmentType() == 53) {
+        if (tableCounter == tablePosition) {
+          final Table t = (Table) referredToSegmentHeader.getSegmentData();
+          return new EncodedTable(t);
+        } else {
+          tableCounter++;
+        }
+      }
+    }
+    return null;
+  }
+
+  private void symbolIDCodeLengths() throws IOException {
+    /* 1) - 2) */
+    final List<Code> runCodeTable = new ArrayList<Code>();
+
+    for (int i = 0; i < 35; i++) {
+      final int prefLen = (int) (subInputStream.readBits(4) & 0xf);
+      if (prefLen > 0) {
+        runCodeTable.add(new Code(prefLen, 0, i, false));
+      }
+    }
+
+    if (JBIG2ImageReader.DEBUG)
+      log.debug(HuffmanTable.codeTableToString(runCodeTable));
+
+    HuffmanTable ht = new FixedSizeTable(runCodeTable);
+
+    /* 3) - 5) */
+    long previousCodeLength = 0;
+
+    int counter = 0;
+    final List<Code> sbSymCodes = new ArrayList<Code>();
+    while (counter < amountOfSymbols) {
+      final long code = ht.decode(subInputStream);
+      if (code < 32) {
+        if (code > 0) {
+          sbSymCodes.add(new Code((int) code, 0, counter, false));
+        }
+
+        previousCodeLength = code;
+        counter++;
+      } else {
+
+        long runLength = 0;
+        long currCodeLength = 0;
+        if (code == 32) {
+          runLength = 3 + subInputStream.readBits(2);
+          if (counter > 0) {
+            currCodeLength = previousCodeLength;
+          }
+        } else if (code == 33) {
+          runLength = 3 + subInputStream.readBits(3);
+        } else if (code == 34) {
+          runLength = 11 + subInputStream.readBits(7);
+        }
+
+        for (int j = 0; j < runLength; j++) {
+          if (currCodeLength > 0) {
+            sbSymCodes.add(new Code((int) currCodeLength, 0, counter, false));
+          }
+          counter++;
+        }
+      }
+    }
+
+    /* 6) - Skip over remaining bits in the last Byte read */
+    subInputStream.skipBits();
+
+    /* 7) */
+    symbolCodeTable = new FixedSizeTable(sbSymCodes);
+
+  }
+
+  public void init(SegmentHeader header, SubInputStream sis) throws InvalidHeaderValueException,
+      IntegerMaxValueException, IOException {
+    this.segmentHeader = header;
+    this.subInputStream = sis;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+    parseHeader();
+  }
+
+  protected void setContexts(CX cx, CX cxIADT, CX cxIAFS, CX cxIADS, CX cxIAIT, CX cxIAID, CX cxIARDW, CX cxIARDH,
+      CX cxIARDX, CX cxIARDY) {
+    this.cx = cx;
+
+    this.cxIADT = cxIADT;
+    this.cxIAFS = cxIAFS;
+    this.cxIADS = cxIADS;
+    this.cxIAIT = cxIAIT;
+
+    this.cxIAID = cxIAID;
+
+    this.cxIARDW = cxIARDW;
+    this.cxIARDH = cxIARDH;
+    this.cxIARDX = cxIARDX;
+    this.cxIARDY = cxIARDY;
+  }
+
+  protected void setParameters(ArithmeticDecoder arithmeticDecoder, ArithmeticIntegerDecoder iDecoder,
+      boolean isHuffmanEncoded, boolean sbRefine, int sbw, int sbh, long sbNumInstances, int sbStrips, int sbNumSyms,
+      short sbDefaultPixel, short sbCombinationOperator, short transposed, short refCorner, short sbdsOffset,
+      short sbHuffFS, short sbHuffDS, short sbHuffDT, short sbHuffRDWidth, short sbHuffRDHeight, short sbHuffRDX,
+      short sbHuffRDY, short sbHuffRSize, short sbrTemplate, short sbrATX[], short sbrATY[], ArrayList<Bitmap> sbSyms,
+      int sbSymCodeLen) {
+
+    this.arithmeticDecoder = arithmeticDecoder;
+
+    this.integerDecoder = iDecoder;
+
+    this.isHuffmanEncoded = isHuffmanEncoded;
+    this.useRefinement = sbRefine;
+
+    this.regionInfo.setBitmapWidth(sbw);
+    this.regionInfo.setBitmapHeight(sbh);
+
+    this.amountOfSymbolInstances = sbNumInstances;
+    this.sbStrips = sbStrips;
+    this.amountOfSymbols = sbNumSyms;
+    this.defaultPixel = sbDefaultPixel;
+    this.combinationOperator = CombinationOperator.translateOperatorCodeToEnum(sbCombinationOperator);
+    this.isTransposed = transposed;
+    this.referenceCorner = refCorner;
+    this.sbdsOffset = sbdsOffset;
+
+    this.sbHuffFS = sbHuffFS;
+    this.sbHuffDS = sbHuffDS;
+    this.sbHuffDT = sbHuffDT;
+    this.sbHuffRDWidth = sbHuffRDWidth;
+    this.sbHuffRDHeight = sbHuffRDHeight;
+    this.sbHuffRDX = sbHuffRDX;
+    this.sbHuffRDY = sbHuffRDY;
+    this.sbHuffRSize = sbHuffRSize;
+
+    this.sbrTemplate = sbrTemplate;
+    this.sbrATX = sbrATX;
+    this.sbrATY = sbrATY;
+
+    this.symbols = sbSyms;
+    this.symbolCodeLength = sbSymCodeLen;
+  }
+
+  public RegionSegmentInformation getRegionInfo() {
+    return regionInfo;
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/CombinationOperator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/CombinationOperator.java b/src/main/java/org/apache/pdfbox/jbig2/util/CombinationOperator.java
new file mode 100644
index 0000000..1d40717
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/CombinationOperator.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+/**
+ * This enumeration keeps the available logical operator defined in the JBIG2 ISO standard.
+ */
+public enum CombinationOperator {
+  OR, AND, XOR, XNOR, REPLACE;
+
+  public static CombinationOperator translateOperatorCodeToEnum(short combinationOperatorCode) {
+    switch (combinationOperatorCode){
+      case 0 :
+        return OR;
+      case 1 :
+        return AND;
+      case 2 :
+        return XOR;
+      case 3 :
+        return XNOR;
+      default :
+        return REPLACE;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/DictionaryViewer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/DictionaryViewer.java b/src/main/java/org/apache/pdfbox/jbig2/util/DictionaryViewer.java
new file mode 100644
index 0000000..2659082
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/DictionaryViewer.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+import java.util.List;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.TestImage;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+
+/**
+ * This class is for debug purpose only. The {@code DictionaryViewer} is able to show a single
+ * bitmap or all symbol bitmaps.
+ */
+class DictionaryViewer {
+
+  public static void show(Bitmap b) {
+    new TestImage(Bitmaps.asBufferedImage(b));
+  }
+
+  public static void show(List<Bitmap> symbols) {
+    int width = 0;
+    int height = 0;
+
+    for (Bitmap b : symbols) {
+      width += b.getWidth();
+
+      if (b.getHeight() > height) {
+        height = b.getHeight();
+      }
+    }
+
+    Bitmap result = new Bitmap(width, height);
+
+    int xOffset = 0;
+
+    for (Bitmap b : symbols) {
+      Bitmaps.blit(b, result, xOffset, 0, CombinationOperator.REPLACE);
+      xOffset += b.getWidth();
+    }
+
+    new TestImage(Bitmaps.asBufferedImage(result));
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/ServiceLookup.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/ServiceLookup.java b/src/main/java/org/apache/pdfbox/jbig2/util/ServiceLookup.java
new file mode 100644
index 0000000..0c3f78e
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/ServiceLookup.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+public class ServiceLookup<B> {
+
+  public Iterator<B> getServices(Class<B> cls) {
+    return getServices(cls, null);
+  }
+
+  public Iterator<B> getServices(Class<B> cls, ClassLoader clsLoader) {
+    Iterator<B> services = ServiceLoader.load(cls).iterator();
+
+    if (!services.hasNext()) {
+      services = ServiceLoader.load(cls, cls.getClass().getClassLoader()).iterator();
+    }
+
+    if (!services.hasNext() && clsLoader != null) {
+      services = ServiceLoader.load(cls, clsLoader).iterator();
+    }
+
+    return services;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/Utils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/Utils.java b/src/main/java/org/apache/pdfbox/jbig2/util/Utils.java
new file mode 100644
index 0000000..ae2ae7a
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/Utils.java
@@ -0,0 +1,101 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+
+public class Utils {
+
+  /**
+   * Create a rectangle with the same area as the given input rectangle but with all of its edges
+   * snapped (rounded) to the integer grid. The resulting rectangle is guaranteed to cover
+   * <em>all</em> of the input rectangle's area, so that
+   * <code>enlargeToGrid(r).contains(r) == true</code> holds. This can be depicted as the edges
+   * being stretched in an outward direction.
+   * 
+   * @param r
+   * @return
+   */
+  public static Rectangle enlargeRectToGrid(Rectangle2D r) {
+    final int x0 = floor(r.getMinX());
+    final int y0 = floor(r.getMinY());
+    final int x1 = ceil(r.getMaxX());
+    final int y1 = ceil(r.getMaxY());
+    return new Rectangle(x0, y0, x1 - x0, y1 - y0);
+  }
+  
+  /**
+   * Return a new rectangle which covers the area of the given rectangle with an additional margin
+   * on the sides.
+   * 
+   * @param r
+   * @param marginX
+   */
+  public static Rectangle2D dilateRect(Rectangle2D r, double marginX, double marginY) {
+    return new Rectangle2D.Double(r.getX() - marginX, r.getY() - marginY, r.getWidth() + 2 * marginX, r.getHeight() + 2
+        * marginY);
+  }
+  
+  /**
+   * Clamp the value into the range [min..max].
+   * 
+   * @param value
+   * @param min
+   * @param max
+   * @return the clamped value
+   */
+  public static double clamp(double value, double min, double max) {
+    return Math.min(max, Math.max(value, min));
+  }
+  
+  private static final int BIG_ENOUGH_INT = 16 * 1024;
+  private static final double BIG_ENOUGH_FLOOR = BIG_ENOUGH_INT;
+  private static final double BIG_ENOUGH_ROUND = BIG_ENOUGH_INT + 0.5;
+  
+  /**
+   * A fast implementation of {@link Math#floor(double)}.
+   * 
+   * @param x the argument
+   * @return
+   */
+  public static int floor(double x) {
+    return (int) (x + BIG_ENOUGH_FLOOR) - BIG_ENOUGH_INT;
+  }
+
+  /**
+   * A fast implementation of {@link Math#round(double)}.
+   * 
+   * @param x the argument
+   * @return
+   */
+  public static int round(double x) {
+    return (int) (x + BIG_ENOUGH_ROUND) - BIG_ENOUGH_INT;
+  }
+
+  /**
+   * A fast implementation of {@link Math#ceil(double)}.
+   * 
+   * @param x the argument
+   * @return
+   */
+  public static int ceil(double x) {
+    return BIG_ENOUGH_INT - (int) (BIG_ENOUGH_FLOOR - x);
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/cache/Cache.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/Cache.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/Cache.java
new file mode 100644
index 0000000..41038cf
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/Cache.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+public interface Cache {
+
+/**
+ * 
+ * @param key
+ * @param value
+ * @param sizeEstimate
+ * 
+ * @return the old object, that was replaced if present. Otherwise {@code null}.
+ */
+  Object put(Object key, Object value, int sizeEstimate);
+
+  Object get(Object key);
+
+  /**
+   * Removes all mappings from a map (optional operation).
+   * 
+   * @throws UnsupportedOperationException if {@code clear()} is not supported by the map.
+   */
+  void clear();
+
+  /**
+   * 
+   * @param key
+   * @return the removed object, if present. Otherwise {@code null}.
+   */
+  Object remove(Object key);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheBridge.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheBridge.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheBridge.java
new file mode 100644
index 0000000..19338b1
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheBridge.java
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+public interface CacheBridge {
+
+  Cache getCache();
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheFactory.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheFactory.java
new file mode 100644
index 0000000..718c04a
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheFactory.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+import java.util.Iterator;
+
+import org.apache.pdfbox.jbig2.util.ServiceLookup;
+
+/**
+ * Retrieves a {@link Cache} via registered {@link CacheBridge} through
+ * <code>META-INF/services</code> lookup.
+ */
+public class CacheFactory {
+
+  private static CacheBridge cacheBridge;
+
+  private static ClassLoader clsLoader;
+
+  public static Cache getCache(ClassLoader clsLoader) {
+    if (null == cacheBridge) {
+      final ServiceLookup<CacheBridge> serviceLookup = new ServiceLookup<CacheBridge>();
+      final Iterator<CacheBridge> cacheBridgeServices = serviceLookup.getServices(CacheBridge.class, clsLoader);
+
+      if (!cacheBridgeServices.hasNext()) {
+        throw new IllegalStateException("No implementation of " + CacheBridge.class
+            + " was avaliable using META-INF/services lookup");
+      }
+      cacheBridge = cacheBridgeServices.next();
+    }
+    return cacheBridge.getCache();
+  }
+
+  public static Cache getCache() {
+    return getCache(clsLoader != null ? clsLoader : CacheBridge.class.getClassLoader());
+  }
+
+  public static void setClassLoader(ClassLoader clsLoader) {
+    CacheFactory.clsLoader = clsLoader;
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCache.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCache.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCache.java
new file mode 100644
index 0000000..35a1407
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCache.java
@@ -0,0 +1,50 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+
+public class SoftReferenceCache implements Cache {
+
+  private HashMap<Object, SoftReference<?>> cache = new HashMap<Object, SoftReference<?>>();
+
+  public Object put(Object key, Object value, int sizeEstimate) {
+    SoftReference<Object> softReference = new SoftReference<Object>(value);
+    SoftReference<?> oldValue = cache.put(key, softReference);
+    return getValueNullSafe(oldValue);
+  }
+
+  public Object get(Object key) {
+    SoftReference<?> softReference = cache.get(key);
+    return getValueNullSafe(softReference);
+  }
+
+  public void clear() {
+    cache.clear();
+  }
+
+  public Object remove(Object key) {
+    SoftReference<?> removedObj = cache.remove(key);
+    return getValueNullSafe(removedObj);
+  }
+
+  private Object getValueNullSafe(SoftReference<?> softReference) {
+    return softReference == null ? null : softReference.get();
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCacheBridge.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCacheBridge.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCacheBridge.java
new file mode 100644
index 0000000..59f039f
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCacheBridge.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+public class SoftReferenceCacheBridge implements CacheBridge {
+
+  private static final SoftReferenceCache cache = new SoftReferenceCache();
+
+  public Cache getCache() {
+    return cache;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLogger.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLogger.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLogger.java
new file mode 100644
index 0000000..70f3e0e
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLogger.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+import java.util.logging.Level;
+
+public class JDKLogger implements Logger {
+  final java.util.logging.Logger wrappedLogger;
+
+  public JDKLogger(java.util.logging.Logger logger) {
+    wrappedLogger = logger;
+  }
+
+  public void debug(String msg) {
+    wrappedLogger.log(Level.FINE, msg);
+  }
+
+  public void debug(String msg, Throwable t) {
+    wrappedLogger.log(Level.FINE, msg, t);
+  }
+
+  public void info(String msg) {
+    wrappedLogger.log(Level.INFO, msg);
+  }
+
+  public void info(String msg, Throwable t) {
+    wrappedLogger.log(Level.INFO, msg, t);
+  }
+
+  public void warn(String msg) {
+    wrappedLogger.log(Level.WARNING, msg);
+  }
+
+  public void warn(String msg, Throwable t) {
+    wrappedLogger.log(Level.WARNING, msg, t);
+  }
+
+  public void fatal(String msg) {
+    wrappedLogger.log(Level.SEVERE, msg);
+  }
+
+  public void fatal(String msg, Throwable t) {
+    wrappedLogger.log(Level.SEVERE, msg, t);
+  }
+
+  public void error(String msg) {
+    wrappedLogger.log(Level.SEVERE, msg);
+  }
+
+  public void error(String msg, Throwable t) {
+    wrappedLogger.log(Level.SEVERE, msg, t);
+  }
+
+  public boolean isDebugEnabled() {
+    return wrappedLogger.isLoggable(Level.FINE);
+  }
+
+  public boolean isInfoEnabled() {
+    return wrappedLogger.isLoggable(Level.INFO);
+  }
+
+  public boolean isWarnEnabled() {
+    return wrappedLogger.isLoggable(Level.WARNING);
+  }
+
+  public boolean isFatalEnabled() {
+    return wrappedLogger.isLoggable(Level.SEVERE);
+  }
+
+  public boolean isErrorEnabled() {
+    return wrappedLogger.isLoggable(Level.SEVERE);
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLoggerBridge.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLoggerBridge.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLoggerBridge.java
new file mode 100644
index 0000000..bc82d9e
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLoggerBridge.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+public class JDKLoggerBridge implements LoggerBridge {
+
+  public Logger getLogger(Class<?> classToBeLogged) {
+    return new JDKLogger(java.util.logging.Logger.getLogger(classToBeLogged.getName()));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/log/Logger.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/Logger.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/Logger.java
new file mode 100644
index 0000000..941ea61
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/Logger.java
@@ -0,0 +1,135 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+public interface Logger {
+
+
+  /**
+   * Log a message at the DEBUG level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void debug(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the DEBUG level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception.
+   * @param t the exception ({@link Throwable}) to log.
+   */
+  public void debug(String msg, Throwable t);
+
+  /**
+   * Log a message at the INFO level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void info(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the INFO level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception
+   * @param t the exception ({@link Throwable}) to log
+   */
+  public void info(String msg, Throwable t);
+
+  /**
+   * Log a message at the WARN level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void warn(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the WARN level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception
+   * @param t the exception ({@link Throwable}) to log
+   */
+  public void warn(String msg, Throwable t);
+
+  /**
+   * Log a message at the WARN level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void fatal(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the WARN level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception
+   * @param t the exception ({@link Throwable}) to log
+   */
+  public void fatal(String msg, Throwable t);
+
+  /**
+   * Log a message at the ERROR level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void error(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the ERROR level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception
+   * @param t the exception ({@link Throwable}) to log
+   */
+  public void error(String msg, Throwable t);
+
+  /**
+   * Is the logger instance enabled for the DEBUG level?
+   * 
+   * @return True if this Logger is enabled for the DEBUG level, false otherwise.
+   * 
+   */
+  public boolean isDebugEnabled();
+
+  /**
+   * Is the logger instance enabled for the INFO level?
+   * 
+   * @return True if this Logger is enabled for the INFO level, false otherwise.
+   */
+  public boolean isInfoEnabled();
+
+  /**
+   * Is the logger instance enabled for the WARN level?
+   * 
+   * @return True if this Logger is enabled for the WARN level, false otherwise.
+   */
+  public boolean isWarnEnabled();
+
+  /**
+   * Is the logger instance enabled for the FATAL level?
+   * 
+   * @return True if this Logger is enabled for the FATAL level, false otherwise.
+   */
+  public boolean isFatalEnabled();
+
+  /**
+   * Is the logger instance enabled for the ERROR level?
+   * 
+   * @return True if this Logger is enabled for the ERROR level, false otherwise.
+   */
+  public boolean isErrorEnabled();
+
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerBridge.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerBridge.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerBridge.java
new file mode 100644
index 0000000..6dd033c
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerBridge.java
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+public interface LoggerBridge {
+
+  Logger getLogger(Class<?> clazz);
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerFactory.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerFactory.java
new file mode 100644
index 0000000..2eea946
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerFactory.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+import java.util.Iterator;
+
+import org.apache.pdfbox.jbig2.util.ServiceLookup;
+
+/**
+ * Retrieves a {@link Logger} via registered {@link LoggerBridge} through META-INF/services lookup.
+ */
+public class LoggerFactory {
+
+  private static LoggerBridge loggerBridge;
+
+  private static ClassLoader clsLoader;
+
+  public static Logger getLogger(Class<?> cls, ClassLoader clsLoader) {
+    if (null == loggerBridge) {
+      final ServiceLookup<LoggerBridge> serviceLookup = new ServiceLookup<LoggerBridge>();
+      final Iterator<LoggerBridge> loggerBridgeServices = serviceLookup.getServices(LoggerBridge.class, clsLoader);
+
+      if (!loggerBridgeServices.hasNext()) {
+        throw new IllegalStateException("No implementation of " + LoggerBridge.class
+            + " was avaliable using META-INF/services lookup");
+      }
+      loggerBridge = loggerBridgeServices.next();
+    }
+    return loggerBridge.getLogger(cls);
+  }
+
+  public static Logger getLogger(Class<?> cls) {
+    return getLogger(cls, clsLoader != null ? clsLoader : LoggerBridge.class.getClassLoader());
+  }
+
+  public static void setClassLoader(ClassLoader clsLoader) {
+    LoggerFactory.clsLoader = clsLoader;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
----------------------------------------------------------------------
diff --git a/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100644
index 0000000..8f70796
--- /dev/null
+++ b/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.apache.pdfbox.jbig2.JBIG2ImageReaderSpi
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.cache.CacheBridge
----------------------------------------------------------------------
diff --git a/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.cache.CacheBridge b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.cache.CacheBridge
new file mode 100644
index 0000000..fa9ad12
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.cache.CacheBridge
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.apache.pdfbox.jbig2.util.cache.SoftReferenceCacheBridge
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.log.LoggerBridge
----------------------------------------------------------------------
diff --git a/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.log.LoggerBridge b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.log.LoggerBridge
new file mode 100644
index 0000000..03e4159
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.log.LoggerBridge
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.apache.pdfbox.jbig2.util.log.JDKLoggerBridge
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/test/java/org/apache/pdfbox/jbig2/BitmapTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/pdfbox/jbig2/BitmapTest.java b/src/test/java/org/apache/pdfbox/jbig2/BitmapTest.java
new file mode 100644
index 0000000..4be8be0
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/BitmapTest.java
@@ -0,0 +1,98 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import static org.junit.Assert.*;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+public class BitmapTest {
+
+  @Test
+  public void getPixelAndSetPixelTest() {
+    final Bitmap bitmap = new Bitmap(37, 49);
+    assertEquals(0, bitmap.getPixel(3, 19));
+    
+    bitmap.setPixel(3, 19, (byte) 1);
+
+    assertEquals(1, bitmap.getPixel(3, 19));
+  }
+
+  @Test
+  public void getByteAndSetByteTest() {
+    Bitmap bitmap = new Bitmap(16, 16);
+
+    byte value = (byte) 4;
+    bitmap.setByte(0, value);
+    bitmap.setByte(31, value);
+
+    assertEquals(value, bitmap.getByte(0));
+    assertEquals(value, bitmap.getByte(31));
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void getByteThrowsExceptionTest() {
+    Bitmap bitmap = new Bitmap(16, 16);
+    bitmap.getByte(32);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void setByteThrowsExceptionTest() {
+    Bitmap bitmap = new Bitmap(16, 16);
+    bitmap.setByte(32, (byte) 0);
+  }
+
+  @Test
+  public void getByteAsIntegerTest() {
+    Bitmap bitmap = new Bitmap(16, 16);
+
+    byte byteValue = (byte) 4;
+    int integerValue = byteValue;
+    bitmap.setByte(0, byteValue);
+    bitmap.setByte(31, byteValue);
+
+    Assert.assertEquals(integerValue, bitmap.getByteAsInteger(0));
+    Assert.assertEquals(integerValue, bitmap.getByteAsInteger(31));
+
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void getByteAsIntegerThrowsExceptionTest() {
+    Bitmap bitmap = new Bitmap(16, 16);
+    bitmap.getByte(32);
+  }
+
+  @Test
+  public void getHeightTest() {
+    int height = 16;
+    Bitmap bitmap = new Bitmap(1, height);
+    Assert.assertEquals(height, bitmap.getHeight());
+  }
+
+  @Test
+  public void getWidthTest() {
+    int width = 16;
+    Bitmap bitmap = new Bitmap(width, 1);
+    Assert.assertEquals(width, bitmap.getWidth());
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/test/java/org/apache/pdfbox/jbig2/ChecksumCalculator.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/pdfbox/jbig2/ChecksumCalculator.java b/src/test/java/org/apache/pdfbox/jbig2/ChecksumCalculator.java
new file mode 100644
index 0000000..debc7b9
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/ChecksumCalculator.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2Document;
+import org.apache.pdfbox.jbig2.err.JBIG2Exception;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.junit.Ignore;
+import org.junit.Test;
+
+@Ignore
+public class ChecksumCalculator {
+
+  @Ignore
+  @Test
+  public void computeChecksum() throws NoSuchAlgorithmException, IOException, JBIG2Exception {
+    String filepath = "/images/sampledata_page3.jb2";
+    int pageNumber = 1;
+
+    InputStream is = getClass().getResourceAsStream(filepath);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    JBIG2Document doc = new JBIG2Document(iis);
+    Bitmap bitmap = doc.getPage(pageNumber).getBitmap();
+
+    byte[] md5 = md5(bitmap);
+    for (byte b : md5) {
+      System.out.print(b);
+    }
+    System.out.println(Arrays.toString(md5));
+  }
+
+  public static byte[] md5(Bitmap b) throws NoSuchAlgorithmException {
+    return MessageDigest.getInstance("MD5").digest(b.getByteArray());
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/test/java/org/apache/pdfbox/jbig2/ChecksumTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/pdfbox/jbig2/ChecksumTest.java b/src/test/java/org/apache/pdfbox/jbig2/ChecksumTest.java
new file mode 100644
index 0000000..3dd208c
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/ChecksumTest.java
@@ -0,0 +1,175 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Collection;
+
+import javax.imageio.stream.ImageInputStream;
+
+import junit.framework.Assert;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2Document;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ChecksumTest {
+
+  @Parameters
+  public static Collection<Object[]> data() {
+    return Arrays.asList(new Object[][]{
+        {
+            "/images/042_1.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_2.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_3.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_4.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_5.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_6.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_7.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_8.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_9.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_10.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_11.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_12.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        },
+        // { "/images/042_13.jb2",
+        // "69-26-6629-1793-107941058147-58-79-37-31-79" },
+        // { "/images/042_14.jb2",
+        // "69-26-6629-1793-107941058147-58-79-37-31-79" },
+        {
+            "/images/042_15.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_16.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_17.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_18.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_19.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_20.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_21.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_22.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_23.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_24.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/042_25.jb2", "69-26-6629-1793-107941058147-58-79-37-31-79"
+        }, {
+            "/images/amb_1.jb2", "58311272494-318210035-125100-344625-126-79"
+        }, {
+            "/images/amb_2.jb2", "58311272494-318210035-125100-344625-126-79"
+        }, {
+            "/images/002.jb2", "-12713-4587-92-651657111-57121-1582564895"
+        }, {
+            "/images/003.jb2", "-37-108-89-33-78-5019-966-96-124-9675-1-108-24"
+        }, {
+            "/images/004.jb2", "-10709436-24-59-48-217114-37-85-3126-24"
+        }, {
+            "/images/005.jb2", "712610586-1224021396100112-102-77-1177851"
+        }, {
+            "/images/006.jb2", "-8719-116-83-83-35-3425-64-528667602154-25"
+        }, {
+            "/images/007.jb2", "6171-125-109-20-128-71925295955793-127-41-122"
+        }, {
+            "/images/sampledata_page1.jb2", "104-68-555325117-4757-48527676-9775-8432"
+        }, {
+            "/images/sampledata_page2.jb2", "104-68-555325117-4757-48527676-9775-8432"
+        }, {
+            "/images/sampledata_page3.jb2", "-7825-56-41-30-19-719536-3678580-61-2586"
+        }, {
+            "/images/20123110001.jb2", "60-96-101-2458-3335024-5468-5-11068-78-80"
+        }, {
+            "/images/20123110002.jb2", "-28-921048181-117-48-96126-110-9-2865611113"
+        }, {
+            "/images/20123110003.jb2", "-3942-239351-28-56-729169-5839122-439231"
+        }, {
+            "/images/20123110004.jb2", "-49-101-28-20-57-4-24-17-9352104-106-118-122-122"
+        }, {
+            "/images/20123110005.jb2", "-48221261779-94-838820-127-114110-2-88-80-106"
+        }, {
+            "/images/20123110006.jb2", "81-11870-63-30124-1614-45838-53-123-41639"
+        }, {
+            "/images/20123110007.jb2", "12183-49124728346-29-124-9-10775-63-44116103"
+        }, {
+            "/images/20123110008.jb2", "15-74-49-45958458-67-2545-96-119-122-60100-35"
+        }, {
+            "/images/20123110009.jb2", "36115-114-28-123-3-70-87-113-4197-8512396113-65"
+        }, {
+            "/images/20123110010.jb2", "-109-1069-61-1576-67-43122406037-75-1091115"
+        }
+    });
+  }
+
+  private final String filepath;
+  private final String checksum;
+
+  public ChecksumTest(String filepath, String cksum) {
+    this.filepath = filepath;
+    this.checksum = cksum;
+  }
+
+  @Test
+  public void compareChecksum() throws Throwable {
+    int imageIndex = 1;
+
+    InputStream is = getClass().getResourceAsStream(filepath);
+    System.out.println("####################################");
+    System.out.println("File: " + filepath);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+
+    JBIG2Document doc = new JBIG2Document(iis);
+
+    long time = System.currentTimeMillis();
+    Bitmap b = doc.getPage(imageIndex).getBitmap();
+    long duration = System.currentTimeMillis() - time;
+
+    byte[] digest = MessageDigest.getInstance("MD5").digest(b.getByteArray());
+
+    StringBuilder stringBuilder = new StringBuilder();
+    for (byte toAppend : digest) {
+      stringBuilder.append(toAppend);
+    }
+    System.out.println("Completed decoding in " + duration + " ms");
+    System.out.println("####################################\n");
+
+    Assert.assertEquals(checksum, stringBuilder.toString());
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/test/java/org/apache/pdfbox/jbig2/GithubIssuesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/pdfbox/jbig2/GithubIssuesTest.java b/src/test/java/org/apache/pdfbox/jbig2/GithubIssuesTest.java
new file mode 100644
index 0000000..96aa2a2
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/GithubIssuesTest.java
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import static org.apache.pdfbox.jbig2.ChecksumCalculator.*;
+import static org.apache.pdfbox.jbig2.JBIG2DocumentFacade.*;
+
+import java.io.InputStream;
+
+import javax.imageio.ImageIO;
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2Document;
+import org.apache.pdfbox.jbig2.JBIG2Page;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Collection of tests for <a href="https://github.com/levigo/jbig2-imageio/issues">Github
+ * issues</a>.
+ */
+public class GithubIssuesTest {
+
+  /**
+   * <a href="https://github.com/levigo/jbig2-imageio/issues/21">Github issue 21s</a>
+   */
+  @Test
+  public void issue21() throws Exception {
+    final byte[] md5Expected = new byte[]{
+        83, 74, -69, -60, -122, -99, 21, 126, -115, 13, 9, 107, -31, -109, 77, -119
+    };
+
+    final InputStream imageStream = getClass().getResourceAsStream("/com/levigo/jbig2/github/21.jb2");
+    final InputStream globalsStream = getClass().getResourceAsStream("/com/levigo/jbig2/github/21.glob");
+    final ImageInputStream globalsIIS = ImageIO.createImageInputStream(globalsStream);
+    final ImageInputStream imageIIS = ImageIO.createImageInputStream(imageStream);
+
+    byte[] md5Actual = null;
+    try {
+      final JBIG2Document doc = doc(imageIIS, globalsIIS);
+      final JBIG2Page page = doc.getPage(1);
+      final Bitmap bitmap = page.getBitmap();
+      md5Actual = md5(bitmap);
+    } finally {
+      Assert.assertArrayEquals(md5Expected, md5Actual);
+
+      globalsIIS.close();
+      globalsStream.close();
+      imageIIS.close();
+      imageStream.close();
+    }
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/test/java/org/apache/pdfbox/jbig2/JBIG2DocumentFacade.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/pdfbox/jbig2/JBIG2DocumentFacade.java b/src/test/java/org/apache/pdfbox/jbig2/JBIG2DocumentFacade.java
new file mode 100644
index 0000000..e38c09d
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/JBIG2DocumentFacade.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.IOException;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2Document;
+import org.apache.pdfbox.jbig2.JBIG2Globals;
+import org.apache.pdfbox.jbig2.JBIG2Page;
+import org.apache.pdfbox.jbig2.err.JBIG2Exception;
+
+public class JBIG2DocumentFacade extends JBIG2Document {
+  
+  public static JBIG2Document doc(ImageInputStream doc, ImageInputStream globals) throws IOException {
+    final JBIG2Document globalsDoc = new JBIG2Document(globals);
+    return new JBIG2Document(doc, globalsDoc.getGlobalSegments());
+  }
+
+  public JBIG2DocumentFacade(ImageInputStream input) throws IOException {
+    super(input);
+  }
+
+  public JBIG2DocumentFacade(ImageInputStream input, JBIG2Globals globals) throws IOException {
+    super(input, globals);
+  }
+
+  public JBIG2Page getPage(int pageNumber) {
+    return super.getPage(pageNumber);
+  }
+
+  public Bitmap getPageBitmap(int pageNumber) throws JBIG2Exception, IOException {
+    return getPage(pageNumber).getBitmap();
+  }
+
+}