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] -> [ccvvv]
+ *
+ * "C" denotes the first code word bit
+ * "c" denotes a code word bit
+ * "v" 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