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:19 UTC

[6/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/decoder/mmr/MMRConstants.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRConstants.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRConstants.java
new file mode 100644
index 0000000..a5d3971
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRConstants.java
@@ -0,0 +1,754 @@
+/**
+ * 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.decoder.mmr;
+
+/**
+ * Constants for MMR (de)compression.
+ */
+public class MMRConstants {
+  public static final int COMP_FAXG3 = 0;
+  public static final int COMP_FAXG4 = 1;
+  public static final int COMP_MMR = 2;
+  public static final int COMP_RLE = 3;
+  public static final int COMP_FAXG3_2D = 4;
+
+  public static final int NOMASK = 0xFFFF;
+  public static final int INCOMP = -4;
+  public static final int EOF = -3;
+  public static final int INVALID = -2;
+  public static final int EOL = -1;
+  public static final int CODE_P = 0;
+  public static final int CODE_H = 1;
+  public static final int CODE_V0 = 2;
+  public static final int CODE_VR1 = 3;
+  public static final int CODE_VR2 = 4;
+  public static final int CODE_VR3 = 5;
+  public static final int CODE_VL1 = 6;
+  public static final int CODE_VL2 = 7;
+  public static final int CODE_VL3 = 8;
+  public static final int CODE_EXT2D = 9;
+  public static final int CODE_EXT1D = 10;
+  public static final int CODE_EOL = 11;
+  public static final int CODE_EOF = 12;
+  public static final int CODE_MAX = 12;
+
+  // --------------------------------------------------------------------------------------------------------------
+  public static final int ModeCodes[][] = {
+      {
+          4, 0x1, CODE_P
+      }, // 0001 pass
+      {
+          3, 0x1, CODE_H
+      }, // 001 horizontal
+      {
+          1, 0x1, CODE_V0
+      }, // 1 vert 0
+      {
+          3, 0x3, CODE_VR1
+      }, // 011 vert r 1
+      {
+          6, 0x3, CODE_VR2
+      }, // 000011 vert r 2
+      {
+          7, 0x3, CODE_VR3
+      }, // 0000011 vert r 3
+      {
+          3, 0x2, CODE_VL1
+      }, // 010 vert l 1
+      {
+          6, 0x2, CODE_VL2
+      }, // 000010 vert l 2
+      {
+          7, 0x2, CODE_VL3
+      }, // 0000010 vert l 3
+      {
+          10, 0xf, CODE_EXT2D
+      }, // 0000001111
+      {
+          12, 0xf, CODE_EXT1D
+      }, // 000000001111
+      {
+          12, 0x1, EOL
+      }
+  // 000000000001
+  };
+
+  public static final int WhiteCodes[][] = {
+      {
+          4, 0x07, 2
+      }, // 0111
+      {
+          4, 0x08, 3
+      }, // 1000
+      {
+          4, 0x0B, 4
+      }, // 1011
+      {
+          4, 0x0C, 5
+      }, // 1100
+      {
+          4, 0x0E, 6
+      }, // 1110
+      {
+          4, 0x0F, 7
+      }, // 1111
+      {
+          5, 0x12, 128
+      }, // 1001 0
+      {
+          5, 0x13, 8
+      }, // 1001 1
+      {
+          5, 0x14, 9
+      }, // 1010 0
+      {
+          5, 0x1B, 64
+      }, // 1101 1
+      {
+          5, 0x07, 10
+      }, // 0011 1
+      {
+          5, 0x08, 11
+      }, // 0100 0
+      {
+          6, 0x17, 192
+      }, // 0101 11
+      {
+          6, 0x18, 1664
+      }, // 0110 00
+      {
+          6, 0x2A, 16
+      }, // 1010 10
+      {
+          6, 0x2B, 17
+      }, // 1010 11
+      {
+          6, 0x03, 13
+      }, // 0000 11
+      {
+          6, 0x34, 14
+      }, // 1101 00
+      {
+          6, 0x35, 15
+      }, // 1101 01
+      {
+          6, 0x07, 1
+      }, // 0001 11
+      {
+          6, 0x08, 12
+      }, // 0010 00
+      {
+          7, 0x13, 26
+      }, // 0010 011
+      {
+          7, 0x17, 21
+      }, // 0010 111
+      {
+          7, 0x18, 28
+      }, // 0011 000
+      {
+          7, 0x24, 27
+      }, // 0100 100
+      {
+          7, 0x27, 18
+      }, // 0100 111
+      {
+          7, 0x28, 24
+      }, // 0101 000
+      {
+          7, 0x2B, 25
+      }, // 0101 011
+      {
+          7, 0x03, 22
+      }, // 0000 011
+      {
+          7, 0x37, 256
+      }, // 0110 111
+      {
+          7, 0x04, 23
+      }, // 0000 100
+      {
+          7, 0x08, 20
+      }, // 0001 000
+      {
+          7, 0xC, 19
+      }, // 0001 100
+      {
+          8, 0x12, 33
+      }, // 0001 0010
+      {
+          8, 0x13, 34
+      }, // 0001 0011
+      {
+          8, 0x14, 35
+      }, // 0001 0100
+      {
+          8, 0x15, 36
+      }, // 0001 0101
+      {
+          8, 0x16, 37
+      }, // 0001 0110
+      {
+          8, 0x17, 38
+      }, // 0001 0111
+      {
+          8, 0x1A, 31
+      }, // 0001 1010
+      {
+          8, 0x1B, 32
+      }, // 0001 1011
+      {
+          8, 0x02, 29
+      }, // 0000 0010
+      {
+          8, 0x24, 53
+      }, // 0010 0100
+      {
+          8, 0x25, 54
+      }, // 0010 0101
+      {
+          8, 0x28, 39
+      }, // 0010 1000
+      {
+          8, 0x29, 40
+      }, // 0010 1001
+      {
+          8, 0x2A, 41
+      }, // 0010 1010
+      {
+          8, 0x2B, 42
+      }, // 0010 1011
+      {
+          8, 0x2C, 43
+      }, // 0010 1100
+      {
+          8, 0x2D, 44
+      }, // 0010 1101
+      {
+          8, 0x03, 30
+      }, // 0000 0011
+      {
+          8, 0x32, 61
+      }, // 0011 0010
+      {
+          8, 0x33, 62
+      }, // 0011 0011
+      {
+          8, 0x34, 63
+      }, // 0011 0100
+      {
+          8, 0x35, 0
+      }, // 0011 0101
+      {
+          8, 0x36, 320
+      }, // 0011 0110
+      {
+          8, 0x37, 384
+      }, // 0011 0111
+      {
+          8, 0x04, 45
+      }, // 0000 0100
+      {
+          8, 0x4A, 59
+      }, // 0100 1010
+      {
+          8, 0x4B, 60
+      }, // 0100 1011
+      {
+          8, 0x5, 46
+      }, // 0000 0101
+      {
+          8, 0x52, 49
+      }, // 0101 0010
+      {
+          8, 0x53, 50
+      }, // 0101 0011
+      {
+          8, 0x54, 51
+      }, // 0101 0100
+      {
+          8, 0x55, 52
+      }, // 0101 0101
+      {
+          8, 0x58, 55
+      }, // 0101 1000
+      {
+          8, 0x59, 56
+      }, // 0101 1001
+      {
+          8, 0x5A, 57
+      }, // 0101 1010
+      {
+          8, 0x5B, 58
+      }, // 0101 1011
+      {
+          8, 0x64, 448
+      }, // 0110 0100
+      {
+          8, 0x65, 512
+      }, // 0110 0101
+      {
+          8, 0x67, 640
+      }, // 0110 0111
+      {
+          8, 0x68, 576
+      }, // 0110 1000
+      {
+          8, 0x0A, 47
+      }, // 0000 1010
+      {
+          8, 0x0B, 48
+      }, // 0000 1011
+      {
+          9, 0x01, INVALID
+      }, // 0000 0000 1
+      {
+          9, 0x98, 1472
+      }, // 0100 1100 0
+      {
+          9, 0x99, 1536
+      }, // 0100 1100 1
+      {
+          9, 0x9A, 1600
+      }, // 0100 1101 0
+      {
+          9, 0x9B, 1728
+      }, // 0100 1101 1
+      {
+          9, 0xCC, 704
+      }, // 0110 0110 0
+      {
+          9, 0xCD, 768
+      }, // 0110 0110 1
+      {
+          9, 0xD2, 832
+      }, // 0110 1001 0
+      {
+          9, 0xD3, 896
+      }, // 0110 1001 1
+      {
+          9, 0xD4, 960
+      }, // 0110 1010 0
+      {
+          9, 0xD5, 1024
+      }, // 0110 1010 1
+      {
+          9, 0xD6, 1088
+      }, // 0110 1011 0
+      {
+          9, 0xD7, 1152
+      }, // 0110 1011 1
+      {
+          9, 0xD8, 1216
+      }, // 0110 1100 0
+      {
+          9, 0xD9, 1280
+      }, // 0110 1100 1
+      {
+          9, 0xDA, 1344
+      }, // 0110 1101 0
+      {
+          9, 0xDB, 1408
+      }, // 0110 1101 1
+      {
+          10, 0x01, INVALID
+      }, // 0000 0000 01
+      {
+          11, 0x01, INVALID
+      }, // 0000 0000 001
+      {
+          11, 0x08, 1792
+      }, // 0000 0001 000
+      {
+          11, 0x0C, 1856
+      }, // 0000 0001 100
+      {
+          11, 0x0D, 1920
+      }, // 0000 0001 101
+      {
+          12, 0x00, EOF
+      }, // 0000 0000 0000
+      {
+          12, 0x01, EOL
+      }, // 0000 0000 0001
+      {
+          12, 0x12, 1984
+      }, // 0000 0001 0010
+      {
+          12, 0x13, 2048
+      }, // 0000 0001 0011
+      {
+          12, 0x14, 2112
+      }, // 0000 0001 0100
+      {
+          12, 0x15, 2176
+      }, // 0000 0001 0101
+      {
+          12, 0x16, 2240
+      }, // 0000 0001 0110
+      {
+          12, 0x17, 2304
+      }, // 0000 0001 0111
+      {
+          12, 0x1C, 2368
+      }, // 0000 0001 1100
+      {
+          12, 0x1D, 2432
+      }, // 0000 0001 1101
+      {
+          12, 0x1E, 2496
+      }, // 0000 0001 1110
+      {
+          12, 0x1F, 2560
+      }
+  // 0000 0001 1111
+  };
+  public static final int MAX_WHITE_RUN = 2560;
+
+  public static final int BlackCodes[][] = {
+      {
+          2, 0x02, 3
+      }, // 10
+      {
+          2, 0x03, 2
+      }, // 11
+      {
+          3, 0x02, 1
+      }, // 010
+      {
+          3, 0x03, 4
+      }, // 011
+      {
+          4, 0x02, 6
+      }, // 0010
+      {
+          4, 0x03, 5
+      }, // 0011
+      {
+          5, 0x03, 7
+      }, // 0001 1
+      {
+          6, 0x04, 9
+      }, // 0001 00
+      {
+          6, 0x05, 8
+      }, // 0001 01
+      {
+          7, 0x04, 10
+      }, // 0000 100
+      {
+          7, 0x05, 11
+      }, // 0000 101
+      {
+          7, 0x07, 12
+      }, // 0000 111
+      {
+          8, 0x04, 13
+      }, // 0000 0100
+      {
+          8, 0x07, 14
+      }, // 0000 0111
+      {
+          9, 0x01, INVALID
+      }, // 0000 0000 1
+      {
+          9, 0x18, 15
+      }, // 0000 1100 0
+      {
+          10, 0x01, INVALID
+      }, // 0000 0000 01
+      {
+          10, 0x17, 16
+      }, // 0000 0101 11
+      {
+          10, 0x18, 17
+      }, // 0000 0110 00
+      {
+          10, 0x37, 0
+      }, // 0000 1101 11
+      {
+          10, 0x08, 18
+      }, // 0000 0010 00
+      {
+          10, 0x0F, 64
+      }, // 0000 0011 11
+      {
+          11, 0x01, INVALID
+      }, // 0000 0000 001
+      {
+          11, 0x17, 24
+      }, // 0000 0010 111
+      {
+          11, 0x18, 25
+      }, // 0000 0011 000
+      {
+          11, 0x28, 23
+      }, // 0000 0101 000
+      {
+          11, 0x37, 22
+      }, // 0000 0110 111
+      {
+          11, 0x67, 19
+      }, // 0000 1100 111
+      {
+          11, 0x68, 20
+      }, // 0000 1101 000
+      {
+          11, 0x6C, 21
+      }, // 0000 1101 100
+      {
+          11, 0x08, 1792
+      }, // 0000 0001 000
+      {
+          11, 0x0C, 1856
+      }, // 0000 0001 100
+      {
+          11, 0x0D, 1920
+      }, // 0000 0001 101
+      {
+          12, 0x00, EOF
+      }, // 0000 0000 0000
+      {
+          12, 0x01, EOL
+      }, // 0000 0000 0001
+      {
+          12, 0x12, 1984
+      }, // 0000 0001 0010
+      {
+          12, 0x13, 2048
+      }, // 0000 0001 0011
+      {
+          12, 0x14, 2112
+      }, // 0000 0001 0100
+      {
+          12, 0x15, 2176
+      }, // 0000 0001 0101
+      {
+          12, 0x16, 2240
+      }, // 0000 0001 0110
+      {
+          12, 0x17, 2304
+      }, // 0000 0001 0111
+      {
+          12, 0x1C, 2368
+      }, // 0000 0001 1100
+      {
+          12, 0x1D, 2432
+      }, // 0000 0001 1101
+      {
+          12, 0x1E, 2496
+      }, // 0000 0001 1110
+      {
+          12, 0x1F, 2560
+      }, // 0000 0001 1111
+      {
+          12, 0x24, 52
+      }, // 0000 0010 0100
+      {
+          12, 0x27, 55
+      }, // 0000 0010 0111
+      {
+          12, 0x28, 56
+      }, // 0000 0010 1000
+      {
+          12, 0x2B, 59
+      }, // 0000 0010 1011
+      {
+          12, 0x2C, 60
+      }, // 0000 0010 1100
+      {
+          12, 0x33, 320
+      }, // 0000 0011 0011
+      {
+          12, 0x34, 384
+      }, // 0000 0011 0100
+      {
+          12, 0x35, 448
+      }, // 0000 0011 0101
+      {
+          12, 0x37, 53
+      }, // 0000 0011 0111
+      {
+          12, 0x38, 54
+      }, // 0000 0011 1000
+      {
+          12, 0x52, 50
+      }, // 0000 0101 0010
+      {
+          12, 0x53, 51
+      }, // 0000 0101 0011
+      {
+          12, 0x54, 44
+      }, // 0000 0101 0100
+      {
+          12, 0x55, 45
+      }, // 0000 0101 0101
+      {
+          12, 0x56, 46
+      }, // 0000 0101 0110
+      {
+          12, 0x57, 47
+      }, // 0000 0101 0111
+      {
+          12, 0x58, 57
+      }, // 0000 0101 1000
+      {
+          12, 0x59, 58
+      }, // 0000 0101 1001
+      {
+          12, 0x5A, 61
+      }, // 0000 0101 1010
+      {
+          12, 0x5B, 256
+      }, // 0000 0101 1011
+      {
+          12, 0x64, 48
+      }, // 0000 0110 0100
+      {
+          12, 0x65, 49
+      }, // 0000 0110 0101
+      {
+          12, 0x66, 62
+      }, // 0000 0110 0110
+      {
+          12, 0x67, 63
+      }, // 0000 0110 0111
+      {
+          12, 0x68, 30
+      }, // 0000 0110 1000
+      {
+          12, 0x69, 31
+      }, // 0000 0110 1001
+      {
+          12, 0x6A, 32
+      }, // 0000 0110 1010
+      {
+          12, 0x6B, 33
+      }, // 0000 0110 1011
+      {
+          12, 0x6C, 40
+      }, // 0000 0110 1100
+      {
+          12, 0x6D, 41
+      }, // 0000 0110 1101
+      {
+          12, 0xC8, 128
+      }, // 0000 1100 1000
+      {
+          12, 0xC9, 192
+      }, // 0000 1100 1001
+      {
+          12, 0xCA, 26
+      }, // 0000 1100 1010
+      {
+          12, 0xCB, 27
+      }, // 0000 1100 1011
+      {
+          12, 0xCC, 28
+      }, // 0000 1100 1100
+      {
+          12, 0xCD, 29
+      }, // 0000 1100 1101
+      {
+          12, 0xD2, 34
+      }, // 0000 1101 0010
+      {
+          12, 0xD3, 35
+      }, // 0000 1101 0011
+      {
+          12, 0xD4, 36
+      }, // 0000 1101 0100
+      {
+          12, 0xD5, 37
+      }, // 0000 1101 0101
+      {
+          12, 0xD6, 38
+      }, // 0000 1101 0110
+      {
+          12, 0xD7, 39
+      }, // 0000 1101 0111
+      {
+          12, 0xDA, 42
+      }, // 0000 1101 1010
+      {
+          12, 0xDB, 43
+      }, // 0000 1101 1011
+      {
+          13, 0x4A, 640
+      }, // 0000 0010 0101 0
+      {
+          13, 0x4B, 704
+      }, // 0000 0010 0101 1
+      {
+          13, 0x4C, 768
+      }, // 0000 0010 0110 0
+      {
+          13, 0x4D, 832
+      }, // 0000 0010 0110 1
+      {
+          13, 0x52, 1280
+      }, // 0000 0010 1001 0
+      {
+          13, 0x53, 1344
+      }, // 0000 0010 1001 1
+      {
+          13, 0x54, 1408
+      }, // 0000 0010 1010 0
+      {
+          13, 0x55, 1472
+      }, // 0000 0010 1010 1
+      {
+          13, 0x5A, 1536
+      }, // 0000 0010 1101 0
+      {
+          13, 0x5B, 1600
+      }, // 0000 0010 1101 1
+      {
+          13, 0x64, 1664
+      }, // 0000 0011 0010 0
+      {
+          13, 0x65, 1728
+      }, // 0000 0011 0010 1
+      {
+          13, 0x6C, 512
+      }, // 0000 0011 0110 0
+      {
+          13, 0x6D, 576
+      }, // 0000 0011 0110 1
+      {
+          13, 0x72, 896
+      }, // 0000 0011 1001 0
+      {
+          13, 0x73, 960
+      }, // 0000 0011 1001 1
+      {
+          13, 0x74, 1024
+      }, // 0000 0011 1010 0
+      {
+          13, 0x75, 1088
+      }, // 0000 0011 1010 1
+      {
+          13, 0x76, 1152
+      }, // 0000 0011 1011 0
+      {
+          13, 0x77, 1216
+      }
+  // 0000 0011 1011 1
+  };
+  public static final int MAX_BLACK_RUN = 2560;
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRDecompressor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRDecompressor.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRDecompressor.java
new file mode 100644
index 0000000..b1ab753
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRDecompressor.java
@@ -0,0 +1,612 @@
+/**
+ * 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.decoder.mmr;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+
+/**
+ * A decompressor for MMR compression.
+ */
+public class MMRDecompressor {
+
+  private int width;
+  private int height;
+
+  /**
+   * A class encapsulating the compressed raw data.
+   */
+  private final class RunData {
+    private static final int MAX_RUN_DATA_BUFFER = 1024 << 7; // 1024 * 128
+    private static final int MIN_RUN_DATA_BUFFER = 3; // min. bytes to decompress
+    private static final int CODE_OFFSET = 24;
+
+    /** Compressed data stream. */
+    ImageInputStream stream;
+
+    int offset;
+    int lastOffset = 0;
+    int lastCode = 0;
+
+    byte buffer[];
+    int bufferBase;
+    int bufferTop;
+
+    RunData(ImageInputStream stream) {
+      this.stream = stream;
+      offset = 0;
+      lastOffset = 1;
+
+      try {
+        long len = stream.length();
+
+        len = Math.min(Math.max(MIN_RUN_DATA_BUFFER, len), MAX_RUN_DATA_BUFFER);
+
+        buffer = new byte[(int) len];
+        fillBuffer(0);
+      } catch (IOException e) {
+        buffer = new byte[10];
+        e.printStackTrace();
+      }
+    }
+
+    private final Code uncompressGetCode(Code table[]) {
+      return uncompressGetCodeLittleEndian(table);
+    }
+
+    private final Code uncompressGetCodeLittleEndian(Code table[]) {
+      final int code = uncompressGetNextCodeLittleEndian() & 0xffffff;
+      Code result = table[code >> CODE_OFFSET - FIRST_LEVEL_TABLE_SIZE];
+
+      // perform second-level lookup
+      if (null != result && null != result.subTable) {
+        result = result.subTable[(code >> CODE_OFFSET - FIRST_LEVEL_TABLE_SIZE - SECOND_LEVEL_TABLE_SIZE)
+            & SECOND_LEVEL_TABLE_MASK];
+      }
+
+      return result;
+    }
+
+    /**
+     * Fill up the code word in little endian mode. This is a hotspot, therefore the algorithm is
+     * heavily optimised. For the frequent cases (i.e. short words) we try to get away with as
+     * little work as possible. <br>
+     * This method returns code words of 16 bits, which are aligned to the 24th bit. The lowest 8
+     * bits are used as a "queue" of bits so that an access to the actual data is only needed, when
+     * this queue becomes empty.
+     */
+    private final int uncompressGetNextCodeLittleEndian() {
+      try {
+
+        // the number of bits to fill (offset difference)
+        int bitsToFill = offset - lastOffset;
+
+        // check whether we can refill, or need to fill in absolute mode
+        if (bitsToFill < 0 || bitsToFill > 24) {
+          // refill at absolute offset
+          int byteOffset = (offset >> 3) - bufferBase; // offset>>3 is equivalent to offset/8
+
+          if (byteOffset >= bufferTop) {
+            byteOffset += bufferBase;
+            fillBuffer(byteOffset);
+            byteOffset -= bufferBase;
+          }
+
+          lastCode = (buffer[byteOffset] & 0xff) << 16 | (buffer[byteOffset + 1] & 0xff) << 8
+              | (buffer[byteOffset + 2] & 0xff);
+
+          int bitOffset = offset & 7; // equivalent to offset%8
+          lastCode <<= bitOffset;
+        } else {
+          // the offset to the next byte boundary as seen from the last offset
+          int bitOffset = lastOffset & 7;
+          final int avail = 7 - bitOffset;
+
+          // check whether there are enough bits in the "queue"
+          if (bitsToFill <= avail) {
+            lastCode <<= bitsToFill;
+          } else {
+            int byteOffset = (lastOffset >> 3) + 3 - bufferBase;
+
+            if (byteOffset >= bufferTop) {
+              byteOffset += bufferBase;
+              fillBuffer(byteOffset);
+              byteOffset -= bufferBase;
+            }
+
+            bitOffset = 8 - bitOffset;
+            do {
+              lastCode <<= bitOffset;
+              lastCode |= buffer[byteOffset] & 0xff;
+              bitsToFill -= bitOffset;
+              byteOffset++;
+              bitOffset = 8;
+            } while (bitsToFill >= 8);
+
+            lastCode <<= bitsToFill; // shift the rest
+          }
+        }
+        lastOffset = offset;
+
+        return lastCode;
+      } catch (IOException e) {
+        // will this actually happen? only with broken data, I'd say.
+        throw new ArrayIndexOutOfBoundsException("Corrupted RLE data caused by an IOException while reading raw data: "
+            + e.toString());
+      }
+    }
+
+    private void fillBuffer(int byteOffset) throws IOException {
+      bufferBase = byteOffset;
+      synchronized (stream) {
+        try {
+          stream.seek(byteOffset);
+          bufferTop = stream.read(buffer);
+        } catch (EOFException e) {
+          // you never know which kind of EOF will kick in
+          bufferTop = -1;
+        }
+        // check filling degree
+        if (bufferTop > -1 && bufferTop < 3) {
+          // CK: if filling degree is too small,
+          // smoothly fill up to the next three bytes or substitute with with
+          // empty bytes
+          int read = 0;
+          while (bufferTop < 3) {
+            try {
+              read = stream.read();
+            } catch (EOFException e) {
+              read = -1;
+            }
+            buffer[bufferTop++] = read == -1 ? 0 : (byte) (read & 0xff);
+          }
+        }
+      }
+      // leave some room, in order to save a few tests in the calling code
+      bufferTop -= 3;
+
+      if (bufferTop < 0) {
+        // if we're at EOF, just supply zero-bytes
+        Arrays.fill(buffer, (byte) 0);
+        bufferTop = buffer.length - 3;
+      }
+    }
+
+    /**
+     * Skip to next byte
+     */
+    private void align() {
+      offset = ((offset + 7) >> 3) << 3;
+    }
+  }
+
+  private static final class Code {
+    Code subTable[] = null;
+
+    final int bitLength, codeWord, runLength;
+
+    Code(int codeData[]) {
+      bitLength = codeData[0];
+      codeWord = codeData[1];
+      runLength = codeData[2];
+    }
+
+    public String toString() {
+      return bitLength + "/" + codeWord + "/" + runLength;
+    }
+
+    /**
+     * @see java.lang.Object#equals(Object)
+     */
+    public boolean equals(Object obj) {
+      return (obj instanceof Code) && //
+          ((Code) obj).bitLength == bitLength && //
+          ((Code) obj).codeWord == codeWord && //
+          ((Code) obj).runLength == runLength;
+    }
+  }
+
+  private static final int FIRST_LEVEL_TABLE_SIZE = 8;
+  private static final int FIRST_LEVEL_TABLE_MASK = (1 << FIRST_LEVEL_TABLE_SIZE) - 1;
+  private static final int SECOND_LEVEL_TABLE_SIZE = 5;
+  private static final int SECOND_LEVEL_TABLE_MASK = (1 << SECOND_LEVEL_TABLE_SIZE) - 1;
+
+  private static Code whiteTable[] = null;
+  private static Code blackTable[] = null;
+  private static Code modeTable[] = null;
+
+  private RunData data;
+
+  private synchronized final static void initTables() {
+    if (null == whiteTable) {
+      whiteTable = createLittleEndianTable(MMRConstants.WhiteCodes);
+      blackTable = createLittleEndianTable(MMRConstants.BlackCodes);
+      modeTable = createLittleEndianTable(MMRConstants.ModeCodes);
+    }
+  }
+
+  private final int uncompress2D(RunData runData, int[] referenceOffsets, int refRunLength, int[] runOffsets, int width) {
+
+    int referenceBufferOffset = 0;
+    int currentBufferOffset = 0;
+    int currentLineBitPosition = 0;
+
+    boolean whiteRun = true; // Always start with a white run
+    Code code = null; // Storage var for current code being processed
+
+    referenceOffsets[refRunLength] = referenceOffsets[refRunLength + 1] = width;
+    referenceOffsets[refRunLength + 2] = referenceOffsets[refRunLength + 3] = width + 1;
+
+    try {
+      decodeLoop : while (currentLineBitPosition < width) {
+
+        // Get the mode code
+        code = runData.uncompressGetCode(modeTable);
+
+        if (code == null) {
+          runData.offset++;
+          break decodeLoop;
+        }
+
+        // Add the code length to the bit offset
+        runData.offset += code.bitLength;
+
+        switch (code.runLength){
+          case MMRConstants.CODE_V0 :
+            currentLineBitPosition = referenceOffsets[referenceBufferOffset];
+            break;
+
+          case MMRConstants.CODE_VR1 :
+            currentLineBitPosition = referenceOffsets[referenceBufferOffset] + 1;
+            break;
+
+          case MMRConstants.CODE_VL1 :
+            currentLineBitPosition = referenceOffsets[referenceBufferOffset] - 1;
+            break;
+
+          case MMRConstants.CODE_H :
+            for (int ever = 1; ever > 0;) {
+
+              code = runData.uncompressGetCode(whiteRun == true ? whiteTable : blackTable);
+
+              if (code == null)
+                break decodeLoop;
+
+              runData.offset += code.bitLength;
+              if (code.runLength < 64) {
+                if (code.runLength < 0) {
+                  runOffsets[currentBufferOffset++] = currentLineBitPosition;
+                  code = null;
+                  break decodeLoop;
+                }
+                currentLineBitPosition += code.runLength;
+                runOffsets[currentBufferOffset++] = currentLineBitPosition;
+                break;
+              }
+              currentLineBitPosition += code.runLength;
+            }
+
+            final int firstHalfBitPos = currentLineBitPosition;
+            for (int ever1 = 1; ever1 > 0;) {
+              code = runData.uncompressGetCode(whiteRun != true ? whiteTable : blackTable);
+              if (code == null)
+                break decodeLoop;
+
+              runData.offset += code.bitLength;
+              if (code.runLength < 64) {
+                if (code.runLength < 0) {
+                  runOffsets[currentBufferOffset++] = currentLineBitPosition;
+                  break decodeLoop;
+                }
+                currentLineBitPosition += code.runLength;
+                // don't generate 0-length run at EOL for cases where the line ends in an H-run.
+                if (currentLineBitPosition < width || currentLineBitPosition != firstHalfBitPos)
+                  runOffsets[currentBufferOffset++] = currentLineBitPosition;
+                break;
+              }
+              currentLineBitPosition += code.runLength;
+            }
+
+            while (currentLineBitPosition < width && referenceOffsets[referenceBufferOffset] <= currentLineBitPosition) {
+              referenceBufferOffset += 2;
+            }
+            continue decodeLoop;
+
+          case MMRConstants.CODE_P :
+            referenceBufferOffset++;
+            currentLineBitPosition = referenceOffsets[referenceBufferOffset++];
+            continue decodeLoop;
+
+          case MMRConstants.CODE_VR2 :
+            currentLineBitPosition = referenceOffsets[referenceBufferOffset] + 2;
+            break;
+
+          case MMRConstants.CODE_VL2 :
+            currentLineBitPosition = referenceOffsets[referenceBufferOffset] - 2;
+            break;
+
+          case MMRConstants.CODE_VR3 :
+            currentLineBitPosition = referenceOffsets[referenceBufferOffset] + 3;
+            break;
+
+          case MMRConstants.CODE_VL3 :
+            currentLineBitPosition = referenceOffsets[referenceBufferOffset] - 3;
+            break;
+
+          case MMRConstants.EOL :
+          default :
+            System.err.println("Should not happen!");
+            // Possibly MMR Decoded
+            if (runData.offset == 12 && code.runLength == MMRConstants.EOL) {
+              runData.offset = 0;
+              uncompress1D(runData, referenceOffsets, width);
+              runData.offset++;
+              uncompress1D(runData, runOffsets, width);
+              int retCode = uncompress1D(runData, referenceOffsets, width);
+              runData.offset++;
+              return retCode;
+            }
+            currentLineBitPosition = width;
+            continue decodeLoop;
+        }
+
+        // Only vertical modes get this far
+        if (currentLineBitPosition <= width) {
+          whiteRun = !whiteRun;
+
+          runOffsets[currentBufferOffset++] = currentLineBitPosition;
+
+          if (referenceBufferOffset > 0) {
+            referenceBufferOffset--;
+          } else {
+            referenceBufferOffset++;
+          }
+
+          while (currentLineBitPosition < width && referenceOffsets[referenceBufferOffset] <= currentLineBitPosition) {
+            referenceBufferOffset += 2;
+          }
+        }
+      }
+    } catch (Throwable t) {
+      StringBuffer strBuf = new StringBuffer();
+      strBuf.append("whiteRun           = ");
+      strBuf.append(whiteRun);
+      strBuf.append("\n");
+      strBuf.append("code               = ");
+      strBuf.append(code);
+      strBuf.append("\n");
+      strBuf.append("refOffset          = ");
+      strBuf.append(referenceBufferOffset);
+      strBuf.append("\n");
+      strBuf.append("curOffset          = ");
+      strBuf.append(currentBufferOffset);
+      strBuf.append("\n");
+      strBuf.append("bitPos             = ");
+      strBuf.append(currentLineBitPosition);
+      strBuf.append("\n");
+      strBuf.append("runData.offset = ");
+      strBuf.append(runData.offset);
+      strBuf.append(" ( byte:");
+      strBuf.append(runData.offset / 8);
+      strBuf.append(", bit:");
+      strBuf.append(runData.offset & 0x07);
+      strBuf.append(" )");
+
+      System.out.println(strBuf.toString());
+
+      return MMRConstants.EOF;
+    }
+
+    if (runOffsets[currentBufferOffset] != width) {
+      runOffsets[currentBufferOffset] = width;
+    }
+
+    if (code == null) {
+      return MMRConstants.EOL;
+    }
+    return currentBufferOffset;
+  }
+
+  public MMRDecompressor(int width, int height, ImageInputStream stream) {
+    this.width = width;
+    this.height = height;
+
+    data = new RunData(stream);
+
+    initTables();
+  }
+
+  public Bitmap uncompress() {
+    final Bitmap result = new Bitmap(width, height);
+
+    int[] currentOffsets = new int[width + 5];
+    int[] referenceOffsets = new int[width + 5];
+    referenceOffsets[0] = width;
+    int refRunLength = 1;
+
+    int count = 0;
+
+    for (int line = 0; line < height; line++) {
+      count = uncompress2D(data, referenceOffsets, refRunLength, currentOffsets, width);
+
+      if (count == MMRConstants.EOF) {
+        break;
+      }
+
+      if (count > 0) {
+        fillBitmap(result, line, currentOffsets, count);
+      }
+
+      // Swap lines
+      int tempOffsets[] = referenceOffsets;
+      referenceOffsets = currentOffsets;
+      currentOffsets = tempOffsets;
+      refRunLength = count;
+    }
+
+    detectAndSkipEOL();
+
+    data.align();
+
+    return result;
+  }
+
+  private void detectAndSkipEOL() {
+    while (true) {
+      Code code = data.uncompressGetCode(modeTable);
+      if (null != code && code.runLength == MMRConstants.EOL) {
+        data.offset += code.bitLength;
+      } else
+        break;
+    }
+  }
+
+  private void fillBitmap(Bitmap result, int line, int[] currentOffsets, int count) {
+
+    int x = 0;
+    int targetByte = result.getByteIndex(0, line);
+    byte targetByteValue = 0;
+    for (int index = 0; index < count; index++) {
+
+      final int offset = currentOffsets[index];
+      byte value;
+
+      if ((index & 1) == 0) {
+        value = 0;
+      } else {
+        value = 1;
+      }
+
+      while (x < offset) {
+        targetByteValue = (byte) ((targetByteValue << 1) | value);
+        x++;
+
+        if ((x & 7) == 0) {
+          result.setByte(targetByte++, targetByteValue);
+          targetByteValue = 0;
+        }
+      }
+    }
+
+    if ((x & 7) != 0) {
+      targetByteValue <<= 8 - (x & 7);
+      result.setByte(targetByte, targetByteValue);
+    }
+  }
+
+  private final int uncompress1D(RunData runData, int[] runOffsets, int width) {
+
+    boolean whiteRun = true;
+    int iBitPos = 0;
+    Code code = null;
+    int refOffset = 0;
+
+    loop : while (iBitPos < width) {
+      while (true) {
+        if (whiteRun) {
+          code = runData.uncompressGetCode(whiteTable);
+        } else {
+          code = runData.uncompressGetCode(blackTable);
+        }
+
+        runData.offset += code.bitLength;
+
+        if (code.runLength < 0) {
+          break loop;
+        }
+
+        iBitPos += code.runLength;
+
+        if (code.runLength < 64) {
+          whiteRun = !whiteRun;
+          runOffsets[refOffset++] = iBitPos;
+          break;
+        }
+      }
+    }
+
+    if (runOffsets[refOffset] != width) {
+      runOffsets[refOffset] = width;
+    }
+
+    return code != null && code.runLength != MMRConstants.EOL ? refOffset : MMRConstants.EOL;
+  }
+
+  /**
+   * For little endian, the tables are structured like this:
+   * 
+   * <pre>
+   *  v--------v length = FIRST_LEVEL_TABLE_LENGTH
+   *                v-----v length = SECOND_LEVEL_TABLE_LENGTH
+   * 
+   *  A code word which fits into the first level table (length=3)
+   *  [Cccvvvvv]
+   * 
+   *  A code word which needs the second level table also (length=10)
+   *  [Cccccccc] -&gt; [ccvvv]
+   * 
+   *  &quot;C&quot; denotes the first code word bit
+   *  &quot;c&quot; denotes a code word bit
+   *  &quot;v&quot; denotes a variant bit
+   * </pre>
+   * 
+   */
+  private static Code[] createLittleEndianTable(int codes[][]) {
+    final Code firstLevelTable[] = new Code[FIRST_LEVEL_TABLE_MASK + 1];
+    for (int i = 0; i < codes.length; i++) {
+      final Code code = new Code(codes[i]);
+
+      if (code.bitLength <= FIRST_LEVEL_TABLE_SIZE) {
+        final int variantLength = FIRST_LEVEL_TABLE_SIZE - code.bitLength;
+        final int baseWord = code.codeWord << variantLength;
+
+        for (int variant = (1 << variantLength) - 1; variant >= 0; variant--) {
+          final int index = baseWord | variant;
+          firstLevelTable[index] = code;
+        }
+      } else {
+        // init second level table
+        final int firstLevelIndex = code.codeWord >>> code.bitLength - FIRST_LEVEL_TABLE_SIZE;
+
+        if (firstLevelTable[firstLevelIndex] == null) {
+          final Code firstLevelCode = new Code(new int[3]);
+          firstLevelCode.subTable = new Code[SECOND_LEVEL_TABLE_MASK + 1];
+          firstLevelTable[firstLevelIndex] = firstLevelCode;
+        }
+
+        // fill second level table
+        if (code.bitLength <= FIRST_LEVEL_TABLE_SIZE + SECOND_LEVEL_TABLE_SIZE) {
+          final Code secondLevelTable[] = firstLevelTable[firstLevelIndex].subTable;
+          final int variantLength = FIRST_LEVEL_TABLE_SIZE + SECOND_LEVEL_TABLE_SIZE - code.bitLength;
+          final int baseWord = (code.codeWord << variantLength) & SECOND_LEVEL_TABLE_MASK;
+
+          for (int variant = (1 << variantLength) - 1; variant >= 0; variant--) {
+            secondLevelTable[baseWord | variant] = code;
+          }
+        } else
+          throw new IllegalArgumentException("Code table overflow in MMRDecompressor");
+      }
+    }
+    return firstLevelTable;
+  }
+}
\ 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/err/IntegerMaxValueException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/err/IntegerMaxValueException.java b/src/main/java/org/apache/pdfbox/jbig2/err/IntegerMaxValueException.java
new file mode 100644
index 0000000..3f1b479
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/err/IntegerMaxValueException.java
@@ -0,0 +1,42 @@
+/**
+ * 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.err;
+
+/**
+ * Can be used if the maximum value limit of an integer is exceeded.
+ */
+public class IntegerMaxValueException extends JBIG2Exception {
+
+  private static final long serialVersionUID = -5534202639860867867L;
+
+  public IntegerMaxValueException() {
+  }
+
+  public IntegerMaxValueException(String message) {
+    super(message);
+  }
+
+  public IntegerMaxValueException(Throwable cause) {
+    super(cause);
+  }
+
+  public IntegerMaxValueException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/err/InvalidHeaderValueException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/err/InvalidHeaderValueException.java b/src/main/java/org/apache/pdfbox/jbig2/err/InvalidHeaderValueException.java
new file mode 100644
index 0000000..c1ed069
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/err/InvalidHeaderValueException.java
@@ -0,0 +1,42 @@
+/**
+ * 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.err;
+
+/**
+ * Can be used if a segment header value is invalid.
+ */
+public class InvalidHeaderValueException extends JBIG2Exception {
+
+  private static final long serialVersionUID = -5534202639860867867L;
+
+  public InvalidHeaderValueException() {
+  }
+
+  public InvalidHeaderValueException(String message) {
+    super(message);
+  }
+
+  public InvalidHeaderValueException(Throwable cause) {
+    super(cause);
+  }
+
+  public InvalidHeaderValueException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/err/JBIG2Exception.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/err/JBIG2Exception.java b/src/main/java/org/apache/pdfbox/jbig2/err/JBIG2Exception.java
new file mode 100644
index 0000000..2a53fe1
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/err/JBIG2Exception.java
@@ -0,0 +1,42 @@
+/**
+ * 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.err;
+
+/**
+ * Identifies a JBIG2 exception.
+ */
+public class JBIG2Exception extends Exception {
+
+  private static final long serialVersionUID = 5063673874564442169L;
+
+  public JBIG2Exception() {
+  }
+
+  public JBIG2Exception(String message) {
+    super(message);
+  }
+
+  public JBIG2Exception(Throwable cause) {
+    super(cause);
+  }
+
+  public JBIG2Exception(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/image/BitmapScanline.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/image/BitmapScanline.java b/src/main/java/org/apache/pdfbox/jbig2/image/BitmapScanline.java
new file mode 100644
index 0000000..1babb59
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/image/BitmapScanline.java
@@ -0,0 +1,127 @@
+/**
+ * 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.image;
+
+import java.awt.image.WritableRaster;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+
+final class BitmapScanline extends Scanline {
+
+  private Bitmap bitmap;
+  private WritableRaster raster;
+
+  private int[] lineBuffer;
+
+  public BitmapScanline(final Bitmap src, final WritableRaster dst, final int width) {
+    super(width);
+    this.bitmap = src;
+    this.raster = dst;
+    lineBuffer = new int[length];
+  }
+
+  @Override
+  protected void clear() {
+    lineBuffer = new int[length];
+  }
+
+  @Override
+  protected void fetch(int x, final int y) {
+    lineBuffer = new int[length]; // really required?
+    int srcByteIdx = bitmap.getByteIndex(x, y);
+    while (x < length) {
+      final byte srcByte = (byte) ~bitmap.getByte(srcByteIdx++);
+      final int bits = bitmap.getWidth() - x > 8 ? 8 : bitmap.getWidth() - x;
+      for (int bitPosition = bits - 1; bitPosition >= 0; bitPosition--, x++) {
+        if (((srcByte >> bitPosition) & 0x1) != 0)
+          lineBuffer[x] = 255;
+      }
+    }
+  }
+
+  @Override
+  protected void filter(final int[] preShift, final int[] postShift, final Weighttab[] tabs, final Scanline dst) {
+    final BitmapScanline dstBitmapScanline = (BitmapScanline) dst;
+    final int dstLength = dst.length;
+
+    // start sum at 1 << shift - 1 for rounding
+    final int start = 1 << postShift[0] - 1;
+    final int srcBuffer[] = lineBuffer;
+    final int dstBuffer[] = dstBitmapScanline.lineBuffer;
+
+    // the next two blocks are duplicated except for the missing shift operation if preShift == 0.
+    final int preShift0 = preShift[0];
+    final int postShift0 = postShift[0];
+    if (preShift0 != 0) {
+      for (int dstIndex = 0, tab = 0; tab < dstLength; tab++) {
+        final Weighttab weightTab = tabs[tab];
+        final int weights = weightTab.weights.length;
+
+        int sum = start;
+        for (int weightIndex = 0, srcIndex = weightTab.i0; weightIndex < weights && srcIndex < srcBuffer.length; weightIndex++) {
+          sum += weightTab.weights[weightIndex] * (srcBuffer[srcIndex++] >> preShift0);
+        }
+
+        final int t = sum >> postShift0;
+        dstBuffer[dstIndex++] = t < 0 ? 0 : t > 255 ? 255 : t;
+      }
+    } else {
+      for (int dstIndex = 0, tab = 0; tab < dstLength; tab++) {
+        final Weighttab weightTab = tabs[tab];
+        final int weights = weightTab.weights.length;
+
+        int sum = start;
+        for (int weightIndex = 0, srcIndex = weightTab.i0; weightIndex < weights && srcIndex < srcBuffer.length; weightIndex++) {
+          sum += weightTab.weights[weightIndex] * srcBuffer[srcIndex++];
+        }
+
+        dstBuffer[dstIndex++] = sum >> postShift0;
+      }
+    }
+  }
+
+  @Override
+  protected void accumulate(final int weight, final Scanline dst) {
+    final BitmapScanline dstBitmapScanline = (BitmapScanline) dst;
+
+    final int srcBuffer[] = lineBuffer;
+    final int dstBuffer[] = dstBitmapScanline.lineBuffer;
+
+    for (int b = 0; b < dstBuffer.length; b++)
+      dstBuffer[b] += weight * srcBuffer[b];
+  }
+
+  @Override
+  protected void shift(final int[] shift) {
+    final int shift0 = shift[0];
+    final int half = 1 << shift0 - 1;
+
+    final int srcBuffer[] = lineBuffer;
+
+    for (int b = 0; b < srcBuffer.length; b++) {
+      final int pixel = srcBuffer[b] + half >> shift0;
+      srcBuffer[b] = pixel < 0 ? 0 : pixel > 255 ? 255 : pixel;
+    }
+  }
+
+  @Override
+  protected void store(final int x, final int y) {
+    raster.setSamples(x, y, length, 1, 0, lineBuffer);
+  }
+
+}
\ 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/image/Bitmaps.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/image/Bitmaps.java b/src/main/java/org/apache/pdfbox/jbig2/image/Bitmaps.java
new file mode 100644
index 0000000..d73f2f8
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/image/Bitmaps.java
@@ -0,0 +1,525 @@
+/**
+ * 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.image;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+
+import javax.imageio.ImageReadParam;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2ReadParam;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+
+public class Bitmaps {
+
+  public static WritableRaster asRaster(final Bitmap bitmap) {
+    return asRaster(bitmap, FilterType.Gaussian);
+  }
+
+  public static WritableRaster asRaster(final Bitmap bitmap, final FilterType filterType) {
+    if (bitmap == null)
+      throw new IllegalArgumentException("bitmap must not be null");
+
+    final JBIG2ReadParam param = new JBIG2ReadParam(1, 1, 0, 0, new Rectangle(0, 0, bitmap.getWidth(),
+        bitmap.getHeight()), new Dimension(bitmap.getWidth(), bitmap.getHeight()));
+
+    return asRaster(bitmap, param, filterType);
+  }
+
+  public static WritableRaster asRaster(Bitmap bitmap, final ImageReadParam param, final FilterType filterType) {
+    if (bitmap == null)
+      throw new IllegalArgumentException("bitmap must not be null");
+
+    if (param == null)
+      throw new IllegalArgumentException("param must not be null");
+
+    final Dimension sourceRenderSize = param.getSourceRenderSize();
+
+    double scaleX;
+    double scaleY;
+    if (sourceRenderSize != null) {
+      scaleX = sourceRenderSize.getWidth() / bitmap.getWidth();
+      scaleY = sourceRenderSize.getHeight() / bitmap.getHeight();
+    } else {
+      scaleX = scaleY = 1;
+    }
+
+    Rectangle sourceRegion = param.getSourceRegion();
+    if (sourceRegion != null && !bitmap.getBounds().equals(sourceRegion)) {
+      // make sure we don't request an area outside of the source bitmap
+      sourceRegion = bitmap.getBounds().intersection(sourceRegion);
+
+      // get region of interest
+      bitmap = Bitmaps.extract(sourceRegion, bitmap);
+    }
+
+    /*
+     * Subsampling is the advance of columns/rows for each pixel in the according direction. The
+     * resulting image's quality will be bad because we loose information if we step over
+     * columns/rows. For example, a thin line (1 pixel high) may disappear completely. To avoid this
+     * we use resize filters if scaling will be performed anyway. The resize filters use scale
+     * factors, one for horizontal and vertical direction. We care about the given subsampling steps
+     * by adjusting the scale factors. If scaling is not performed, subsampling is performed in its
+     * original manner.
+     */
+
+    final boolean requiresScaling = scaleX != 1 || scaleY != 1;
+
+    final boolean requiresXSubsampling = param.getSourceXSubsampling() != 1;
+    final boolean requiresYSubsampling = param.getSourceYSubsampling() != 1;
+
+    if (requiresXSubsampling && requiresYSubsampling) {
+      // Apply vertical and horizontal subsampling
+      if (requiresScaling) {
+        scaleX /= (double) param.getSourceXSubsampling();
+        scaleY /= (double) param.getSourceYSubsampling();
+      } else {
+        bitmap = subsample(bitmap, param);
+      }
+    } else {
+      if (requiresXSubsampling) {
+        // Apply horizontal subsampling only
+        if (requiresScaling) {
+          scaleX /= (double) param.getSourceXSubsampling();
+        } else {
+          bitmap = Bitmaps.subsampleX(bitmap, param.getSourceXSubsampling(), param.getSubsamplingXOffset());
+        }
+      }
+
+      if (requiresYSubsampling) {
+        // Apply vertical subsampling only
+        if (requiresScaling) {
+          scaleY /= (double) param.getSourceYSubsampling();
+        } else {
+          bitmap = Bitmaps.subsampleY(bitmap, param.getSourceYSubsampling(), param.getSubsamplingYOffset());
+        }
+      }
+    }
+
+    return buildRaster(bitmap, filterType, scaleX, scaleY);
+  }
+
+  private static WritableRaster buildRaster(final Bitmap bitmap, final FilterType filterType, final double scaleX,
+      final double scaleY) {
+    final Rectangle dstBounds = new Rectangle(0, 0, //
+        (int) Math.round(bitmap.getWidth() * scaleX), //
+        (int) Math.round(bitmap.getHeight() * scaleY));
+
+    final WritableRaster dst = WritableRaster.createInterleavedRaster(DataBuffer.TYPE_BYTE, dstBounds.width,
+        dstBounds.height, 1, new Point());
+
+    if (scaleX != 1 || scaleY != 1) {
+      // scaling required
+      final Resizer resizer = new Resizer(scaleX, scaleY);
+      final Filter filter = Filter.byType(filterType);
+      resizer.resize(bitmap, bitmap.getBounds() /* sourceRegion */, dst, dstBounds, filter, filter);
+    } else {
+      // scaling not required, paste bitmap into raster pixel per pixel
+      int byteIndex = 0;
+      for (int y = 0; y < bitmap.getHeight(); y++) {
+        for (int x = 0; x < bitmap.getWidth(); byteIndex++) {
+          final int pixels = (~bitmap.getByte(byteIndex)) & 0xFF;
+          final int relevantPixels = bitmap.getWidth() - x > 8 ? 8 : bitmap.getWidth() - x;
+          final int endIdx = 7 - relevantPixels;
+          for (int bytePosition = 7; bytePosition > endIdx; bytePosition--, x++) {
+            dst.setSample(x, y, 0, (pixels >> bytePosition) & 0x1);
+          }
+        }
+      }
+    }
+
+    return dst;
+  }
+
+  public static BufferedImage asBufferedImage(Bitmap bitmap) {
+    return asBufferedImage(bitmap, FilterType.Gaussian);
+  }
+
+  public static BufferedImage asBufferedImage(Bitmap bitmap, FilterType filterType) {
+    if (bitmap == null)
+      throw new IllegalArgumentException("bitmap must not be null");
+
+    final JBIG2ReadParam param = new JBIG2ReadParam(1, 1, 0, 0, new Rectangle(0, 0, bitmap.getWidth(),
+        bitmap.getHeight()), new Dimension(bitmap.getWidth(), bitmap.getHeight()));
+
+    return asBufferedImage(bitmap, param, filterType);
+  }
+
+  public static BufferedImage asBufferedImage(Bitmap bitmap, ImageReadParam param, FilterType filterType) {
+    if (bitmap == null)
+      throw new IllegalArgumentException("bitmap must not be null");
+
+    if (param == null)
+      throw new IllegalArgumentException("param must not be null");
+
+    final WritableRaster raster = asRaster(bitmap, param, filterType);
+
+    final Dimension sourceRenderSize = param.getSourceRenderSize();
+
+    final double scaleX;
+    final double scaleY;
+    if (sourceRenderSize != null) {
+      scaleX = sourceRenderSize.getWidth() / bitmap.getWidth();
+      scaleY = sourceRenderSize.getHeight() / bitmap.getHeight();
+    } else {
+      scaleX = scaleY = 1d;
+    }
+
+    ColorModel cm = null;
+    final boolean isScaled = scaleX != 1 || scaleY != 1;
+    if (isScaled) {
+      final int size = 256;
+      final int divisor = size - 1;
+
+      final byte[] gray = new byte[size];
+      for (int i = size - 1, s = 0; i >= 0; i--, s++) {
+        gray[i] = (byte) (255 - s * 255 / divisor);
+      }
+      cm = new IndexColorModel(8, size, gray, gray, gray);
+    } else {
+
+      cm = new IndexColorModel(8, 2, //
+          new byte[]{
+              0x00, (byte) 0xff
+          }, new byte[]{
+              0x00, (byte) 0xff
+          }, new byte[]{
+              0x00, (byte) 0xff
+          });
+    }
+
+    return new BufferedImage(cm, raster, false, null);
+  }
+
+  /**
+   * Returns the specified rectangle area of the bitmap.
+   * 
+   * @param roi - A {@link Rectangle} that specifies the requested image section.
+   * @return A {@code Bitmap} that represents the requested image section.
+   */
+  public static Bitmap extract(final Rectangle roi, final Bitmap src) {
+    final Bitmap dst = new Bitmap(roi.width, roi.height);
+
+    final int upShift = roi.x & 0x07;
+    final int downShift = 8 - upShift;
+    int dstLineStartIdx = 0;
+
+    final int padding = (8 - dst.getWidth() & 0x07);
+    int srcLineStartIdx = src.getByteIndex(roi.x, roi.y);
+    int srcLineEndIdx = src.getByteIndex(roi.x + roi.width - 1, roi.y);
+    final boolean usePadding = dst.getRowStride() == srcLineEndIdx + 1 - srcLineStartIdx;
+
+    for (int y = roi.y; y < roi.getMaxY(); y++) {
+      int srcIdx = srcLineStartIdx;
+      int dstIdx = dstLineStartIdx;
+
+      if (srcLineStartIdx == srcLineEndIdx) {
+        final byte pixels = (byte) (src.getByte(srcIdx) << upShift);
+        dst.setByte(dstIdx, unpad(padding, pixels));
+      } else if (upShift == 0) {
+        for (int x = srcLineStartIdx; x <= srcLineEndIdx; x++) {
+          byte value = src.getByte(srcIdx++);
+
+          if (x == srcLineEndIdx && usePadding) {
+            value = unpad(padding, value);
+          }
+
+          dst.setByte(dstIdx++, value);
+        }
+      } else {
+        copyLine(src, dst, upShift, downShift, padding, srcLineStartIdx, srcLineEndIdx, usePadding, srcIdx, dstIdx);
+      }
+
+      srcLineStartIdx += src.getRowStride();
+      srcLineEndIdx += src.getRowStride();
+      dstLineStartIdx += dst.getRowStride();
+    }
+
+    return dst;
+  }
+
+  private static void copyLine(Bitmap src, Bitmap dst, int sourceUpShift, int sourceDownShift, int padding,
+      int firstSourceByteOfLine, int lastSourceByteOfLine, boolean usePadding, int sourceOffset, int targetOffset) {
+    for (int x = firstSourceByteOfLine; x < lastSourceByteOfLine; x++) {
+
+      if (sourceOffset + 1 < src.getByteArray().length) {
+        final boolean isLastByte = x + 1 == lastSourceByteOfLine;
+        byte value = (byte) (src.getByte(sourceOffset++) << sourceUpShift | (src.getByte(sourceOffset) & 0xff) >>> sourceDownShift);
+
+        if (isLastByte && !usePadding) {
+          value = unpad(padding, value);
+        }
+
+        dst.setByte(targetOffset++, value);
+
+        if (isLastByte && usePadding) {
+          value = unpad(padding, (byte) ((src.getByte(sourceOffset) & 0xff) << sourceUpShift));
+          dst.setByte(targetOffset, value);
+        }
+
+      } else {
+        final byte value = (byte) (src.getByte(sourceOffset++) << sourceUpShift & 0xff);
+        dst.setByte(targetOffset++, value);
+      }
+    }
+  }
+
+  /**
+   * Removes unnecessary bits from a byte.
+   * 
+   * @param padding - The amount of unnecessary bits.
+   * @param value - The byte that should be cleaned up.
+   * @return A cleaned byte.
+   */
+  private static byte unpad(int padding, byte value) {
+    return (byte) (value >> padding << padding);
+  }
+
+  public static Bitmap subsample(Bitmap src, ImageReadParam param) {
+    if (src == null)
+      throw new IllegalArgumentException("src must not be null");
+
+    if (param == null)
+      throw new IllegalArgumentException("param must not be null");
+
+    final int xSubsampling = param.getSourceXSubsampling();
+    final int ySubsampling = param.getSourceYSubsampling();
+    final int xSubsamplingOffset = param.getSubsamplingXOffset();
+    final int ySubsamplingOffset = param.getSubsamplingYOffset();
+
+    final int dstWidth = (src.getWidth() - xSubsamplingOffset) / xSubsampling;
+    final int dstHeight = (src.getHeight() - ySubsamplingOffset) / ySubsampling;
+
+    final Bitmap dst = new Bitmap(dstWidth, dstHeight);
+
+    for (int yDst = 0, ySrc = ySubsamplingOffset; yDst < dst.getHeight(); yDst++, ySrc += ySubsampling) {
+      for (int xDst = 0, xSrc = xSubsamplingOffset; xDst < dst.getWidth(); xDst++, xSrc += xSubsampling) {
+        final byte pixel = src.getPixel(xSrc, ySrc);
+        if (pixel != 0)
+          dst.setPixel(xDst, yDst, pixel);
+      }
+    }
+
+    return dst;
+  }
+
+  public static Bitmap subsampleX(Bitmap src, final int xSubsampling, final int xSubsamplingOffset) {
+    if (src == null)
+      throw new IllegalArgumentException("src must not be null");
+
+    final int dstHeight = (src.getWidth() - xSubsamplingOffset) / xSubsampling;
+    final Bitmap dst = new Bitmap(src.getWidth(), dstHeight);
+
+    for (int yDst = 0; yDst < dst.getHeight(); yDst++) {
+      for (int xDst = 0, xSrc = xSubsamplingOffset; xDst < dst.getWidth(); xDst++, xSrc += xSubsampling) {
+        final byte pixel = src.getPixel(xSrc, yDst);
+        if (pixel != 0)
+          dst.setPixel(xDst, yDst, pixel);
+      }
+    }
+
+    return dst;
+  }
+
+  public static Bitmap subsampleY(Bitmap src, final int ySubsampling, final int ySubsamplingOffset) {
+    if (src == null)
+      throw new IllegalArgumentException("src must not be null");
+
+    final int dstWidth = (src.getWidth() - ySubsamplingOffset) / ySubsampling;
+    final Bitmap dst = new Bitmap(dstWidth, src.getHeight());
+
+    for (int yDst = 0, ySrc = ySubsamplingOffset; yDst < dst.getHeight(); yDst++, ySrc += ySubsampling) {
+      for (int xDst = 0; xDst < dst.getWidth(); xDst++) {
+        final byte pixel = src.getPixel(xDst, ySrc);
+        if (pixel != 0)
+          dst.setPixel(xDst, yDst, pixel);
+      }
+    }
+
+    return dst;
+  }
+
+  /**
+   * The method combines two given bytes with an logical operator.
+   * <p>
+   * The JBIG2 Standard specifies 5 possible combinations of bytes.<br>
+   * <p>
+   * <b>Hint:</b> Please take a look at ISO/IEC 14492:2001 (E) for detailed definition and
+   * description of the operators.
+   * 
+   * @param value1 - The value that should be combined with value2.
+   * @param value2 - The value that should be combined with value1.
+   * @param op - The specified combination operator.
+   * 
+   * @return The combination result.
+   */
+  public static byte combineBytes(byte value1, byte value2, CombinationOperator op) {
+
+    switch (op){
+      case OR :
+        return (byte) (value2 | value1);
+      case AND :
+        return (byte) (value2 & value1);
+      case XOR :
+        return (byte) (value2 ^ value1);
+      case XNOR :
+        return (byte) ~(value1 ^ value2);
+      case REPLACE :
+      default :
+        // Old value is replaced by new value.
+        return value2;
+    }
+  }
+
+  /**
+   * This method combines a given bitmap with the current instance.
+   * <p>
+   * Parts of the bitmap to blit that are outside of the target bitmap will be ignored.
+   * 
+   * @param src - The bitmap that should be combined with the one of the current instance.
+   * @param x - The x coordinate where the upper left corner of the bitmap to blit should be
+   *          positioned.
+   * @param y - The y coordinate where the upper left corner of the bitmap to blit should be
+   *          positioned.
+   * @param combinationOperator - The combination operator for combining two pixels.
+   */
+  public static void blit(Bitmap src, Bitmap dst, int x, int y, CombinationOperator combinationOperator) {
+
+    int startLine = 0;
+    int srcStartIdx = 0;
+    int srcEndIdx = (src.getRowStride() - 1);
+
+    // Ignore those parts of the source bitmap which would be placed outside the target bitmap.
+    if (x < 0) {
+      srcStartIdx = -x;
+      x = 0;
+    } else if (x + src.getWidth() > dst.getWidth()) {
+      srcEndIdx -= (src.getWidth() + x - dst.getWidth());
+    }
+
+    if (y < 0) {
+      startLine = -y;
+      y = 0;
+      srcStartIdx += src.getRowStride();
+      srcEndIdx += src.getRowStride();
+    } else if (y + src.getHeight() > dst.getHeight()) {
+      startLine = src.getHeight() + y - dst.getHeight();
+    }
+
+    final int shiftVal1 = x & 0x07;
+    final int shiftVal2 = 8 - shiftVal1;
+
+    final int padding = src.getWidth() & 0x07;
+    final int toShift = shiftVal2 - padding;
+
+    final boolean useShift = (shiftVal2 & 0x07) != 0;
+    final boolean specialCase = src.getWidth() <= ((srcEndIdx - srcStartIdx) << 3) + shiftVal2;
+
+    final int dstStartIdx = dst.getByteIndex(x, y);
+
+    final int lastLine = Math.min(src.getHeight(), startLine + dst.getHeight());
+
+    if (!useShift) {
+      blitUnshifted(src, dst, startLine, lastLine, dstStartIdx, srcStartIdx, srcEndIdx, combinationOperator);
+    } else if (specialCase) {
+      blitSpecialShifted(src, dst, startLine, lastLine, dstStartIdx, srcStartIdx, srcEndIdx, toShift, shiftVal1,
+          shiftVal2, combinationOperator);
+    } else {
+      blitShifted(src, dst, startLine, lastLine, dstStartIdx, srcStartIdx, srcEndIdx, toShift, shiftVal1, shiftVal2,
+          combinationOperator, padding);
+    }
+  }
+
+  private static void blitUnshifted(Bitmap src, Bitmap dst, int startLine, int lastLine, int dstStartIdx,
+      int srcStartIdx, int srcEndIdx, CombinationOperator op) {
+
+    for (int dstLine = startLine; dstLine < lastLine; dstLine++, dstStartIdx += dst.getRowStride(), srcStartIdx += src.getRowStride(), srcEndIdx += src.getRowStride()) {
+      int dstIdx = dstStartIdx;
+
+      // Go through the bytes in a line of the Symbol
+      for (int srcIdx = srcStartIdx; srcIdx <= srcEndIdx; srcIdx++) {
+        byte oldByte = dst.getByte(dstIdx);
+        byte newByte = src.getByte(srcIdx);
+        dst.setByte(dstIdx++, Bitmaps.combineBytes(oldByte, newByte, op));
+      }
+    }
+  }
+
+  private static void blitSpecialShifted(Bitmap src, Bitmap dst, int startLine, int lastLine, int dstStartIdx,
+      int srcStartIdx, int srcEndIdx, int toShift, int shiftVal1, int shiftVal2, CombinationOperator op) {
+
+    for (int dstLine = startLine; dstLine < lastLine; dstLine++, dstStartIdx += dst.getRowStride(), srcStartIdx += src.getRowStride(), srcEndIdx += src.getRowStride()) {
+      short register = 0;
+      int dstIdx = dstStartIdx;
+
+      // Go through the bytes in a line of the Symbol
+      for (int srcIdx = srcStartIdx; srcIdx <= srcEndIdx; srcIdx++) {
+        byte oldByte = dst.getByte(dstIdx);
+        register = (short) ((register | src.getByte(srcIdx) & 0xff) << shiftVal2);
+        byte newByte = (byte) (register >> 8);
+
+        if (srcIdx == srcEndIdx) {
+          newByte = unpad(toShift, newByte);
+        }
+
+        dst.setByte(dstIdx++, Bitmaps.combineBytes(oldByte, newByte, op));
+        register <<= shiftVal1;
+      }
+    }
+  }
+
+  private static void blitShifted(Bitmap src, Bitmap dst, int startLine, int lastLine, int dstStartIdx,
+      int srcStartIdx, int srcEndIdx, int toShift, int shiftVal1, int shiftVal2, CombinationOperator op, int padding) {
+
+    for (int dstLine = startLine; dstLine < lastLine; dstLine++, dstStartIdx += dst.getRowStride(), srcStartIdx += src.getRowStride(), srcEndIdx += src.getRowStride()) {
+      short register = 0;
+      int dstIdx = dstStartIdx;
+
+      // Go through the bytes in a line of the symbol
+      for (int srcIdx = srcStartIdx; srcIdx <= srcEndIdx; srcIdx++) {
+        byte oldByte = dst.getByte(dstIdx);
+        register = (short) ((register | src.getByte(srcIdx) & 0xff) << shiftVal2);
+
+        byte newByte = (byte) (register >> 8);
+        dst.setByte(dstIdx++, Bitmaps.combineBytes(oldByte, newByte, op));
+
+        register <<= shiftVal1;
+
+        if (srcIdx == srcEndIdx) {
+          newByte = (byte) (register >> (8 - shiftVal2));
+
+          if (padding != 0) {
+            newByte = unpad(8 + toShift, newByte);
+          }
+
+          oldByte = dst.getByte(dstIdx);
+          dst.setByte(dstIdx, Bitmaps.combineBytes(oldByte, newByte, op));
+        }
+      }
+    }
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/image/Filter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/image/Filter.java b/src/main/java/org/apache/pdfbox/jbig2/image/Filter.java
new file mode 100644
index 0000000..2b81957
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/image/Filter.java
@@ -0,0 +1,457 @@
+/**
+ * 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.image;
+
+
+abstract class Filter {
+
+  /**
+   * Find a filter name by its type.
+   * 
+   * @param type the filter type
+   * @return filter name
+   */
+  public static String nameByType(final FilterType type) {
+    if (type == null)
+      throw new IllegalArgumentException("type must not be null");
+    return type.name();
+  }
+
+  /**
+   * Find a filter type by its name.
+   * 
+   * @param name the filter name
+   * @return filter type
+   */
+  public static FilterType typeByName(final String name) {
+    if (name == null)
+      throw new IllegalArgumentException("name must not be null");
+    return FilterType.valueOf(name);
+  }
+
+  /**
+   * Find a filter by its type.
+   * 
+   * @param type the filter type
+   * @return the Filter
+   */
+  public static Filter byType(final FilterType type) {
+    switch (type){
+      case Bessel :
+        return new Bessel();
+      case Blackman :
+        return new Blackman();
+      case Box :
+        return new Box();
+      case Catrom :
+        return new Catrom();
+      case Cubic :
+        return new Cubic();
+      case Gaussian :
+        return new Gaussian();
+      case Hamming :
+        return new Hamming();
+      case Hanning :
+        return new Hanning();
+      case Hermite :
+        return new Hermite();
+      case Lanczos :
+        return new Lanczos();
+      case Mitchell :
+        return new Mitchell();
+      case Point :
+        return new Point();
+      case Quadratic :
+        return new Quadratic();
+      case Sinc :
+        return new Sinc();
+      case Triangle :
+        return new Triangle();
+    }
+    throw new IllegalArgumentException("No filter for given type.");
+  }
+
+  public static final class Bessel extends Filter {
+    public Bessel() {
+      super(false, 3.2383, 1.0);
+    }
+
+    private double J1(final double x) {
+      double p, q;
+
+      int i;
+
+      final double Pone[] = {
+          0.581199354001606143928050809e+21, -0.6672106568924916298020941484e+20, 0.2316433580634002297931815435e+19,
+          -0.3588817569910106050743641413e+17, 0.2908795263834775409737601689e+15, -0.1322983480332126453125473247e+13,
+          0.3413234182301700539091292655e+10, -0.4695753530642995859767162166e+7, 0.270112271089232341485679099e+4
+      }, Qone[] = {
+          0.11623987080032122878585294e+22, 0.1185770712190320999837113348e+20, 0.6092061398917521746105196863e+17,
+          0.2081661221307607351240184229e+15, 0.5243710262167649715406728642e+12, 0.1013863514358673989967045588e+10,
+          0.1501793594998585505921097578e+7, 0.1606931573481487801970916749e+4, 0.1e+1
+      };
+
+      p = Pone[8];
+      q = Qone[8];
+      for (i = 7; i >= 0; i--) {
+        p = p * x * x + Pone[i];
+        q = q * x * x + Qone[i];
+      }
+      return p / q;
+    }
+
+    private double P1(final double x) {
+      double p, q;
+
+      int i;
+
+      final double Pone[] = {
+          0.352246649133679798341724373e+5, 0.62758845247161281269005675e+5, 0.313539631109159574238669888e+5,
+          0.49854832060594338434500455e+4, 0.2111529182853962382105718e+3, 0.12571716929145341558495e+1
+      }, Qone[] = {
+          0.352246649133679798068390431e+5, 0.626943469593560511888833731e+5, 0.312404063819041039923015703e+5,
+          0.4930396490181088979386097e+4, 0.2030775189134759322293574e+3, 0.1e+1
+      };
+
+      p = Pone[5];
+      q = Qone[5];
+      for (i = 4; i >= 0; i--) {
+        p = p * (8.0 / x) * (8.0 / x) + Pone[i];
+        q = q * (8.0 / x) * (8.0 / x) + Qone[i];
+      }
+      return p / q;
+    }
+
+    private double Q1(final double x) {
+      double p, q;
+
+      int i;
+
+      final double Pone[] = {
+          0.3511751914303552822533318e+3, 0.7210391804904475039280863e+3, 0.4259873011654442389886993e+3,
+          0.831898957673850827325226e+2, 0.45681716295512267064405e+1, 0.3532840052740123642735e-1
+      }, Qone[] = {
+          0.74917374171809127714519505e+4, 0.154141773392650970499848051e+5, 0.91522317015169922705904727e+4,
+          0.18111867005523513506724158e+4, 0.1038187585462133728776636e+3, 0.1e+1
+      };
+
+      p = Pone[5];
+      q = Qone[5];
+      for (i = 4; i >= 0; i--) {
+        p = p * (8.0 / x) * (8.0 / x) + Pone[i];
+        q = q * (8.0 / x) * (8.0 / x) + Qone[i];
+      }
+      return p / q;
+    }
+
+    private double BesselOrderOne(double x) {
+      double p, q;
+
+      if (x == 0.0)
+        return 0.0;
+      p = x;
+      if (x < 0.0)
+        x = -x;
+      if (x < 8.0)
+        return p * J1(x);
+      q = Math.sqrt(2.0 / (Math.PI * x))
+          * (P1(x) * (1.0 / Math.sqrt(2.0) * (Math.sin(x) - Math.cos(x))) - 8.0 / x * Q1(x)
+              * (-1.0 / Math.sqrt(2.0) * (Math.sin(x) + Math.cos(x))));
+      if (p < 0.0)
+        q = -q;
+      return q;
+    }
+
+    @Override
+    public double f(final double x) {
+      if (x == 0.0)
+        return Math.PI / 4.0;
+      return BesselOrderOne(Math.PI * x) / (2.0 * x);
+    }
+  }
+
+  public static final class Blackman extends Filter {
+    @Override
+    public double f(final double x) {
+      return 0.42 + 0.50 * Math.cos(Math.PI * x) + 0.08 * Math.cos(2.0 * Math.PI * x);
+    }
+  }
+
+  public static class Box extends Filter {
+    public Box() {
+      super(true, .5, 1.0);
+    }
+
+    public Box(final double supp) {
+      super(true, supp, 1.0);
+    }
+
+    @Override
+    public double f(final double x) {
+      if (x >= -0.5 && x < 0.5)
+        return 1.0;
+      return 0.0;
+    }
+  }
+
+  public static final class Point extends Box {
+    public Point() {
+      super(0);
+    }
+
+    @Override
+    public double fWindowed(double x) {
+      // don't apply windowing as we have a radius of zero.
+      return super.f(x);
+    }
+  }
+
+  public static final class Catrom extends Filter {
+    public Catrom() {
+      super(true, 2.0, 1.0);
+    }
+
+    @Override
+    public double f(double x) {
+      if (x < 0)
+        x = -x;
+      if (x < 1.0)
+        return 0.5 * (2.0 + x * x * (-5.0 + x * 3.0));
+      if (x < 2.0)
+        return 0.5 * (4.0 + x * (-8.0 + x * (5.0 - x)));
+      return 0.0;
+    }
+  }
+
+  public static final class Cubic extends Filter {
+    public Cubic() {
+      super(false, 2.0, 1.0);
+    }
+
+    @Override
+    public double f(double x) {
+      if (x < 0)
+        x = -x;
+      if (x < 1.0)
+        return 0.5 * x * x * x - x * x + 2.0 / 3.0;
+      if (x < 2.0) {
+        x = 2.0 - x;
+        return 1.0 / 6.0 * x * x * x;
+      }
+      return 0.0;
+    }
+  }
+
+  public static final class Gaussian extends Filter {
+    public Gaussian() {
+      super(false, 1.25, 1.0);
+    }
+
+    @Override
+    public double f(final double x) {
+      return Math.exp(-2.0 * x * x) * Math.sqrt(2.0 / Math.PI);
+    }
+  }
+
+  public static final class Hamming extends Filter {
+    @Override
+    public double f(final double x) {
+      return 0.54 + 0.46 * Math.cos(Math.PI * x);
+    }
+  }
+
+  public static final class Hanning extends Filter {
+    @Override
+    public double f(final double x) {
+      return 0.5 + 0.5 * Math.cos(Math.PI * x);
+    }
+  }
+
+  public static final class Hermite extends Filter {
+    @Override
+    public double f(double x) {
+      if (x < 0) {
+        x = -x;
+      }
+
+      if (x < 1.0) {
+        return (2.0 * x - 3.0) * x * x + 1.0;
+      }
+      return 0.0;
+    }
+  }
+
+  public static final class Lanczos extends Filter {
+    public Lanczos() {
+      super(true, 3.0, 1.0);
+    }
+
+    @Override
+    public double f(double x) {
+      if (x < 0)
+        x = -x;
+      if (x < 3.0)
+        return (float) (sinc(x) * sinc(x / 3.0));
+      return 0.0;
+    }
+
+    private double sinc(double value) {
+      if (value != 0.0f) {
+        value = value * Math.PI;
+        return Math.sin(value) / value;
+      } else {
+        return 1.0;
+      }
+    }
+
+  }
+
+  public static final class Mitchell extends Filter {
+    public Mitchell() {
+      super(false, 2.0, 1.0);
+    }
+
+    @Override
+    public double f(double x) {
+      double b, c;
+
+      b = 1.0 / 3.0;
+      c = 1.0 / 3.0;
+      if (x < 0)
+        x = -x;
+      if (x < 1.0) {
+        x = (12.0 - 9.0 * b - 6.0 * c) * (x * x * x) + (-18.0 + 12.0 * b + 6.0 * c) * x * x + (6.0 - 2.0 * b);
+        return x / 6.0;
+      }
+      if (x < 2.0) {
+        x = (-1.0 * b - 6.0 * c) * (x * x * x) + (6.0 * b + 30.0 * c) * x * x + (-12.0 * b - 48.0 * c) * x
+            + (8.0 * b + 24.0 * c);
+        return x / 6.0;
+      }
+      return 0.0;
+    }
+  }
+
+  public static final class Quadratic extends Filter {
+    public Quadratic() {
+      super(false, 1.5, 1.0);
+    }
+
+    @Override
+    public double f(double x) {
+      if (x < 0)
+        x = -x;
+      if (x < 0.5)
+        return 0.75 - x * x;
+      if (x < 1.5) {
+        x -= 1.5;
+        return 0.5 * x * x;
+      }
+      return 0.0;
+    }
+  }
+
+  public static final class Sinc extends Filter {
+    public Sinc() {
+      super(true, 4.0, 1.0);
+    }
+
+    @Override
+    public double f(double x) {
+      x *= Math.PI;
+      if (x != 0.0)
+        return Math.sin(x) / x;
+      return 1.0;
+    }
+  }
+
+  public static final class Triangle extends Filter {
+    @Override
+    public double f(double x) {
+      if (x < 0.0)
+        x = -x;
+      if (x < 1.0)
+        return 1.0 - x;
+      return 0.0;
+    }
+  }
+
+  /**
+   * is this filter cardinal? ie, does func(x) = (x==0) for integer x?
+   */
+  final boolean cardinal;
+
+  /** radius of nonzero portion */
+  double support;
+
+  /** blur factor (1=normal) */
+  double blur;
+
+  protected Filter() {
+    this(true, 1.0, 1.0);
+  }
+
+  protected Filter(final boolean cardinal, final double support, final double blur) {
+    this.cardinal = cardinal;
+    this.support = support;
+    this.blur = blur;
+  }
+
+  public double fWindowed(double x) {
+    return x < -support || x > support ? 0 : f(x);
+  }
+
+  public abstract double f(double x);
+
+  /**
+   * Return the filter name.
+   * 
+   * @return the filter's name
+   */
+  public String getName() {
+    return getClass().getSimpleName();
+  }
+
+  /**
+   * @return the support
+   */
+  public double getSupport() {
+    return support;
+  }
+
+  /**
+   * @param support the support to set
+   */
+  public void setSupport(final double support) {
+    this.support = support;
+  }
+
+  /**
+   * @return the blur
+   */
+  public double getBlur() {
+    return blur;
+  }
+
+  /**
+   * @param blur the blur to set
+   */
+  public void setBlur(final double blur) {
+    this.blur = blur;
+  }
+}

http://git-wip-us.apache.org/repos/asf/pdfbox-jbig2/blob/4619d28b/src/main/java/org/apache/pdfbox/jbig2/image/FilterType.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/image/FilterType.java b/src/main/java/org/apache/pdfbox/jbig2/image/FilterType.java
new file mode 100644
index 0000000..1420726
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/image/FilterType.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.image;
+
+
+/**
+ * A FilterType enum for defining certain downscale filters to apply.
+ */
+public enum FilterType {
+  Bessel,
+  Blackman,
+  Box,
+  Catrom,
+  Cubic,
+  Gaussian,
+  Hamming,
+  Hanning,
+  Hermite,
+  Lanczos,
+  Mitchell,
+  Point,
+  Quadratic,
+  Sinc,
+  Triangle;
+
+  private static FilterType defaultFilter = Triangle;
+
+  public static void setDefaultFilterType(FilterType defaultFilter) {
+    FilterType.defaultFilter = defaultFilter;
+  }
+
+  public static FilterType getDefaultFilterType() {
+    return defaultFilter;
+  }
+}
\ 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/image/ParameterizedFilter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/pdfbox/jbig2/image/ParameterizedFilter.java b/src/main/java/org/apache/pdfbox/jbig2/image/ParameterizedFilter.java
new file mode 100644
index 0000000..bc32755
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/image/ParameterizedFilter.java
@@ -0,0 +1,69 @@
+/**
+ * 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.image;
+
+import org.apache.pdfbox.jbig2.util.Utils;
+
+class ParameterizedFilter {
+  public ParameterizedFilter(final Filter f, final double scale) {
+    filter = f;
+    /*
+     * find scale of filter in a space (source space) when minifying, ascale=1/scale, but when
+     * magnifying, ascale=1
+     */
+    this.scale = f.blur * Math.max(1., 1. / scale);
+
+    /*
+     * find support radius of scaled filter if ax.supp and ay.supp are both <=.5 then we've got
+     * point sampling. Point sampling is essentially a special filter whose width is fixed at one
+     * source pixel.
+     */
+    support = Math.max(.5, this.scale * f.support);
+    width = (int) Math.ceil(2. * support);
+  }
+
+  public ParameterizedFilter(final Filter f, final double scale, final double support, final int width) {
+    filter = f;
+    this.scale = scale;
+    this.support = support;
+    this.width = width;
+  }
+
+  final Filter filter;
+
+  /* filter scale (spacing between centers in a space) */
+  final double scale;
+
+  /* scaled filter support radius */
+  final double support;
+
+  /* filter width: max number of nonzero samples */
+  final int width;
+
+  public double eval(double center, int i) {
+    return filter.fWindowed((i + .5 - center) / scale);
+  }
+
+  public int minIndex(double center) {
+    return Utils.floor(center - support);
+  }
+
+  public int maxIndex(double center) {
+    return Utils.ceil(center + support);
+  }
+}
\ No newline at end of file