initial commit of the JBig2 ImageIO plugin
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7652970
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+atlassian-ide-plugin.xml
+levigo-jbig2-imageio.iml
+target/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..250483c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,25 @@
+#

+# 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.

+#

+

+language: java

+dist: trusty

+jdk:

+# Disable for now due to https://github.com/travis-ci/travis-ci/issues/7019

+#  - oraclejdk7

+  - oraclejdk8

+  - oraclejdk9

+  - openjdk7

diff --git a/LICENSE-HEADER.txt b/LICENSE-HEADER.txt
new file mode 100644
index 0000000..a9ac967
--- /dev/null
+++ b/LICENSE-HEADER.txt
@@ -0,0 +1,14 @@
+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.
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..75b5248
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+

+                                 Apache License

+                           Version 2.0, January 2004

+                        http://www.apache.org/licenses/

+

+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

+

+   1. Definitions.

+

+      "License" shall mean the terms and conditions for use, reproduction,

+      and distribution as defined by Sections 1 through 9 of this document.

+

+      "Licensor" shall mean the copyright owner or entity authorized by

+      the copyright owner that is granting the License.

+

+      "Legal Entity" shall mean the union of the acting entity and all

+      other entities that control, are controlled by, or are under common

+      control with that entity. For the purposes of this definition,

+      "control" means (i) the power, direct or indirect, to cause the

+      direction or management of such entity, whether by contract or

+      otherwise, or (ii) ownership of fifty percent (50%) or more of the

+      outstanding shares, or (iii) beneficial ownership of such entity.

+

+      "You" (or "Your") shall mean an individual or Legal Entity

+      exercising permissions granted by this License.

+

+      "Source" form shall mean the preferred form for making modifications,

+      including but not limited to software source code, documentation

+      source, and configuration files.

+

+      "Object" form shall mean any form resulting from mechanical

+      transformation or translation of a Source form, including but

+      not limited to compiled object code, generated documentation,

+      and conversions to other media types.

+

+      "Work" shall mean the work of authorship, whether in Source or

+      Object form, made available under the License, as indicated by a

+      copyright notice that is included in or attached to the work

+      (an example is provided in the Appendix below).

+

+      "Derivative Works" shall mean any work, whether in Source or Object

+      form, that is based on (or derived from) the Work and for which the

+      editorial revisions, annotations, elaborations, or other modifications

+      represent, as a whole, an original work of authorship. For the purposes

+      of this License, Derivative Works shall not include works that remain

+      separable from, or merely link (or bind by name) to the interfaces of,

+      the Work and Derivative Works thereof.

+

+      "Contribution" shall mean any work of authorship, including

+      the original version of the Work and any modifications or additions

+      to that Work or Derivative Works thereof, that is intentionally

+      submitted to Licensor for inclusion in the Work by the copyright owner

+      or by an individual or Legal Entity authorized to submit on behalf of

+      the copyright owner. For the purposes of this definition, "submitted"

+      means any form of electronic, verbal, or written communication sent

+      to the Licensor or its representatives, including but not limited to

+      communication on electronic mailing lists, source code control systems,

+      and issue tracking systems that are managed by, or on behalf of, the

+      Licensor for the purpose of discussing and improving the Work, but

+      excluding communication that is conspicuously marked or otherwise

+      designated in writing by the copyright owner as "Not a Contribution."

+

+      "Contributor" shall mean Licensor and any individual or Legal Entity

+      on behalf of whom a Contribution has been received by Licensor and

+      subsequently incorporated within the Work.

+

+   2. Grant of Copyright License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      copyright license to reproduce, prepare Derivative Works of,

+      publicly display, publicly perform, sublicense, and distribute the

+      Work and such Derivative Works in Source or Object form.

+

+   3. Grant of Patent License. Subject to the terms and conditions of

+      this License, each Contributor hereby grants to You a perpetual,

+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

+      (except as stated in this section) patent license to make, have made,

+      use, offer to sell, sell, import, and otherwise transfer the Work,

+      where such license applies only to those patent claims licensable

+      by such Contributor that are necessarily infringed by their

+      Contribution(s) alone or by combination of their Contribution(s)

+      with the Work to which such Contribution(s) was submitted. If You

+      institute patent litigation against any entity (including a

+      cross-claim or counterclaim in a lawsuit) alleging that the Work

+      or a Contribution incorporated within the Work constitutes direct

+      or contributory patent infringement, then any patent licenses

+      granted to You under this License for that Work shall terminate

+      as of the date such litigation is filed.

+

+   4. Redistribution. You may reproduce and distribute copies of the

+      Work or Derivative Works thereof in any medium, with or without

+      modifications, and in Source or Object form, provided that You

+      meet the following conditions:

+

+      (a) You must give any other recipients of the Work or

+          Derivative Works a copy of this License; and

+

+      (b) You must cause any modified files to carry prominent notices

+          stating that You changed the files; and

+

+      (c) You must retain, in the Source form of any Derivative Works

+          that You distribute, all copyright, patent, trademark, and

+          attribution notices from the Source form of the Work,

+          excluding those notices that do not pertain to any part of

+          the Derivative Works; and

+

+      (d) If the Work includes a "NOTICE" text file as part of its

+          distribution, then any Derivative Works that You distribute must

+          include a readable copy of the attribution notices contained

+          within such NOTICE file, excluding those notices that do not

+          pertain to any part of the Derivative Works, in at least one

+          of the following places: within a NOTICE text file distributed

+          as part of the Derivative Works; within the Source form or

+          documentation, if provided along with the Derivative Works; or,

+          within a display generated by the Derivative Works, if and

+          wherever such third-party notices normally appear. The contents

+          of the NOTICE file are for informational purposes only and

+          do not modify the License. You may add Your own attribution

+          notices within Derivative Works that You distribute, alongside

+          or as an addendum to the NOTICE text from the Work, provided

+          that such additional attribution notices cannot be construed

+          as modifying the License.

+

+      You may add Your own copyright statement to Your modifications and

+      may provide additional or different license terms and conditions

+      for use, reproduction, or distribution of Your modifications, or

+      for any such Derivative Works as a whole, provided Your use,

+      reproduction, and distribution of the Work otherwise complies with

+      the conditions stated in this License.

+

+   5. Submission of Contributions. Unless You explicitly state otherwise,

+      any Contribution intentionally submitted for inclusion in the Work

+      by You to the Licensor shall be under the terms and conditions of

+      this License, without any additional terms or conditions.

+      Notwithstanding the above, nothing herein shall supersede or modify

+      the terms of any separate license agreement you may have executed

+      with Licensor regarding such Contributions.

+

+   6. Trademarks. This License does not grant permission to use the trade

+      names, trademarks, service marks, or product names of the Licensor,

+      except as required for reasonable and customary use in describing the

+      origin of the Work and reproducing the content of the NOTICE file.

+

+   7. Disclaimer of Warranty. Unless required by applicable law or

+      agreed to in writing, Licensor provides the Work (and each

+      Contributor provides its Contributions) on an "AS IS" BASIS,

+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+      implied, including, without limitation, any warranties or conditions

+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A

+      PARTICULAR PURPOSE. You are solely responsible for determining the

+      appropriateness of using or redistributing the Work and assume any

+      risks associated with Your exercise of permissions under this License.

+

+   8. Limitation of Liability. In no event and under no legal theory,

+      whether in tort (including negligence), contract, or otherwise,

+      unless required by applicable law (such as deliberate and grossly

+      negligent acts) or agreed to in writing, shall any Contributor be

+      liable to You for damages, including any direct, indirect, special,

+      incidental, or consequential damages of any character arising as a

+      result of this License or out of the use or inability to use the

+      Work (including but not limited to damages for loss of goodwill,

+      work stoppage, computer failure or malfunction, or any and all

+      other commercial damages or losses), even if such Contributor

+      has been advised of the possibility of such damages.

+

+   9. Accepting Warranty or Additional Liability. While redistributing

+      the Work or Derivative Works thereof, You may choose to offer,

+      and charge a fee for, acceptance of support, warranty, indemnity,

+      or other liability obligations and/or rights consistent with this

+      License. However, in accepting such obligations, You may act only

+      on Your own behalf and on Your sole responsibility, not on behalf

+      of any other Contributor, and only if You agree to indemnify,

+      defend, and hold each Contributor harmless for any liability

+      incurred by, or claims asserted against, such Contributor by reason

+      of your accepting any such warranty or additional liability.

+

+   END OF TERMS AND CONDITIONS

+

+   APPENDIX: How to apply the Apache License to your work.

+

+      To apply the Apache License to your work, attach the following

+      boilerplate notice, with the fields enclosed by brackets "[]"

+      replaced with your own identifying information. (Don't include

+      the brackets!)  The text should be enclosed in the appropriate

+      comment syntax for the file format. We also recommend that a

+      file or class name and description of purpose be included on the

+      same "printed page" as the copyright notice for easier

+      identification within third-party archives.

+

+   Copyright [yyyy] [name of copyright owner]

+

+   Licensed 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.

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f1abb37
--- /dev/null
+++ b/README.md
@@ -0,0 +1,80 @@
+<!---
+  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.
+--->
+:warning: Please note that this component is currently undergoing migration under the ASF PDFBox umbrella. The new Maven coordinates are not yet working. In the mean time please refer to the [latest released version](https://github.com/levigo/jbig2-imageio/tree/levigo-jbig2-imageio-2.0).
+
+# Welcome to the Java ImageIO plugin for the JBIG2 image format!
+The Java ImageIO plugin for JBIG2 enables access to images encoded using the JBIG2 image compression standard.
+This component is part of the [Apache PDFBox® project](https://pdfbox.apache.org/) 
+
+## Status
+[![Build Status](https://travis-ci.org/levigo/jbig2-imageio.svg?branch=master)](https://travis-ci.org/levigo/jbig2-imageio)
+
+## Features
+The key features of the plugin are:
+
+- Read-only (decode) support for images encoded using the JBIG2 standard, aka *ITU T.88* and *ISO/IEC 14492*.
+- Full support for all features defined in the standard, including arithmetic coding, Huffman coding and MQ coding.
+- Pure Java code. No funny native or JNI stuff required.
+- Secure.
+- Production quality. Robust and well-tested.
+- Reasonable performance.
+- Standard ImageIO API.
+- Support for shared data segments as used by PDF via custom DecodeParams.
+
+## License
+This ImageIO plugin is licensed under the [Apache Software License 2.0](https://www.apache.org/licenses/LICENSE-2.0). Alternatively see [here](LICENSE.txt).
+
+## Release Notes
+Please take a look at the [release notes](release-notes.md).
+
+## Support
+Support is available through the [Apache PDFBox community}(https://pdfbox.apache.org/support.html)
+
+## Usage
+### Introduction
+Please choose the appropriate section below, depending on whether you need to embed the ImageIO decoder inside your application (which would, for example, be the case if you want to support shared data segments) or you just want to add JBIG2 support to an existing ImageIO-enabled application.
+
+### Use within existing Image I/O-based applications
+Using the JBIG2 plugin with an existing application that already supports Java ImageIO is - at least in theory - very simple: just plunk the plugin jar down into your classpath and you're ready to go. What, exactly, you have to do to achieve this, depends on the application in question. Unfortunately, there is no general way to add the plugin to an application. Some general recommendations:
+
+- If you implemented the application yourself: hey, don't look at us. You'll know best what to do. If you build your application using Maven, just pull in the plugin as a dependency. See below.
+- Consult the application's manual (well, duh!).
+- Maybe the application installation has a folder called 'plugins' or something like that. Try putting the jar into it. With some luck, the application will pick it up.
+- Is the application started using a Unix shell script or Windows batch file? Try to identify where the classpath is assembled and add the plugin jar to it.
+- As a last resort, you could try to add the plugin jar to the [lib/ext directory of your JRE installation](http://download.oracle.com/javase/1.4.2/docs/guide/extensions/spec.html). But please be advised that this is not considered to be good style.
+
+### Use dependency management
+The JBIG2 ImageIO-Plugin is available from [Maven Central](http://search.maven.org/). 
+
+#### Maven
+To use the plugin within a Maven POM-based project, simply include a dependency to the following artifact in the appropriate ```pom.xml```:
+
+    <dependency>
+      <groupId>org.apache.pdfbox.jbig2</groupId>
+		<artifactId>pdfbox-jbig2-imageio</artifactId>
+      <version>3.0-SNAPSHOT</version>
+    </dependency>
+
+### How to deal with embedded JBIG2 data
+Several formats allow to embed JBIG2-compressed data in its own structure. PDF, for example, supports JBIG2-compressed data and adds the ability to embed shared data segments. Therefore the `JBIG2ImageReader` can handle `JBIG2Globals`  which are stored separately and can be passed into the reader if neccessary via `setGlobals()`-method.
+
+The reader recognizes if the input data is headless and so embedded. This is assumed if the file header is missing.
+
+You can also specify that the coming input data is embedded by using the special constructor in `JBIG2ImageReader`.
+
+### What if the plugin is on classpath but not seen?
+ImageIO is able to scan the classpath for readers and writers. Call `ImageIO.scanForPlugins()` if the reader is not seen. (Note: Thanks to George Sexton for this tip in context of using ImageIO within Apache Tomcat)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..16f692a
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,208 @@
+<!--

+

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

+

+-->

+

+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

+	<modelVersion>4.0.0</modelVersion>

+

+    <parent>

+        <groupId>org.apache</groupId>

+        <artifactId>apache</artifactId>

+        <version>18</version>

+        <relativePath/>

+    </parent>

+    

+	<groupId>org.apache.pdfbox</groupId>

+	<artifactId>jbig2-imageio</artifactId>

+	<version>3.0.0-SNAPSHOT</version>

+	

+	<name>PDFBox JBIG2 ImageIO plugin</name>

+	

+	<description>

+		Java Image I/O plugin for reading JBIG2-compressed image data. 

+		Formerly known as the levigo JBig2 ImageIO plugin (com.levigo.jbig2:levigo-jbig2-imageio).

+	</description>

+	

+	<url>https://github.com/levigo/jbig2-imageio</url>

+

+	<developers>

+		<developer>

+			<id>krzikams</id>

+			<name>Matthäus Mayer</name>

+			<email>m.mayer@levigo.de</email>

+			<roles>

+				<role>retired developer</role>

+			</roles>

+		</developer>

+		<developer>

+			<id>hennejg</id>

+			<name>Jörg Henne</name>

+			<email>j.henne@levigo.de</email>

+			<roles>

+				<role>project transition to ASL/ASF</role>

+			</roles>

+		</developer>

+	</developers>

+

+	<distributionManagement>

+		<snapshotRepository>

+			<id>sonatype-nexus-snapshots</id>

+			<name>Sonatype Nexus Snapshot Repository</name>

+			<url>https://oss.sonatype.org/content/repositories/snapshots/</url>

+		</snapshotRepository>

+		<repository>

+			<id>sonatype-nexus-staging</id>

+			<name>Sonatype Nexus Release Staging Repository</name>

+			<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>

+		</repository>

+	</distributionManagement>

+

+	<scm>

+    	<connection>scm:git:ssh://github.com/levigo/jbig2-imageio.git</connection>

+		<developerConnection>scm:git:ssh://git@github.com/levigo/jbig2-imageio.git</developerConnection>

+    	<url>https://github.com/levigo/jbig2-imageio</url>

+    	<tag>HEAD</tag>

+	</scm>

+

+	<dependencies>

+		<dependency>

+			<groupId>junit</groupId>

+			<artifactId>junit</artifactId>

+			<version>4.10</version>

+			<scope>test</scope>

+		</dependency>

+	</dependencies>

+

+	<build>

+		<plugins>

+			<plugin>

+				<artifactId>maven-compiler-plugin</artifactId>

+				<configuration>

+					<source>1.7</source>

+					<target>1.7</target>

+					<debug>false</debug>

+					<optimize>true</optimize>

+				</configuration>

+			</plugin>

+

+			<plugin>

+				<inherited>false</inherited>

+				<groupId>com.mycila</groupId>

+				<artifactId>license-maven-plugin</artifactId>

+				<version>3.0</version>

+				<configuration>

+					<header>${basedir}/LICENSE-HEADER.txt</header>

+					<failIfMissing>true</failIfMissing>

+					<aggregate>true</aggregate>

+					<useDefaultExcludes>false</useDefaultExcludes>

+					<excludes>

+						<exclude>**/.classpath</exclude>

+						<exclude>**/.project</exclude>

+						<exclude>**/.settings/**</exclude>

+						<exclude>**/target/**</exclude>

+						<exclude>**/.idea/**</exclude>

+						<exclude>**/atlassian-ide-plugin.xml</exclude>

+					</excludes>

+					<includes>

+						<include>**/*.xml</include>

+						<include>**/*.md</include>

+						<include>**/src/**/*.java</include>

+						<include>**/src/**/*.properties</include>

+						<include>**/META-INF/services/*</include>

+						<include>**/*.yml</include>

+					</includes>

+					<headerDefinitions>

+					   <headerDefinition>src/build/license-maven-plugin/additionalHeaders.xml</headerDefinition>

+					</headerDefinitions>

+					<mapping>

+						<md>MD_STYLE</md>

+						<CacheBridge>SCRIPT_STYLE</CacheBridge>

+						<ReaderSpi>SCRIPT_STYLE</ReaderSpi>

+						<LoggerBridge>SCRIPT_STYLE</LoggerBridge>

+						<TestService>SCRIPT_STYLE</TestService>

+						<ImageReaderSpi>SCRIPT_STYLE</ImageReaderSpi>

+					</mapping>

+				</configuration>

+				<executions>

+					<execution>

+						<id>check-headers</id>

+						<phase>verify</phase>

+						<goals>

+							<goal>check</goal>

+						</goals>

+					</execution>

+				</executions>

+			</plugin>

+

+      <plugin>

+        <artifactId>maven-release-plugin</artifactId>

+        <configuration>

+          <autoVersionSubmodules>true</autoVersionSubmodules>

+          <!-- Keep changes in the local repo, push will be done afterwards -->

+          <pushChanges>false</pushChanges>

+          <localCheckout>true</localCheckout>

+          <!-- Use a better name for the tag -->

+          <tagNameFormat>${project.artifactId}-${project.version}</tagNameFormat>

+        </configuration>

+        <dependencies>

+          <dependency>

+            <groupId>org.apache.maven.scm</groupId>

+            <artifactId>maven-scm-provider-gitexe</artifactId>

+            <version>1.9</version>

+          </dependency>

+        </dependencies>

+      </plugin>

+		</plugins>

+	</build>

+

+	<profiles>

+		<profile>

+			<id>release-sign-artifacts</id>

+			<activation>

+				<property>

+					<name>performRelease</name>

+					<value>true</value>

+				</property>

+			</activation>

+			<build>

+				<plugins>

+					<plugin>

+						<groupId>org.apache.maven.plugins</groupId>

+						<artifactId>maven-gpg-plugin</artifactId>

+						<version>1.1</version>

+						<configuration>

+							<homedir>${levigo-jbig2-imageio.gpg.homedir}</homedir>

+							<keyname>${levigo-jbig2-imageio.gpg.keyname}</keyname>

+							<passphrase>${levigo-jbig2-imageio.gpg.passphrase}</passphrase>

+						</configuration>

+						<executions>

+							<execution>

+								<id>sign-artifacts</id>

+								<phase>verify</phase>

+								<goals>

+									<goal>sign</goal>

+								</goals>

+							</execution>

+						</executions>

+					</plugin>

+				</plugins>

+			</build>

+		</profile>

+	</profiles>

+

+</project>
\ No newline at end of file
diff --git a/release-notes.md b/release-notes.md
new file mode 100644
index 0000000..b4e9266
--- /dev/null
+++ b/release-notes.md
@@ -0,0 +1,94 @@
+<!---
+  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.
+--->
+# Release notes
+
+## Version 3.0.0 (pending)
+- [Issue #30](https://github.com/levigo/jbig2-imageio/issues/30): Transition project to Apache PDFBox.
+- [Issue #25](https://github.com/levigo/jbig2-imageio/issues/25): Build tests fail if project path has a space.
+- [Issue #26](https://github.com/levigo/jbig2-imageio/issues/26): Huffman user tables in text regions.
+- [Issue #27](https://github.com/levigo/jbig2-imageio/issues/27): Problems in standard Huffman tables.
+- [Issue #32](https://github.com/levigo/jbig2-imageio/issues/32): Newlines printed to stdout.
+
+## Version 2.0.0 (2017-06-02)
+- Java 9 support.
+- [Issue #10](https://github.com/levigo/jbig2-imageio/issues/10): Use general service loading instead of Image I/O specific. See [merge request #18](https://github.com/levigo/jbig2-imageio/pull/18).
+- [Issue #11](https://github.com/levigo/jbig2-imageio/issues/11): Remove @author info from javadocs.
+- [Issue #16](https://github.com/levigo/jbig2-imageio/issues/16): Replace deprecated ```STANDARD_INPUT_TYPE``` constant with self-created array.
+- [Issue #21](https://github.com/levigo/jbig2-imageio/issues/21): Move repetition of previous code length into correct scope. See [merge request #22](https://github.com/levigo/jbig2-imageio/pull/22). 
+
+## Version 1.6.5 (2015-12-29)
+- Issue #5: Deployment to maven central
+
+## Version 1.6.3 (2014-08-04)
+- Issue #1: Discard the request of copying the row above the first row.
+
+## Version 1.6.2-RC1 (2014-01-22)
+- Googlecode Issue 17: Changed computation of result dimension from ceiling to rounding. 
+
+##Version 1.6.1 (2013-04-18)
+- Googlecode Issue 11: Added support for `GRREFERENCE` Release notes
+
+## Version 1.6.2-RC1 (2014-01-22)
+- Googlecode Issue 17: Changed computation of result dimension from ceiling to rounding. 
+
+## Version 1.6.1 (2013-04-18)
+- Googlecode Issue 11: Added support for `GRREFERENCEDX` in generic refinement region coding. 
+
+## Version 1.6.0 (2013-03-25):
+- Googlecode Issue 10: Usability of `CacheFactory` and `LoggerFactory` improved.
+
+## Version 1.5.2 (2012-10-09):
+- Googlecode Issue  9: Transfer of bitmap's data into target raster went wrong if bitmap's line ends with a padded byte. 
+
+## Version 1.5.1 (2012-10-02):
+- Googlecode Issue  8: The default read parameters changed. There will be no source region, no source render size (no scaling) 
+  and subsampling factors of 1 (no subsampling). `Bitmaps.java` can handle this correctly.
+
+## Version 1.5.0 (2012-09-20):
+- Moved Exception-classes to dedicated package `com.levigo.jbig2.err`.
+- Introduced a new utility class `com.levigo.jbig2.image.Bitmaps`. This class provides a bunch of new features operating 
+  on a `Bitmap` instance. For example, extracting a region of interest, scaling with high-quality filters and subsampling
+  in either or both horizontal and vertical direction.
+
+## Version 1.4.1 (2012-04-20):
+- Fixed race condition when parsing documents with multiple pages.
+
+## Version 1.4 (2012-04-10):
+- Googlecode Issue  6 : The returned bitmap was too small in case of only one region. Solution is to check if we have only one
+  region that forms the complete page. Only if width and height of region equals width and height of page use region's
+  bitmap as the page's bitmap. 
+- Googlecode Issue  5: A raster which was too small was created. AWT has thrown a `RasterFormatException`.  
+- Googlecode Issue  4: `IndexOutOfBoundsException` indicates the end of the stream in `JBIG2Document#reachedEndOfStream()`
+- Googlecode Issue  3: Reader recognizes if a file header is present or not.
+
+## Version 1.3 (2011-10-28):
+- Untracked Googlecode Issue : Fixed inverted color model for grayscale images.
+- Untracked Googlecode Issue : Fixed `IndexArrayOutOfBoundException` in handling requests with region of interests. The region of
+  interest is clipped at image boundary.
+
+## Version 1.2 (2011-10-06):
+- Googlecode Issue  1: The default read parameters will return a default image size of 1x1 without claiming the missing input.
+- Untracked Googlecode Issue : A black pixel was represented by 1 and a white pixel by 0. For work with image masks the
+  convention says, a black pixel is the minimum and the white pixel is maximum. This corresponds to an additive
+  colorspace. We turned the representation of white and black pixels for conformity.
+
+## Version 1.1 (2010-12-13):
+- raster creation optimized
+- potential NPE in cache 
+
+## Version 1.0 (2010-07-29):
+- open-source'd  DX in generic refinement region coding. 
diff --git a/src/build/license-maven-plugin/additionalHeaders.xml b/src/build/license-maven-plugin/additionalHeaders.xml
new file mode 100644
index 0000000..7effdc9
--- /dev/null
+++ b/src/build/license-maven-plugin/additionalHeaders.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>

+<!--

+

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

+

+-->

+<additionalHeaders>

+    <md_style>

+        <firstLine><![CDATA[<!---]]></firstLine>

+        <beforeEachLine>  </beforeEachLine>

+        <endLine><![CDATA[--->]]></endLine>

+        <firstLineDetectionPattern><![CDATA[(\s|\t)*<!---.*$]]></firstLineDetectionPattern>

+        <lastLineDetectionPattern><![CDATA[.*--->(\s|\t)*$]]></lastLineDetectionPattern>

+        <allowBlankLines>false</allowBlankLines>

+        <isMultiline>true</isMultiline>

+        <padLines>false</padLines>

+    </md_style>

+</additionalHeaders>
\ No newline at end of file
diff --git a/src/main/java/org/apache/pdfbox/jbig2/Bitmap.java b/src/main/java/org/apache/pdfbox/jbig2/Bitmap.java
new file mode 100644
index 0000000..7dbc38d
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/Bitmap.java
@@ -0,0 +1,198 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.awt.Rectangle;
+
+/**
+ * This class represents a bi-level image that is organized like a bitmap.
+ */
+public class Bitmap {
+
+
+  /** The height of the bitmap in pixels. */
+  private final int height;
+
+  /** The width of the bitmap in pixels. */
+  private final int width;
+
+  /** The amount of bytes used per row. */
+  private final int rowStride;
+
+  /** 8 pixels per byte, 0 for white, 1 for black */
+  private byte[] bitmap;
+
+  /**
+   * Creates an instance of a blank image.<br>
+   * The image data is stored in a byte array. Each pixels is stored as one bit, so that each byte
+   * contains 8 pixel. A pixel has by default the value {@code 0} for white and {@code 1} for black. <br>
+   * Row stride means the amount of bytes per line. It is computed automatically and fills the pad
+   * bits with 0.<br>
+   * 
+   * @param height - The real height of the bitmap in pixels.
+   * @param width - The real width of the bitmap in pixels.
+   */
+  public Bitmap(int width, int height) {
+    this.height = height;
+    this.width = width;
+    this.rowStride = (width + 7) >> 3;
+
+    bitmap = new byte[this.height * this.rowStride];
+  }
+
+  /**
+   * Returns the value of a pixel specified by the given coordinates.
+   * <p>
+   * By default, the value is {@code 0} for a white pixel and {@code 1} for a black pixel. The value
+   * is placed in the rightmost bit in the byte.
+   * 
+   * @param x - The x coordinate of the pixel.
+   * @param y - The y coordinate of the pixel.
+   * @return The value of a pixel.
+   */
+  public byte getPixel(int x, int y) {
+    int byteIndex = this.getByteIndex(x, y);
+    int bitOffset = this.getBitOffset(x);
+
+    int toShift = 7 - bitOffset;
+    return (byte) ((this.getByte(byteIndex) >> toShift) & 0x01);
+  }
+  
+  public void setPixel(int x, int y, byte pixelValue) {
+    final int byteIndex = getByteIndex(x, y);
+    final int bitOffset = getBitOffset(x);
+    
+    final int shift = 7 - bitOffset;
+    
+    final byte src = bitmap[byteIndex];
+    final byte result = (byte) (src | (pixelValue << shift));
+    bitmap[byteIndex] = result;
+  }
+
+  /**
+   * 
+   * <p>
+   * Returns the index of the byte that contains the pixel, specified by the pixel's x and y
+   * coordinates.
+   * 
+   * @param x - The pixel's x coordinate.
+   * @param y - The pixel's y coordinate.
+   * @return The index of the byte that contains the specified pixel.
+   */
+  public int getByteIndex(int x, int y) {
+    return y * this.rowStride + (x >> 3);
+  }
+
+  /**
+   * Simply returns the byte array of this bitmap.
+   * 
+   * @return The byte array of this bitmap.
+   */
+  public byte[] getByteArray() {
+    return bitmap;
+  }
+
+  /**
+   * Simply returns a byte from the bitmap byte array. Throws an {@link IndexOutOfBoundsException}
+   * if the given index is out of bound.
+   * 
+   * @param index - The array index that specifies the position of the wanted byte.
+   * @return The byte at the {@code index}-position.
+   * 
+   * @throws IndexOutOfBoundsException if the index is out of bound.
+   */
+  public byte getByte(int index) {
+    return this.bitmap[index];
+  }
+
+  /**
+   * Simply sets the given value at the given array index position. Throws an
+   * {@link IndexOutOfBoundsException} if the given index is out of bound.
+   * 
+   * @param index - The array index that specifies the position of a byte.
+   * @param value - The byte that should be set.
+   * 
+   * @throws IndexOutOfBoundsException if the index is out of bound.
+   */
+  public void setByte(int index, byte value) {
+    this.bitmap[index] = value;
+  }
+
+  /**
+   * Converts the byte at specified index into an integer and returns the value. Throws an
+   * {@link IndexOutOfBoundsException} if the given index is out of bound.
+   * 
+   * @param index - The array index that specifies the position of the wanted byte.
+   * @return The converted byte at the {@code index}-position as an integer.
+   * 
+   * @throws IndexOutOfBoundsException if the index is out of bound.
+   */
+  public int getByteAsInteger(int index) {
+    return (this.bitmap[index] & 0xff);
+  }
+
+
+  /**
+   * Computes the offset of the given x coordinate in its byte. The method uses optimized modulo
+   * operation for a better performance.
+   * 
+   * @param x - The x coordinate of a pixel.
+   * @return The bit offset of a pixel in its byte.
+   */
+  public int getBitOffset(int x) {
+    // The same like x % 8.
+    // The rightmost three bits are 1. The value masks all bits upon the value "7".
+    return (x & 0x07);
+  }
+
+  /**
+   * Simply returns the height of this bitmap.
+   * 
+   * @return The height of this bitmap.
+   */
+  public int getHeight() {
+    return height;
+  }
+
+  /**
+   * Simply returns the width of this bitmap.
+   * 
+   * @return The width of this bitmap.
+   */
+  public int getWidth() {
+    return width;
+  }
+
+  /**
+   * Simply returns the row stride of this bitmap. <br>
+   * (Row stride means the amount of bytes per line.)
+   * 
+   * @return The row stride of this bitmap.
+   */
+  public int getRowStride() {
+    return rowStride;
+  }
+
+  public Rectangle getBounds() {
+    return new Rectangle(0, 0, width, height);
+  }
+
+  public int getMemorySize() {
+    return bitmap.length;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/Dictionary.java b/src/main/java/org/apache/pdfbox/jbig2/Dictionary.java
new file mode 100644
index 0000000..89fa888
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/Dictionary.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+
+/**
+ * Interface for all JBIG2 dictionaries segments.
+ */
+public interface Dictionary extends SegmentData {
+
+  /**
+   * Decodes a dictionary segment and returns the result.
+   * 
+   * @return A list of {@link Bitmap}s as a result of the decoding process of dictionary segments.
+   * 
+   * @throws IOException
+   * @throws InvalidHeaderValueException
+   * @throws IntegerMaxValueException
+   */
+  public ArrayList<Bitmap> getDictionary() throws IOException, InvalidHeaderValueException, IntegerMaxValueException;
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/JBIG2Document.java b/src/main/java/org/apache/pdfbox/jbig2/JBIG2Document.java
new file mode 100644
index 0000000..395c348
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/JBIG2Document.java
@@ -0,0 +1,344 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represents the document structure with its pages and global segments.
+ */
+class JBIG2Document {
+  private static final Logger log = LoggerFactory.getLogger(JBIG2Document.class);
+
+  /** ID string in file header, see ISO/IEC 14492:2001, D.4.1 */
+  private int[] FILE_HEADER_ID = {
+      0x97, 0x4A, 0x42, 0x32, 0x0D, 0x0A, 0x1A, 0x0A
+  };
+
+  /**
+   * This map contains all pages of this document. The key is the number of the page.
+   */
+  private final Map<Integer, JBIG2Page> pages = new TreeMap<Integer, JBIG2Page>();
+
+
+  /** BASIC INFORMATION ABOUT THE CURRENT JBIG2 DOCUMENT */
+
+  /** The length of the file header if exists */
+  private short fileHeaderLength = 9;
+
+  /**
+   * According to D.4.2 - File header bit 0
+   * <p>
+   * This flag contains information about the file organization:
+   * <ul>
+   * <li>{@code 1} for sequential</li>
+   * <li>{@code 0} for random-access</li>
+   * </ul>
+   * You can use the constants {@link #RANDOM} and {@link JBIG2Document#SEQUENTIAL}.
+   */
+  private short organisationType = SEQUENTIAL;
+
+  public static final int RANDOM = 0;
+  public static final int SEQUENTIAL = 1;
+
+  /**
+   * According to D.4.2 - Bit 1<br>
+   * <ul>
+   * <li>{@code true} if amount of pages is unknown, amount of pages field is not present</li>
+   * <li>{@code false} if there is a field in the file header where the amount of pages can be read</li>
+   * </ul>
+   */
+  private boolean amountOfPagesUnknown = true;
+
+  /**
+   * According to D.4.3 - Number of pages field (4 bytes). Only present if
+   * {@link #amountOfPagesUnknown} is {@code false}.
+   */
+  private int amountOfPages;
+
+  /** Defines whether extended Template is used. */
+  private boolean gbUseExtTemplate;
+
+  /**
+   * This is the source data stream wrapped into a {@link SubInputStream}.
+   */
+  private final SubInputStream subInputStream;
+
+  /**
+   * Flag:
+   * <ul>
+   * <li>{@code true} if stream is embedded in another file format and the file header is missing</li>
+   * <li>{@code false} if stream is created of a native jbig2 file and the file header should be
+   * present</li>
+   * </ul>
+   */
+
+  /**
+   * Holds a load of segments, that aren't associated with a page.
+   */
+  private JBIG2Globals globalSegments;
+
+  protected JBIG2Document(ImageInputStream input) throws IOException {
+    this(input, null);
+  }
+
+  protected JBIG2Document(ImageInputStream input, JBIG2Globals globals) throws IOException {
+    if (input == null)
+      throw new IllegalArgumentException("imageInputStream must not be null");
+
+    this.subInputStream = new SubInputStream(input, 0, Long.MAX_VALUE);
+    this.globalSegments = globals;
+
+    mapStream();
+  }
+
+  /**
+   * Retrieves the segment with the given segment number considering only segments that aren't
+   * associated with a page.
+   * 
+   * @param segmentNr - The number of the wanted segment.
+   * @return The requested {@link SegmentHeader}.
+   */
+  SegmentHeader getGlobalSegment(int segmentNr) {
+    if (null != globalSegments) {
+      return globalSegments.getSegment(segmentNr);
+    }
+
+    if (log.isErrorEnabled()) {
+      log.error("Segment not found. Returning null.");
+    }
+
+    return null;
+  }
+
+  /**
+   * Retrieves a {@link JBIG2Page} specified by the given page number.
+   * 
+   * @param pageNumber - The page number of the wanted {@link JBIG2Page}.
+   * 
+   * @return The requested {@link JBIG2Page}.
+   */
+  protected JBIG2Page getPage(int pageNumber) {
+    return pages.get(pageNumber);
+  }
+
+  /**
+   * Retrieves the amount of pages in this JBIG2 document. If the pages are striped, the document
+   * will be completely parsed and the amount of pages will be gathered.
+   * 
+   * @return The amount of pages in this JBIG2 document.
+   * @throws IOException
+   */
+  protected int getAmountOfPages() throws IOException {
+    if (amountOfPagesUnknown || amountOfPages == 0) {
+      if (pages.size() == 0) {
+        mapStream();
+      }
+
+      return pages.size();
+    } else {
+      return amountOfPages;
+    }
+  }
+
+  /**
+   * This method maps the stream and stores all segments.
+   */
+  private void mapStream() throws IOException {
+    final List<SegmentHeader> segments = new LinkedList<SegmentHeader>();
+
+    long offset = 0;
+    int segmentType = 0;
+
+    /*
+     * Parse the file header if there is one.
+     */
+    if (isFileHeaderPresent()) {
+      parseFileHeader();
+      offset += fileHeaderLength;
+    }
+
+    if (globalSegments == null) {
+      globalSegments = new JBIG2Globals();
+    }
+
+    JBIG2Page page = null;
+
+    /*
+     * If organisation type is random-access: walk through the segment headers until EOF segment
+     * appears (specified with segment number 51)
+     */
+    while (segmentType != 51 && !reachedEndOfStream(offset)) {
+      SegmentHeader segment = new SegmentHeader(this, subInputStream, offset, organisationType);
+
+      final int associatedPage = segment.getPageAssociation();
+      segmentType = segment.getSegmentType();
+
+      if (associatedPage != 0) {
+        page = getPage(associatedPage);
+        if (page == null) {
+          page = new JBIG2Page(this, associatedPage);
+          pages.put(associatedPage, page);
+        }
+        page.add(segment);
+      } else {
+        globalSegments.addSegment(segment.getSegmentNr(), segment);
+      }
+      segments.add(segment);
+
+      if (JBIG2ImageReader.DEBUG) {
+        if (log.isDebugEnabled()) {
+          log.debug(segment.toString());
+        }
+      }
+
+      offset = subInputStream.getStreamPosition();
+
+      // Sequential organization skips data part and sets the offset
+      if (organisationType == SEQUENTIAL) {
+        offset += segment.getSegmentDataLength();
+      }
+    }
+
+    /*
+     * Random organization: segment headers are finished. Data part starts and the offset can be
+     * set.
+     */
+    determineRandomDataOffsets(segments, offset);
+  }
+
+  private boolean isFileHeaderPresent() throws IOException {
+    final SubInputStream input = subInputStream;
+    input.mark();
+
+    for (int magicByte : FILE_HEADER_ID) {
+      if (magicByte != input.read()) {
+        input.reset();
+        return false;
+      }
+    }
+
+    input.reset();
+    return true;
+  }
+
+  /**
+   * Determines the start of the data parts and sets the offset.
+   * 
+   * @param segments
+   * @param offset
+   */
+  private void determineRandomDataOffsets(List<SegmentHeader> segments, long offset) {
+    if (organisationType == RANDOM) {
+      for (SegmentHeader s : segments) {
+        s.setSegmentDataStartOffset(offset);
+        offset += s.getSegmentDataLength();
+      }
+    }
+  }
+
+  /**
+   * This method reads the stream and sets variables for information about organization type and
+   * length etc.
+   * 
+   * @return
+   * @throws IOException
+   */
+  private void parseFileHeader() throws IOException {
+    subInputStream.seek(0);
+
+    /* D.4.1 - ID string, read will be skipped */
+    subInputStream.skipBytes(8);
+
+    /*
+     * D.4.2 Header flag (1 byte)
+     */
+
+    // Bit 3-7 are reserved and must be 0
+    subInputStream.readBits(5);
+
+    // Bit 2 - Indicates if extended templates are used
+    if (subInputStream.readBit() == 1) {
+      gbUseExtTemplate = true;
+    }
+
+    // Bit 1 - Indicates if amount of pages are unknown
+    if (subInputStream.readBit() != 1) {
+      amountOfPagesUnknown = false;
+    }
+
+    // Bit 0 - Indicates file organisation type
+    organisationType = (short) subInputStream.readBit();
+
+    // fileHeaderLength = 9;
+
+    /*
+     * D.4.3 Number of pages (field is only present if amount of pages are 'NOT unknown')
+     */
+    if (!amountOfPagesUnknown) {
+      amountOfPages = (int) subInputStream.readUnsignedInt();
+      fileHeaderLength = 13;
+    }
+
+  }
+
+  /**
+   * This method checks, if the stream is at its end to avoid {@link EOFException}s and reads 32
+   * bits.
+   * 
+   * @param offset
+   * @return <li>{@code true} if end of stream reached <li>{@code false} if there are more bytes to
+   *         read
+   * @throws IOException
+   */
+  private boolean reachedEndOfStream(long offset) throws IOException {
+    try {
+      subInputStream.seek(offset);
+      subInputStream.readBits(32);
+      return false;
+    } catch (EOFException e) {
+      return true;
+    } catch (IndexOutOfBoundsException e) {
+      return true;
+    }
+  }
+
+  protected JBIG2Globals getGlobalSegments() {
+    return globalSegments;
+  }
+
+  protected boolean isAmountOfPagesUnknown() {
+    return amountOfPagesUnknown;
+  }
+
+  boolean isGbUseExtTemplate() {
+    return gbUseExtTemplate;
+  }
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/JBIG2Globals.java b/src/main/java/org/apache/pdfbox/jbig2/JBIG2Globals.java
new file mode 100644
index 0000000..1f8f0fe
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/JBIG2Globals.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+
+/**
+ * This class stores segments, that aren't associated to a page.
+ * 
+ * If the data is embedded in another format, for example PDF, this segments might be stored
+ * separately in the file.
+ * 
+ * This segments will be decoded on demand and all results are stored in the document object and can
+ * be retrieved from there.
+ */
+public class JBIG2Globals {
+  private static final Logger log = LoggerFactory.getLogger(JBIG2Globals.class);
+
+  /**
+   * This map contains all segments, that are not associated with a page. The key is the segment
+   * number.
+   */
+  private Map<Integer, SegmentHeader> globalSegments = new HashMap<Integer, SegmentHeader>();
+
+  protected SegmentHeader getSegment(int segmentNr) {
+    if (globalSegments.size() == 0) {
+      if (log.isErrorEnabled()) {
+        log.error("No global segment added so far. Use JBIG2ImageReader.setGlobals().");
+      }
+    }
+
+    return globalSegments.get(segmentNr);
+  }
+
+  protected void addSegment(Integer segmentNumber, SegmentHeader segment) {
+    globalSegments.put(segmentNumber, segment);
+  }
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageMetadata.java b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageMetadata.java
new file mode 100644
index 0000000..a931b10
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageMetadata.java
@@ -0,0 +1,107 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.metadata.IIOMetadataNode;
+
+import org.w3c.dom.Node;
+
+/**
+ * @see IIOMetadata
+ */
+public class JBIG2ImageMetadata extends IIOMetadata {
+
+  static final String IMAGE_METADATA_FORMAT_NAME = "jbig2";
+
+  private final JBIG2Page page;
+
+  public JBIG2ImageMetadata(JBIG2Page page) {
+    super(true, IMAGE_METADATA_FORMAT_NAME, JBIG2ImageMetadataFormat.class.getName(), null, null);
+
+    if (page == null)
+      throw new IllegalArgumentException("page must not be null");
+
+    this.page = page;
+  }
+
+  @Override
+  public boolean isReadOnly() {
+    return true;
+  }
+
+  @Override
+  public Node getAsTree(String formatName) {
+    if (formatName.equals(nativeMetadataFormatName)) {
+      return getNativeTree();
+    } else if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
+      return getStandardTree();
+    } else {
+      throw new IllegalArgumentException("Not a recognized format!");
+    }
+  }
+
+  private Node getNativeTree() {
+    IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
+    root.appendChild(getStandardDimensionNode());
+    return root;
+  }
+
+  @Override
+  public IIOMetadataNode getStandardDimensionNode() {
+    IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
+    IIOMetadataNode node = null; // scratch node
+
+    node = new IIOMetadataNode("PixelAspectRatio");
+    node.setAttribute("value", "1.0");
+    dimensionNode.appendChild(node);
+
+    node = new IIOMetadataNode("ImageOrientation");
+    node.setAttribute("value", "Normal");
+    dimensionNode.appendChild(node);
+
+    if (page.getResolutionX() != 0) {
+      String pixelResolution = Float.toString(25.4f / (page.getResolutionX() / 39.3701f));
+
+      node = new IIOMetadataNode("HorizontalPixelSize");
+      node.setAttribute("value", pixelResolution);
+      dimensionNode.appendChild(node);
+    }
+
+    if (page.getResolutionY() != 0) {
+      String pixelResolution = Float.toString(25.4f / (page.getResolutionY() / 39.3701f));
+
+      node = new IIOMetadataNode("VerticalPixelSize");
+      node.setAttribute("value", pixelResolution);
+      dimensionNode.appendChild(node);
+    }
+
+    return dimensionNode;
+  }
+
+  @Override
+  public void mergeTree(String formatName, Node root) {
+    throw new IllegalStateException("Metadata is read-only!");
+  }
+
+  @Override
+  public void reset() {
+    throw new IllegalStateException("Metadata is read-only!");
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageMetadataFormat.java b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageMetadataFormat.java
new file mode 100644
index 0000000..1dc1ac7
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageMetadataFormat.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadataFormat;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+
+/**
+ * @see IIOMetadataFormat
+ * @see IIOMetadataFormatImpl
+ */
+public class JBIG2ImageMetadataFormat extends IIOMetadataFormatImpl {
+
+  private static IIOMetadataFormat instance = null;
+
+  private JBIG2ImageMetadataFormat() {
+    super(JBIG2ImageMetadata.IMAGE_METADATA_FORMAT_NAME, CHILD_POLICY_SOME);
+
+    // root -> ImageDescriptor
+    addElement("ImageDescriptor", JBIG2ImageMetadata.IMAGE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY);
+    addAttribute("ImageDescriptor", "imageWidth", DATATYPE_INTEGER, true, null, "1", "65535", true, true);
+    addAttribute("ImageDescriptor", "imageHeight", DATATYPE_INTEGER, true, null, "1", "65535", true, true);
+    addAttribute("ImageDescriptor", "Xdensity", DATATYPE_FLOAT, true, null, "1", "65535", true, true);
+    addAttribute("ImageDescriptor", "Ydensity", DATATYPE_FLOAT, true, null, "1", "65535", true, true);
+  }
+
+  public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) {
+    return true;
+  }
+
+  public static synchronized IIOMetadataFormat getInstance() {
+    if (instance == null) {
+      instance = new JBIG2ImageMetadataFormat();
+    }
+    return instance;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageReader.java b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageReader.java
new file mode 100644
index 0000000..db706ab
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageReader.java
@@ -0,0 +1,316 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.err.JBIG2Exception;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.image.FilterType;
+import org.apache.pdfbox.jbig2.util.cache.CacheFactory;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * @see ImageReader
+ */
+public class JBIG2ImageReader extends ImageReader {
+  private static final Logger log = LoggerFactory.getLogger(JBIG2ImageReader.class);
+
+  public static final boolean DEBUG = false;
+  public static final boolean PERFORMANCE_TEST = false;
+
+  /** JBIG2 document to which we delegate current work. */
+  private JBIG2Document document;
+
+  /** Globals are JBIG2 segments for PDF wide use. */
+  private JBIG2Globals globals;
+
+  /**
+   * @see ImageReader#ImageReader(ImageReaderSpi)
+   * 
+   * @param originatingProvider - The {@code ImageReaderSpi} that is invoking this constructor, or
+   *          {@code null}.
+   * @throws IOException if something went wrong while reading the provided stream.
+   */
+  public JBIG2ImageReader(ImageReaderSpi originatingProvider) throws IOException {
+    super(originatingProvider);
+  }
+
+  /**
+   * @see ImageReader#getDefaultReadParam()
+   */
+  @Override
+  public JBIG2ReadParam getDefaultReadParam() {
+    return new JBIG2ReadParam();
+  }
+
+  /**
+   * Returns a default {@linkplain ImageReadParam} object for a specific page.
+   * 
+   * @param imageIndex - The page number.
+   * @return
+   */
+  private JBIG2ReadParam getDefaultReadParam(final int imageIndex) {
+    int width = 1;
+    int height = 1;
+
+    try {
+      final int index = (imageIndex < getDocument().getAmountOfPages()) ? imageIndex : 0;
+      width = getWidth(index);
+      height = getHeight(index);
+    } catch (IOException e) {
+      if (log.isInfoEnabled()) {
+        log.info("Dimensions could not be determined. Returning read params with size " + width + "x" + height);
+      }
+    }
+
+    return new JBIG2ReadParam(1, 1, 0, 0, new Rectangle(0, 0, width, height), new Dimension(width, height));
+  }
+
+  /**
+   * Calculates the width of the specified page.
+   * 
+   * @param imageIndex - The image index. In this case it is the page number.
+   * 
+   * @return The width of the specified page.
+   * 
+   * @throws IOException if an error occurs reading the width information from the input source.
+   */
+  @Override
+  public int getWidth(int imageIndex) throws IOException {
+    return getDocument().getPage(imageIndex + 1).getWidth();
+  }
+
+  /**
+   * Calculates the height of the specified page.
+   * 
+   * @param imageIndex - The image index. In this case it is the page number.
+   * 
+   * @return The height of the specified page or {@code 0} if an error occurred.
+   * 
+   * @throws IOException if an error occurs reading the height information from the input source.
+   */
+  @Override
+  public int getHeight(int imageIndex) throws IOException {
+    try {
+      return getDocument().getPage(imageIndex + 1).getHeight();
+    } catch (JBIG2Exception e) {
+      throw new IOException(e.getMessage());
+    }
+  }
+
+  /**
+   * Simply returns the {@link JBIG2ImageMetadata}.
+   * 
+   * @return The associated {@link JBIG2ImageMetadata}.
+   * 
+   * @throws IOException if an error occurs reading the height information from the input source.
+   */
+  @Override
+  public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
+    return new JBIG2ImageMetadata(getDocument().getPage(imageIndex + 1));
+  }
+
+
+  /**
+   * Returns the iterator for available image types.
+   * 
+   * @param imageIndex - The page number.
+   * 
+   * @return An {@link Iterator} for available image types.
+   * 
+   * @throws IOException if an error occurs reading the height information from the input source.
+   */
+  @Override
+  public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
+    List<ImageTypeSpecifier> l = new ArrayList<ImageTypeSpecifier>();
+
+    l.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_INDEXED));
+
+    return l.iterator();
+  }
+
+  /**
+   * @see ImageReader#getNumImages(boolean)
+   */
+  @Override
+  public int getNumImages(boolean allowSearch) throws IOException {
+    if (allowSearch) {
+      if (getDocument().isAmountOfPagesUnknown()) {
+        log.info("Amount of pages is unknown.");
+      } else {
+        return getDocument().getAmountOfPages();
+      }
+    } else {
+      log.info("Search is not allowed.");
+    }
+    return -1;
+  }
+
+  /**
+   * This ImageIO plugin doesn't record {@link IIOMetadata}.
+   * 
+   * @return {@code null} at every call.
+   */
+  @Override
+  public IIOMetadata getStreamMetadata() {
+    log.info("No metadata recorded");
+    return null;
+  }
+
+  /**
+   * Returns decoded segments that has been set as globals. Globals are jbig2 segments that are used
+   * in embedded case for file wide access. They are not assigned to a specific page.
+   * 
+   * @return Decoded global segments.
+   * 
+   * @throws IOException if an error occurs reading the height information from the input source.
+   */
+  public JBIG2Globals getGlobals() throws IOException {
+    return getDocument().getGlobalSegments();
+  }
+
+  /**
+   * Returns the decoded image of specified page considering the given {@link JBIG2ReadParam}s.
+   * 
+   * @see ImageReader#read(int, ImageReadParam)
+   */
+  @Override
+  public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
+    if (param == null) {
+      log.info("JBIG2ReadParam not specified. Default will be used.");
+      param = (JBIG2ReadParam) getDefaultReadParam(imageIndex);
+    }
+
+    JBIG2Page page = getPage(imageIndex);
+
+    Bitmap pageBitmap = (Bitmap) CacheFactory.getCache().get(page);
+
+    if (pageBitmap == null) {
+      try {
+        pageBitmap = page.getBitmap();
+        CacheFactory.getCache().put(page, pageBitmap, pageBitmap.getMemorySize());
+        page.clearPageData();
+      } catch (JBIG2Exception e) {
+        throw new IOException(e.getMessage());
+      }
+    }
+
+    return Bitmaps.asBufferedImage(pageBitmap, param, FilterType.Gaussian);
+  }
+
+  public boolean canReadRaster() {
+    return true;
+  }
+
+  @Override
+  public Raster readRaster(int imageIndex, ImageReadParam param) throws IOException {
+    if (param == null) {
+      log.info("JBIG2ReadParam not specified. Default will be used.");
+      param = (JBIG2ReadParam) getDefaultReadParam(imageIndex);
+    }
+
+    JBIG2Page page = getPage(imageIndex);
+
+    Bitmap pageBitmap = (Bitmap) CacheFactory.getCache().get(page);
+    if (pageBitmap == null) {
+      try {
+        pageBitmap = page.getBitmap();
+        CacheFactory.getCache().put(page, pageBitmap, pageBitmap.getMemorySize());
+        page.clearPageData();
+      } catch (JBIG2Exception e) {
+        throw new IOException(e.getMessage());
+      }
+    }
+
+    return Bitmaps.asRaster(pageBitmap, param, FilterType.Gaussian);
+  }
+
+  /**
+   * Decodes and returns the global segments.
+   * 
+   * @param globalsInputStream - The input stream of globals data.
+   * 
+   * @return The decoded {@link JBIG2Globals}.
+   * 
+   * @throws IOException if an error occurs reading the height information from the input source.
+   */
+  public JBIG2Globals processGlobals(ImageInputStream globalsInputStream) throws IOException {
+    JBIG2Document doc = new JBIG2Document(globalsInputStream);
+    return doc.getGlobalSegments();
+  }
+
+  /**
+   * Simply sets the globals.
+   * 
+   * @param globals - The globals to set.
+   * @throws IOException
+   */
+  public void setGlobals(JBIG2Globals globals) throws IOException {
+    this.globals = globals;
+    this.document = null;
+  }
+
+  /**
+   * @see ImageReader#setInput(Object, boolean, boolean)
+   */
+  @Override
+  public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
+    super.setInput(input, seekForwardOnly, ignoreMetadata);
+    document = null;
+  }
+
+  private JBIG2Document getDocument() throws IOException {
+    if (this.document == null) {
+      if (this.input == null) {
+        throw new IOException("Input not set.");
+      }
+
+      if (this.globals == null) {
+        log.debug("Globals not set.");
+      }
+
+      this.document = new JBIG2Document((ImageInputStream) this.input, this.globals);
+    }
+    return this.document;
+  }
+
+  private JBIG2Page getPage(int imageIndex) throws IOException {
+    JBIG2Page page = getDocument().getPage(imageIndex + 1);
+
+    if (page == null)
+      throw new IndexOutOfBoundsException("Requested page at index=" + imageIndex + " does not exist.");
+
+    return page;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageReaderSpi.java b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageReaderSpi.java
new file mode 100644
index 0000000..04ec211
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ImageReaderSpi.java
@@ -0,0 +1,124 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.imageio.ImageReader;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+
+/**
+ * Necessary extension for ImageIO standard.
+ * 
+ * @see ImageReaderSpi
+ */
+public class JBIG2ImageReaderSpi extends ImageReaderSpi {
+
+  private static final String VENDOR = "levigo solutions gmbh";
+  private static final String VERSION = "1.4.1";
+  private static final String READER_CLASS_NAME = "org.apache.pdfbox.jbig2.JBIG2ImageReader";
+  private static final String[] NAMES = {
+      "jbig2", "JBIG2"
+  };
+  private static final String[] SUFFIXES = {
+      "jb2", "jbig2", "JB2", "JBIG2"
+  };
+  private static final String[] MIME_TYPES = {
+      "image/x-jbig2", "image/x-jb2"
+  };
+  private static final Class<?>[] INPUT_TYPES = { ImageInputStream.class };
+
+  /**
+   * According to D.4.1:
+   * <p>
+   * This preamble contains the unique id string for jbig2 files and can be used to check if this
+   * reader plugin can decode the given stream. This can only work with native jbig2 data. If the
+   * stream is embedded in another file format this preamble might be missing.
+   */
+  private static final int[] FILEHEADER_PREAMBLE = {
+      0x97, 0x4A, 0x42, 0x32, 0x0D, 0x0A, 0x1A, 0x0A
+  };
+
+  /* MK: I suppose, there won't be a writer in near future :-) */
+  private static final String[] WRITER_SPI_NAMES = {};
+
+  static final boolean SUPPORTS_STANDARD_STREAM_METADATE_FORMAT = false;
+  static final String NATIVE_STREAM_METADATA_FORMAT_NAME = "JBIG2 Stream Metadata";
+  static final String NATIVE_STREAM_METADATA_FORMAT_CLASSNAME = "JBIG2Metadata";
+  static final String[] EXTRA_STREAM_METADATA_FORMAT_NAME = null;
+  static final String[] EXTRA_STREAM_METADATA_FORMAT_CLASSNAME = null;
+
+  static final boolean SUPPORTS_STANDARD_IMAGE_METADATA_FORMAT = false;
+  static final String NATIVE_IMAGE_METADATA_FORMAT_NAME = "JBIG2 File Metadata";
+  static final String NATIVE_IMAGE_METADATA_FORMAT_CLASSNAME = "JBIG2Metadata";
+  static final String[] EXTRA_IMAGE_METADATA_FORMAT_NAME = null;
+  static final String[] EXTRA_IMAGE_METADATA_FORMAT_CLASSNAME = null;
+
+  public JBIG2ImageReaderSpi() {
+    super(VENDOR, VERSION, NAMES, SUFFIXES, MIME_TYPES, READER_CLASS_NAME, INPUT_TYPES, WRITER_SPI_NAMES,
+        SUPPORTS_STANDARD_STREAM_METADATE_FORMAT, NATIVE_STREAM_METADATA_FORMAT_NAME,
+        NATIVE_STREAM_METADATA_FORMAT_CLASSNAME, EXTRA_STREAM_METADATA_FORMAT_NAME,
+        EXTRA_STREAM_METADATA_FORMAT_CLASSNAME, SUPPORTS_STANDARD_IMAGE_METADATA_FORMAT,
+        NATIVE_IMAGE_METADATA_FORMAT_NAME, NATIVE_IMAGE_METADATA_FORMAT_CLASSNAME, EXTRA_IMAGE_METADATA_FORMAT_NAME,
+        EXTRA_IMAGE_METADATA_FORMAT_CLASSNAME);
+  }
+
+  /*
+   * Checks, if the file header begins with the preamble id string defined in D.4.1, page 100
+   * 
+   * (non-Javadoc)
+   * 
+   * @see javax.imageio.spi.ImageReaderSpi#canDecodeInput(java.lang.Object)
+   */
+  @Override
+  public boolean canDecodeInput(Object source) throws IOException {
+    if (source == null)
+      throw new IllegalArgumentException("source must not be null");
+
+    if (!(source instanceof ImageInputStream)) {
+      System.out.println("source is not an ImageInputStream");
+      return false;
+    }
+
+    ImageInputStream iis = (ImageInputStream) source;
+    iis.mark();
+
+    for (int i = 0; i < FILEHEADER_PREAMBLE.length; i++) {
+      int read = (iis.read() & 0xFF);
+      if (read != FILEHEADER_PREAMBLE[i]) {
+        return false;
+      }
+    }
+
+    iis.reset();
+    return true;
+  }
+
+  @Override
+  public ImageReader createReaderInstance(Object extension) throws IOException {
+    return new JBIG2ImageReader(this);
+  }
+
+  @Override
+  public String getDescription(Locale locale) {
+    return "JBIG2 Image Reader";
+  }
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/JBIG2Page.java b/src/main/java/org/apache/pdfbox/jbig2/JBIG2Page.java
new file mode 100644
index 0000000..566cdf7
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/JBIG2Page.java
@@ -0,0 +1,376 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.err.JBIG2Exception;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.segments.EndOfStripe;
+import org.apache.pdfbox.jbig2.segments.PageInformation;
+import org.apache.pdfbox.jbig2.segments.RegionSegmentInformation;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represents a JBIG2 page.
+ */
+class JBIG2Page {
+
+  private static final Logger log = LoggerFactory.getLogger(JBIG2Page.class);
+
+  /**
+   * This list contains all segments of this page, sorted by segment number in ascending order.
+   */
+  private final Map<Integer, SegmentHeader> segments = new TreeMap<Integer, SegmentHeader>();
+
+  /** NOTE: page number != segmentList index */
+  private final int pageNumber;
+
+  /** The page bitmap that represents the page buffer */
+  private Bitmap pageBitmap;
+
+  private int finalHeight;
+  private int finalWidth;
+  private int resolutionX;
+  private int resolutionY;
+
+  private final JBIG2Document document;
+
+  protected JBIG2Page(JBIG2Document document, int pageNumber) {
+    this.document = document;
+    this.pageNumber = pageNumber;
+  }
+
+  /**
+   * This method searches for a segment specified by its number.
+   * 
+   * @param number - Segment number of the segment to search.
+   * 
+   * @return The retrieved {@link SegmentHeader} or {@code null} if not available.
+   */
+  public SegmentHeader getSegment(int number) {
+    SegmentHeader s = segments.get(number);
+
+    if (null != s) {
+      return s;
+    }
+
+    if (null != document) {
+      return document.getGlobalSegment(number);
+    }
+
+    log.info("Segment not found, returning null.");
+    return null;
+  }
+
+  /**
+   * Returns the associated page information segment.
+   * 
+   * @return The associated {@link PageInformation} segment or {@code null} if not available.
+   */
+  protected SegmentHeader getPageInformationSegment() {
+    for (SegmentHeader s : segments.values()) {
+      if (s.getSegmentType() == 48) {
+        return s;
+      }
+    }
+
+    log.info("Page information segment not found.");
+    return null;
+  }
+
+  /**
+   * This method returns the decoded bitmap if present. Otherwise the page bitmap will be composed
+   * before returning the result.
+   * 
+   * @return pageBitmap - The result of decoding a page
+   * @throws JBIG2Exception
+   * @throws IOException
+   */
+  protected Bitmap getBitmap() throws JBIG2Exception, IOException {
+    long timestamp;
+
+    if (JBIG2ImageReader.PERFORMANCE_TEST) {
+      timestamp = System.currentTimeMillis();
+    }
+
+    if (null == pageBitmap) {
+      composePageBitmap();
+    }
+
+    if (JBIG2ImageReader.PERFORMANCE_TEST) {
+      log.info("PAGE DECODING: " + (System.currentTimeMillis() - timestamp) + " ms");
+    }
+
+    return pageBitmap;
+  }
+
+  /**
+   * This method composes the segments' bitmaps to a page and stores the page as a {@link Bitmap}
+   * 
+   * @throws IOException
+   * @throws JBIG2Exception
+   */
+  private void composePageBitmap() throws IOException, JBIG2Exception {
+    if (pageNumber > 0) {
+      // Page 79, 1) Decoding the page information segment
+      PageInformation pageInformation = (PageInformation) getPageInformationSegment().getSegmentData();
+      createPage(pageInformation);
+      clearSegmentData();
+    }
+  }
+
+  private void createPage(PageInformation pageInformation) throws IOException, IntegerMaxValueException,
+      InvalidHeaderValueException {
+    if (!pageInformation.isStriped() || pageInformation.getHeight() != -1) {
+      // Page 79, 4)
+      createNormalPage(pageInformation);
+    } else {
+      createStripedPage(pageInformation);
+    }
+  }
+
+  private void createNormalPage(PageInformation pageInformation) throws IOException, IntegerMaxValueException,
+      InvalidHeaderValueException {
+
+    pageBitmap = new Bitmap(pageInformation.getWidth(), pageInformation.getHeight());
+
+    // Page 79, 3)
+    // If default pixel value is not 0, byte will be filled with 0xff
+    if (pageInformation.getDefaultPixelValue() != 0) {
+      Arrays.fill(pageBitmap.getByteArray(), (byte) 0xff);
+    }
+
+    for (SegmentHeader s : segments.values()) {
+      // Page 79, 5)
+      switch (s.getSegmentType()){
+        case 6 : // Immediate text region
+        case 7 : // Immediate lossless text region
+        case 22 : // Immediate halftone region
+        case 23 : // Immediate lossless halftone region
+        case 38 : // Immediate generic region
+        case 39 : // Immediate lossless generic region
+        case 42 : // Immediate generic refinement region
+        case 43 : // Immediate lossless generic refinement region
+          final Region r = (Region) s.getSegmentData();
+
+          final Bitmap regionBitmap = r.getRegionBitmap();
+
+          if (fitsPage(pageInformation, regionBitmap)) {
+            pageBitmap = regionBitmap;
+          } else {
+            final RegionSegmentInformation regionInfo = r.getRegionInfo();
+            final CombinationOperator op = getCombinationOperator(pageInformation,
+                regionInfo.getCombinationOperator());
+            Bitmaps.blit(regionBitmap, pageBitmap, regionInfo.getXLocation(), regionInfo.getYLocation(), op);
+          }
+
+          break;
+      }
+    }
+  }
+
+  /**
+   * Check if we have only one region that forms the complete page. If the dimension equals the
+   * page's dimension set the region's bitmap as the page's bitmap. Otherwise we have to blit the
+   * smaller region's bitmap into the page's bitmap (see Issue 6).
+   * 
+   * @param pageInformation
+   * @param regionBitmap
+   * @return
+   */
+  private boolean fitsPage(PageInformation pageInformation, final Bitmap regionBitmap) {
+    return countRegions() == 1 && pageInformation.getDefaultPixelValue() == 0
+        && pageInformation.getWidth() == regionBitmap.getWidth()
+        && pageInformation.getHeight() == regionBitmap.getHeight();
+  }
+
+  private void createStripedPage(PageInformation pageInformation) throws IOException, IntegerMaxValueException,
+      InvalidHeaderValueException {
+    final ArrayList<SegmentData> pageStripes = collectPageStripes();
+
+    pageBitmap = new Bitmap(pageInformation.getWidth(), finalHeight);
+
+    int startLine = 0;
+    for (SegmentData sd : pageStripes) {
+      if (sd instanceof EndOfStripe) {
+        startLine = ((EndOfStripe) sd).getLineNumber() + 1;
+      } else {
+        final Region r = (Region) sd;
+        final RegionSegmentInformation regionInfo = r.getRegionInfo();
+        final CombinationOperator op = getCombinationOperator(pageInformation, regionInfo.getCombinationOperator());
+        Bitmaps.blit(r.getRegionBitmap(), pageBitmap, regionInfo.getXLocation(), startLine, op);
+      }
+    }
+  }
+
+  private ArrayList<SegmentData> collectPageStripes() {
+    final ArrayList<SegmentData> pageStripes = new ArrayList<SegmentData>();
+    for (SegmentHeader s : segments.values()) {
+      // Page 79, 5)
+      switch (s.getSegmentType()){
+        case 6 : // Immediate text region
+        case 7 : // Immediate lossless text region
+        case 22 : // Immediate halftone region
+        case 23 : // Immediate lossless halftone region
+        case 38 : // Immediate generic region
+        case 39 : // Immediate lossless generic region
+        case 42 : // Immediate generic refinement region
+        case 43 : // Immediate lossless generic refinement region
+          Region r = (Region) s.getSegmentData();
+          pageStripes.add(r);
+          break;
+
+        case 50 : // End of stripe
+          EndOfStripe eos = (EndOfStripe) s.getSegmentData();
+          pageStripes.add(eos);
+          finalHeight = eos.getLineNumber() + 1;
+          break;
+      }
+    }
+
+    return pageStripes;
+  }
+
+  /**
+   * This method counts the regions segments. If there is only one region, the bitmap of this
+   * segment is equal to the page bitmap and blitting is not necessary.
+   * 
+   * @return Amount of regions.
+   */
+  private int countRegions() {
+    int regionCount = 0;
+
+    for (SegmentHeader s : segments.values()) {
+      switch (s.getSegmentType()){
+        case 6 : // Immediate text region
+        case 7 : // Immediate lossless text region
+        case 22 : // Immediate halftone region
+        case 23 : // Immediate lossless halftone region
+        case 38 : // Immediate generic region
+        case 39 : // Immediate lossless generic region
+        case 42 : // Immediate generic refinement region
+        case 43 : // Immediate lossless generic refinement region
+          regionCount++;
+      }
+    }
+
+    return regionCount;
+  }
+
+  /**
+   * This method checks and sets, which combination operator shall be used.
+   * 
+   * @param pi - <code>PageInformation</code> object
+   * @param newOperator - The combination operator, specified by actual segment
+   * @return the new combination operator
+   */
+  private CombinationOperator getCombinationOperator(PageInformation pi, CombinationOperator newOperator) {
+    if (pi.isCombinationOperatorOverrideAllowed()) {
+      return newOperator;
+    } else {
+      return pi.getCombinationOperator();
+    }
+  }
+
+  /**
+   * Adds a {@link SegmentHeader} into the page's segments map.
+   * 
+   * @param segment - The segment to be added.
+   */
+  protected void add(SegmentHeader segment) {
+
+    segments.put(segment.getSegmentNr(), segment);
+  }
+
+  /**
+   * Resets the memory-critical segments to force on-demand-decoding and to avoid holding the
+   * segments' bitmap too long.
+   */
+  private void clearSegmentData() {
+    Set<Integer> keySet = segments.keySet();
+
+    for (Integer key : keySet) {
+      segments.get(key).cleanSegmentData();
+    }
+  }
+
+  /**
+   * Reset memory-critical parts of page.
+   */
+  protected void clearPageData() {
+    pageBitmap = null;
+  }
+
+  /**
+   * Returns the final height of the page.
+   * 
+   * @return The final height of the page.
+   * @throws IOException
+   * @throws JBIG2Exception
+   */
+  protected int getHeight() throws IOException, JBIG2Exception {
+    if (finalHeight == 0) {
+      PageInformation pi = (PageInformation) getPageInformationSegment().getSegmentData();
+      if (pi.getHeight() == 0xffffffff) {
+        getBitmap();
+      } else {
+        finalHeight = pi.getHeight();
+      }
+    }
+    return finalHeight;
+  }
+
+  protected int getWidth() {
+    if (finalWidth == 0) {
+      PageInformation pi = (PageInformation) getPageInformationSegment().getSegmentData();
+      finalWidth = pi.getWidth();
+    }
+    return finalWidth;
+  }
+
+  protected int getResolutionX() {
+    if (resolutionX == 0) {
+      PageInformation pi = (PageInformation) getPageInformationSegment().getSegmentData();
+      resolutionX = pi.getResolutionX();
+    }
+    return resolutionX;
+  }
+
+  protected int getResolutionY() {
+    if (resolutionY == 0) {
+      PageInformation pi = (PageInformation) getPageInformationSegment().getSegmentData();
+      resolutionY = pi.getResolutionY();
+    }
+    return resolutionY;
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + " (Page number: " + pageNumber + ")";
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/JBIG2ReadParam.java b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ReadParam.java
new file mode 100644
index 0000000..7554df2
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/JBIG2ReadParam.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+
+import javax.imageio.ImageReadParam;
+
+/**
+ * This class extends {@code ImageReadParam} and contains region of interest and scale / subsampling
+ * functionality
+ */
+public class JBIG2ReadParam extends ImageReadParam {
+
+  public JBIG2ReadParam() {
+    this(1, 1, 0, 0, null, null);
+  }
+
+  public JBIG2ReadParam(final int sourceXSubsampling, final int sourceYSubsampling, final int subsamplingXOffset,
+      final int subsamplingYOffset, final Rectangle sourceRegion, final Dimension sourceRenderSize) {
+    this.canSetSourceRenderSize = true;
+    this.sourceRegion = sourceRegion;
+    this.sourceRenderSize = sourceRenderSize;
+
+    if (sourceXSubsampling < 1 || sourceYSubsampling < 1) {
+      throw new IllegalArgumentException("Illegal subsampling factor: shall be 1 or greater; but was "
+          + " sourceXSubsampling=" + sourceXSubsampling + ", sourceYSubsampling=" + sourceYSubsampling);
+    }
+
+    setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/Region.java b/src/main/java/org/apache/pdfbox/jbig2/Region.java
new file mode 100644
index 0000000..dd30dcd
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/Region.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.segments.RegionSegmentInformation;
+
+/**
+ * Interface for all JBIG2 region segments.
+ */
+public interface Region extends SegmentData {
+
+  /**
+   * Decodes and returns a regions content.
+   * 
+   * @return The decoded region as {@link Bitmap}.
+   * 
+   * @throws IOException
+   * @throws IntegerMaxValueException
+   * @throws InvalidHeaderValueException
+   */
+  public Bitmap getRegionBitmap() throws IOException, IntegerMaxValueException, InvalidHeaderValueException;
+
+  /**
+   * Simply returns the {@link RegionSegmentInformation}.
+   * 
+   * @return The {@link RegionSegmentInformation}.
+   */
+  public RegionSegmentInformation getRegionInfo();
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/SegmentData.java b/src/main/java/org/apache/pdfbox/jbig2/SegmentData.java
new file mode 100644
index 0000000..8efca74
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/SegmentData.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+
+/**
+ * Interface for all data parts of segments.
+ */
+public interface SegmentData {
+
+  /**
+   * Parse the stream and read information of header.
+   * 
+   * @param header - The segments' header (to make referred-to segments available in data part).
+   * @param sis - Wrapped {@code ImageInputStream} into {@code SubInputStream}.
+   * 
+   * @throws InvalidHeaderValueException
+   * @throws IntegerMaxValueException
+   * @throws IOException
+   */
+  public void init(SegmentHeader header, SubInputStream sis) throws InvalidHeaderValueException, IntegerMaxValueException,
+      IOException;
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/SegmentHeader.java b/src/main/java/org/apache/pdfbox/jbig2/SegmentHeader.java
new file mode 100644
index 0000000..c4e9cea
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/SegmentHeader.java
@@ -0,0 +1,432 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.segments.EndOfStripe;
+import org.apache.pdfbox.jbig2.segments.GenericRefinementRegion;
+import org.apache.pdfbox.jbig2.segments.GenericRegion;
+import org.apache.pdfbox.jbig2.segments.HalftoneRegion;
+import org.apache.pdfbox.jbig2.segments.PageInformation;
+import org.apache.pdfbox.jbig2.segments.PatternDictionary;
+import org.apache.pdfbox.jbig2.segments.Profiles;
+import org.apache.pdfbox.jbig2.segments.SymbolDictionary;
+import org.apache.pdfbox.jbig2.segments.Table;
+import org.apache.pdfbox.jbig2.segments.TextRegion;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * The basic class for all JBIG2 segments.
+ */
+@SuppressWarnings("unchecked")
+public class SegmentHeader {
+  private static final Logger log = LoggerFactory.getLogger(SegmentHeader.class);
+
+  private static final Map<Integer, Class<? extends SegmentData>> SEGMENT_TYPE_MAP = new HashMap<Integer, Class<? extends SegmentData>>();
+
+  static {
+    Object SEGMENT_TYPES[][] = {
+        {
+            0, SymbolDictionary.class
+        }, {
+            4, TextRegion.class
+        }, {
+            6, TextRegion.class
+        }, {
+            7, TextRegion.class
+        }, {
+            16, PatternDictionary.class
+        }, {
+            20, HalftoneRegion.class
+        }, {
+            22, HalftoneRegion.class
+        }, {
+            23, HalftoneRegion.class
+        }, {
+            36, GenericRegion.class
+        }, {
+            38, GenericRegion.class
+        }, {
+            39, GenericRegion.class
+        }, {
+            40, GenericRefinementRegion.class
+        }, {
+            42, GenericRefinementRegion.class
+        }, {
+            43, GenericRefinementRegion.class
+        }, {
+            48, PageInformation.class
+        }, {
+            50, EndOfStripe.class
+        }, {
+            52, Profiles.class
+        }, {
+            53, Table.class
+        },
+    };
+
+    for (int i = 0; i < SEGMENT_TYPES.length; i++) {
+      Object[] objects = SEGMENT_TYPES[i];
+      SEGMENT_TYPE_MAP.put((Integer) objects[0], (Class<? extends SegmentData>) objects[1]);
+    }
+  }
+
+  private int segmentNr;
+  private int segmentType;
+  private byte retainFlag;
+  private int pageAssociation;
+  private byte pageAssociationFieldSize;
+  private SegmentHeader[] rtSegments;
+  private long segmentHeaderLength;
+  private long segmentDataLength;
+  private long segmentDataStartOffset;
+  private final SubInputStream subInputStream;
+
+  private Reference<SegmentData> segmentData;
+
+
+  public SegmentHeader(JBIG2Document document, SubInputStream sis, long offset, int organisationType)
+      throws IOException {
+    this.subInputStream = sis;
+    parse(document, sis, offset, organisationType);
+  }
+
+  /**
+   * 
+   * 
+   * @param document
+   * @param subInputStream
+   * @param organisationType
+   * @param offset - The offset where the segment header starts
+   * @throws IOException
+   */
+  private void parse(JBIG2Document document, ImageInputStream subInputStream, long offset, int organisationType)
+      throws IOException {
+
+    printDebugMessage("\n########################");
+    printDebugMessage("Segment parsing started.");
+
+    subInputStream.seek(offset);
+    printDebugMessage("|-Seeked to offset: " + offset);
+
+    /* 7.2.2 Segment number */
+    readSegmentNumber(subInputStream);
+
+    /* 7.2.3 Segment header flags */
+    readSegmentHeaderFlag(subInputStream);
+
+    /* 7.2.4 Amount of referred-to segments */
+    int countOfRTS = readAmountOfReferredToSegments(subInputStream);
+
+    /* 7.2.5 Referred-to segments numbers */
+    int[] rtsNumbers = readReferredToSegmentsNumbers(subInputStream, countOfRTS);
+
+    /* 7.2.6 Segment page association (Checks how big the page association field is.) */
+    readSegmentPageAssociation(document, subInputStream, countOfRTS, rtsNumbers);
+
+    /* 7.2.7 Segment data length (Contains the length of the data part (in bytes).) */
+    readSegmentDataLength(subInputStream);
+
+    readDataStartOffset(subInputStream, organisationType);
+    readSegmentHeaderLength(subInputStream, offset);
+    printDebugMessage("########################\n");
+  }
+
+  /**
+   * 7.2.2 Segment number
+   * 
+   * @param subInputStream
+   * @throws IOException
+   */
+  private void readSegmentNumber(ImageInputStream subInputStream) throws IOException {
+    segmentNr = (int) (subInputStream.readBits(32) & 0xffffffff);
+    printDebugMessage("|-Segment Nr: " + segmentNr);
+  }
+
+  /**
+   * 7.2.3 Segment header flags
+   * 
+   * @param subInputStream
+   * @throws IOException
+   */
+  private void readSegmentHeaderFlag(ImageInputStream subInputStream) throws IOException {
+    // Bit 7: Retain Flag, if 1, this segment is flagged as retained;
+    retainFlag = (byte) subInputStream.readBit();
+    printDebugMessage("|-Retain flag: " + retainFlag);
+
+    // Bit 6: Size of the page association field. One byte if 0, four bytes if 1;
+    pageAssociationFieldSize = (byte) subInputStream.readBit();
+    printDebugMessage("|-Page association field size=" + pageAssociationFieldSize);
+
+    // Bit 5-0: Contains the values (between 0 and 62 with gaps) for segment types, specified in 7.3
+    segmentType = (int) (subInputStream.readBits(6) & 0xff);
+    printDebugMessage("|-Segment type=" + segmentType);
+  }
+
+  /**
+   * 7.2.4 Amount of referred-to segments
+   * 
+   * @param subInputStream
+   * @return The amount of referred-to segments.
+   * @throws IOException
+   */
+  private int readAmountOfReferredToSegments(ImageInputStream subInputStream) throws IOException {
+    int countOfRTS = (int) (subInputStream.readBits(3) & 0xf);
+    printDebugMessage("|-RTS count: " + countOfRTS);
+
+    byte[] retainBit;
+
+    printDebugMessage("  |-Stream position before RTS: " + subInputStream.getStreamPosition());
+
+    if (countOfRTS <= 4) {
+      /* short format */
+      retainBit = new byte[5];
+      for (int i = 0; i <= 4; i++) {
+        retainBit[i] = (byte) subInputStream.readBit();
+      }
+    } else {
+      /* long format */
+      countOfRTS = (int) (subInputStream.readBits(29) & 0xffffffff);
+
+      int arrayLength = (countOfRTS + 8) >> 3;
+      retainBit = new byte[arrayLength <<= 3];
+
+      for (int i = 0; i < arrayLength; i++) {
+        retainBit[i] = (byte) subInputStream.readBit();
+      }
+    }
+
+    printDebugMessage("  |-Stream position after RTS: " + subInputStream.getStreamPosition());
+
+    return countOfRTS;
+  }
+
+  /**
+   * 7.2.5 Referred-to segments numbers
+   * <p>
+   * Gathers all segment numbers of referred-to segments. The segments itself are stored in the
+   * {@link #rtSegments} array.
+   * 
+   * @param subInputStream - Wrapped source data input stream.
+   * @param countOfRTS - The amount of referred-to segments.
+   * 
+   * @return An array with the segment number of all referred-to segments.
+   * 
+   * @throws IOException
+   */
+  private int[] readReferredToSegmentsNumbers(ImageInputStream subInputStream, int countOfRTS) throws IOException {
+    int[] rtsNumbers = new int[countOfRTS];
+
+    if (countOfRTS > 0) {
+      short rtsSize = 1;
+      if (segmentNr > 256) {
+        rtsSize = 2;
+        if (segmentNr > 65536) {
+          rtsSize = 4;
+        }
+      }
+
+      rtSegments = new SegmentHeader[countOfRTS];
+
+      printDebugMessage("|-Length of RT segments list: " + rtSegments.length);
+
+      for (int i = 0; i < countOfRTS; i++) {
+        rtsNumbers[i] = (int) (subInputStream.readBits(rtsSize << 3) & 0xffffffff);
+      }
+    }
+
+    return rtsNumbers;
+  }
+
+  /**
+   * 7.2.6 Segment page association
+   * 
+   * @param document
+   * @param subInputStream
+   * @param countOfRTS
+   * @param rtsNumbers
+   * @throws IOException
+   */
+  private void readSegmentPageAssociation(JBIG2Document document, ImageInputStream subInputStream, int countOfRTS,
+      int[] rtsNumbers) throws IOException {
+    if (pageAssociationFieldSize == 0) {
+      // Short format
+      pageAssociation = (short) (subInputStream.readBits(8) & 0xff);
+    } else {
+      // Long format
+      pageAssociation = (int) (subInputStream.readBits(32) & 0xffffffff);
+    }
+
+    if (countOfRTS > 0) {
+      final JBIG2Page page = document.getPage(pageAssociation);
+      for (int i = 0; i < countOfRTS; i++) {
+        rtSegments[i] = (null != page ? page.getSegment(rtsNumbers[i]) : document.getGlobalSegment(rtsNumbers[i]));
+      }
+    }
+  }
+
+  /**
+   * 7.2.7 Segment data length
+   * <p>
+   * Contains the length of the data part in bytes.
+   * 
+   * @param subInputStream
+   * @throws IOException
+   */
+  private void readSegmentDataLength(ImageInputStream subInputStream) throws IOException {
+    segmentDataLength = (subInputStream.readBits(32) & 0xffffffff);
+    printDebugMessage("|-Data length: " + segmentDataLength);
+  }
+
+  /**
+   * Sets the offset only if organization type is SEQUENTIAL. If random, data starts after segment
+   * headers and can be determined when all segment headers are parsed and allocated.
+   * 
+   * @param subInputStream
+   * @param organisationType
+   * @throws IOException
+   */
+  private void readDataStartOffset(ImageInputStream subInputStream, int organisationType) throws IOException {
+    if (organisationType == JBIG2Document.SEQUENTIAL) {
+      printDebugMessage("|-Organization is sequential.");
+      segmentDataStartOffset = subInputStream.getStreamPosition();
+    }
+  }
+
+  private void readSegmentHeaderLength(ImageInputStream subInputStream, long offset) throws IOException {
+    segmentHeaderLength = subInputStream.getStreamPosition() - offset;
+    printDebugMessage("|-Segment header length: " + segmentHeaderLength);
+  }
+
+  private void printDebugMessage(String message) {
+    log.debug(message);
+  }
+
+  public int getSegmentNr() {
+    return segmentNr;
+  }
+
+  public int getSegmentType() {
+    return segmentType;
+  }
+
+  public long getSegmentHeaderLength() {
+    return segmentHeaderLength;
+  }
+
+  public long getSegmentDataLength() {
+    return segmentDataLength;
+  }
+
+  public long getSegmentDataStartOffset() {
+    return segmentDataStartOffset;
+  }
+
+  public void setSegmentDataStartOffset(long segmentDataStartOffset) {
+    this.segmentDataStartOffset = segmentDataStartOffset;
+  }
+
+  public SegmentHeader[] getRtSegments() {
+    return rtSegments;
+  }
+
+  public int getPageAssociation() {
+    return pageAssociation;
+  }
+
+  public short getRetainFlag() {
+    return retainFlag;
+  }
+
+  /**
+   * Creates and returns a new {@link SubInputStream} that provides the data part of this segment.
+   * It is a clipped view of the source input stream.
+   * 
+   * @return The {@link SubInputStream} that represents the data part of the segment.
+   */
+  public SubInputStream getDataInputStream() {
+    return new SubInputStream(subInputStream, segmentDataStartOffset, segmentDataLength);
+  }
+
+  /**
+   * Retrieves the segments' data part.
+   * 
+   * @return Retrieved {@link SegmentData} instance.
+   */
+  public SegmentData getSegmentData() {
+    SegmentData segmentDataPart = null;
+
+    if (null != segmentData) {
+      segmentDataPart = segmentData.get();
+    }
+
+    if (null == segmentDataPart) {
+      try {
+
+        Class<? extends SegmentData> segmentClass = SEGMENT_TYPE_MAP.get(segmentType);
+
+        if (null == segmentClass) {
+          throw new IllegalArgumentException("No segment class for type " + segmentType);
+        }
+
+        segmentDataPart = segmentClass.newInstance();
+        segmentDataPart.init(this, getDataInputStream());
+
+        segmentData = new SoftReference<SegmentData>(segmentDataPart);
+
+      } catch (Exception e) {
+        throw new RuntimeException("Can't instantiate segment class", e);
+      }
+    }
+
+    return segmentDataPart;
+  }
+
+  public void cleanSegmentData() {
+    if (segmentData != null) {
+      segmentData = null;
+    }
+  }
+
+  public String toString() {
+    StringBuilder stringBuilder = new StringBuilder();
+
+    if (rtSegments != null) {
+      for (SegmentHeader s : rtSegments) {
+        stringBuilder.append(s.segmentNr + " ");
+      }
+    } else {
+      stringBuilder.append("none");
+    }
+
+    return "\n#SegmentNr: " + segmentNr //
+        + "\n SegmentType: " + segmentType //
+        + "\n PageAssociation: " + pageAssociation //
+        + "\n Referred-to segments: " + stringBuilder.toString() //
+        + "\n"; //
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/TestImage.java b/src/main/java/org/apache/pdfbox/jbig2/TestImage.java
new file mode 100644
index 0000000..10bb9c7
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/TestImage.java
@@ -0,0 +1,242 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.MediaTracker;
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.IndexColorModel;
+import java.awt.image.MultiPixelPackedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+
+/**
+ * This is a utility class. It can be used to show intermediary results.
+ */
+public class TestImage extends JFrame {
+  private static final long serialVersionUID = 7353175320371957550L;
+
+  public static void main(String[] args) {
+    int w = 250;
+    int h = 250;
+
+    // (w+7) / 8 entspricht Aufrundung!
+    int scanlineStride = (w + 7) / 8;
+
+    // hier sind die Daten
+    byte data[] = new byte[h * scanlineStride];
+
+    // dummy-Daten erzeugen
+    for (int i = 0; i < data.length; i++)
+      data[i] = (byte) i;
+
+    new TestImage(data, w, h, scanlineStride);
+  }
+
+  static class ImageComponent extends JComponent {
+    private static final long serialVersionUID = -5921296548288376287L;
+    Image myImage;
+    int imgWidth = -1;
+    int imgHeight = -1;
+    Dimension prefSize = null;
+    private int scale = 1;
+
+    /**
+     * Constructor for ImageComponent.
+     */
+    protected ImageComponent() {
+      super();
+    }
+
+    /**
+     * Constructor for ImageComponent.
+     */
+    public ImageComponent(Image image) {
+      super();
+      setImage(image);
+    }
+
+    /**
+     * Gets the preffered Size of the Component
+     * 
+     * @param image java.awt.Image
+     */
+    public Dimension getPreferredSize() {
+      if (prefSize != null)
+        return this.prefSize;
+      else
+        return super.getPreferredSize();
+    }
+
+    /**
+     * Gets the minimum Size of the Component
+     * 
+     * @param image java.awt.Image
+     */
+    public Dimension getMinimumSize() {
+      if (prefSize != null)
+        return prefSize;
+      else
+        return super.getMinimumSize();
+    }
+
+    /**
+     * Sets an image to be shown
+     * 
+     * @param image java.awt.Image
+     */
+    public void setImage(Image image) {
+      if (myImage != null) {
+        myImage.flush();
+      }
+
+      myImage = image;
+
+      if (myImage != null) {
+        MediaTracker mt = new MediaTracker(this);
+
+        mt.addImage(myImage, 0);
+
+        try {
+          mt.waitForAll();
+        } catch (Exception ex) {
+        }
+
+        imgWidth = myImage.getWidth(this);
+        imgHeight = myImage.getHeight(this);
+
+        setSize(imgWidth * scale, imgHeight * scale);
+        prefSize = getSize();
+        invalidate();
+        validate();
+        repaint();
+      }
+    }
+
+    /**
+     * Get the Insets fo the Component
+     * 
+     * @return Insets the Insets of the Component
+     */
+    public Insets getInsets() {
+      return new Insets(1, 1, 1, 1);
+    }
+
+    /**
+     * Paints the component
+     * 
+     * @param g java.awt.Graphics
+     */
+    protected void paintComponent(Graphics g) {
+      Graphics2D g2 = (Graphics2D) g;
+      if (myImage != null) {
+        g2.scale(scale, scale);
+        g2.drawImage(myImage, 1, 1, imgWidth, imgHeight, this);
+      }
+    }
+
+    public void setScale(int scale) {
+      this.scale = scale;
+
+      setSize(imgWidth * scale, imgHeight * scale);
+      prefSize = getSize();
+
+      revalidate();
+      repaint();
+    }
+
+    public int getScale() {
+      return scale;
+    }
+  }
+
+  public TestImage(byte data[], int w, int h, int scanlineStride) {
+    super("Demobild");
+
+    // Color-Model sagt: bit = 0 -> schwarz, bit = 1 -> weiss. Ggf. umdrehen.
+    ColorModel colorModel = new IndexColorModel(1, 2, new byte[]{
+        (byte) 0xff, 0x00
+    }, new byte[]{
+        (byte) 0xff, 0x00
+    }, new byte[]{
+        (byte) 0xff, 0x00
+    });
+
+    DataBuffer dataBuffer = new DataBufferByte(data, data.length);
+    SampleModel sampleModel = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, w, h, 1, scanlineStride, 0);
+    WritableRaster writableRaster = Raster.createWritableRaster(sampleModel, dataBuffer, new Point(0, 0));
+
+    BufferedImage image = new BufferedImage(colorModel, writableRaster, false, null);
+
+    ImageComponent imageComponent = new ImageComponent(image);
+    // imageComponent.setScale(4);
+
+    JScrollPane sp = new JScrollPane(imageComponent);
+
+    setContentPane(sp);
+
+    pack();
+    setSize(new Dimension(1600, 900));
+    setVisible(true);
+
+    try {
+      System.in.read();
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+  }
+
+  public TestImage(BufferedImage bufferedImage) {
+    super("Demobild");
+
+    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+    ImageComponent imageComponent = new ImageComponent(bufferedImage);
+    imageComponent.setScale(1);
+
+    JScrollPane sp = new JScrollPane(imageComponent);
+
+    setContentPane(sp);
+
+    pack();
+    setSize(new Dimension(1600, 900));
+    setVisible(true);
+
+    try {
+      System.in.read();
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticDecoder.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticDecoder.java
new file mode 100644
index 0000000..b380afd
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticDecoder.java
@@ -0,0 +1,262 @@
+/**
+ * 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.arithmetic;
+
+import java.io.IOException;
+
+import javax.imageio.stream.ImageInputStream;
+
+/**
+ * This class represents the arithmetic decoder, described in ISO/IEC 14492:2001 in E.3
+ */
+public class ArithmeticDecoder {
+
+  private static final int QE[][] = {
+      {
+          0x5601, 1, 1, 1
+      }, {
+          0x3401, 2, 6, 0
+      }, {
+          0x1801, 3, 9, 0
+      }, {
+          0x0AC1, 4, 12, 0
+      }, {
+          0x0521, 5, 29, 0
+      }, {
+          0x0221, 38, 33, 0
+      }, {
+          0x5601, 7, 6, 1
+      }, {
+          0x5401, 8, 14, 0
+      }, {
+          0x4801, 9, 14, 0
+      }, {
+          0x3801, 10, 14, 0
+      }, {
+          0x3001, 11, 17, 0
+      }, {
+          0x2401, 12, 18, 0
+      }, {
+          0x1C01, 13, 20, 0
+      }, {
+          0x1601, 29, 21, 0
+      }, {
+          0x5601, 15, 14, 1
+      }, {
+          0x5401, 16, 14, 0
+      }, {
+          0x5101, 17, 15, 0
+      }, {
+          0x4801, 18, 16, 0
+      }, {
+          0x3801, 19, 17, 0
+      }, {
+          0x3401, 20, 18, 0
+      }, {
+          0x3001, 21, 19, 0
+      }, {
+          0x2801, 22, 19, 0
+      }, {
+          0x2401, 23, 20, 0
+      }, {
+          0x2201, 24, 21, 0
+      }, {
+          0x1C01, 25, 22, 0
+      }, {
+          0x1801, 26, 23, 0
+      }, {
+          0x1601, 27, 24, 0
+      }, {
+          0x1401, 28, 25, 0
+      }, {
+          0x1201, 29, 26, 0
+      }, {
+          0x1101, 30, 27, 0
+      }, {
+          0x0AC1, 31, 28, 0
+      }, {
+          0x09C1, 32, 29, 0
+      }, {
+          0x08A1, 33, 30, 0
+      }, {
+          0x0521, 34, 31, 0
+      }, {
+          0x0441, 35, 32, 0
+      }, {
+          0x02A1, 36, 33, 0
+      }, {
+          0x0221, 37, 34, 0
+      }, {
+          0x0141, 38, 35, 0
+      }, {
+          0x0111, 39, 36, 0
+      }, {
+          0x0085, 40, 37, 0
+      }, {
+          0x0049, 41, 38, 0
+      }, {
+          0x0025, 42, 39, 0
+      }, {
+          0x0015, 43, 40, 0
+      }, {
+          0x0009, 44, 41, 0
+      }, {
+          0x0005, 45, 42, 0
+      }, {
+          0x0001, 45, 43, 0
+      }, {
+          0x5601, 46, 46, 0
+      }
+  };
+
+  private int a;
+  private long c;
+  private int ct;
+
+  private int b;
+
+  private long streamPos0;
+
+  private final ImageInputStream iis;
+
+  public ArithmeticDecoder(ImageInputStream iis) throws IOException {
+    this.iis = iis;
+    init();
+  }
+
+  private void init() throws IOException {
+    this.streamPos0 = iis.getStreamPosition();
+    b = this.iis.read();
+
+    c = b << 16;
+
+    byteIn();
+
+    c <<= 7;
+    ct -= 7;
+    a = 0x8000;
+  }
+
+  public int decode(CX cx) throws IOException {
+    int d;
+    final int qeValue = QE[cx.cx()][0];
+    final int icx = cx.cx();
+
+    a -= qeValue;
+
+    if ((c >> 16) < qeValue) {
+      d = lpsExchange(cx, icx, qeValue);
+      renormalize();
+    } else {
+      c -= (qeValue << 16);
+      if ((a & 0x8000) == 0) {
+        d = mpsExchange(cx, icx);
+        renormalize();
+      } else {
+        return cx.mps();
+      }
+    }
+
+    return d;
+  }
+
+  private void byteIn() throws IOException {
+    if (iis.getStreamPosition() > streamPos0) {
+      iis.seek(iis.getStreamPosition() - 1);
+    }
+
+    b = iis.read();
+
+    if (b == 0xFF) {
+      final int b1 = iis.read();
+      if (b1 > 0x8f) {
+        c += 0xff00;
+        ct = 8;
+        iis.seek(iis.getStreamPosition() - 2);
+      } else {
+        c += b1 << 9;
+        ct = 7;
+      }
+    } else {
+      b = iis.read();
+      c += b << 8;
+      ct = 8;
+    }
+
+    c &= 0xffffffffL;
+  }
+
+  private void renormalize() throws IOException {
+    do {
+      if (ct == 0) {
+        byteIn();
+      }
+
+      a <<= 1;
+      c <<= 1;
+      ct--;
+
+    } while ((a & 0x8000) == 0);
+
+    c &= 0xffffffffL;
+  }
+
+  private int mpsExchange(CX cx, int icx) {
+    final int mps = cx.mps();
+
+    if (a < QE[icx][0]) {
+
+      if (QE[icx][3] == 1) {
+        cx.toggleMps();
+      }
+
+      cx.setCx(QE[icx][2]);
+      return 1 - mps;
+    } else {
+      cx.setCx(QE[icx][1]);
+      return mps;
+    }
+  }
+
+  private int lpsExchange(CX cx, int icx, int qeValue) {
+    final int mps = cx.mps();
+
+    if (a < qeValue) {
+      cx.setCx(QE[icx][1]);
+      a = qeValue;
+
+      return mps;
+    } else {
+      if (QE[icx][3] == 1) {
+        cx.toggleMps();
+      }
+
+      cx.setCx(QE[icx][2]);
+      a = qeValue;
+      return 1 - mps;
+    }
+  }
+
+  int getA() {
+    return a;
+  }
+
+  long getC() {
+    return c;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticIntegerDecoder.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticIntegerDecoder.java
new file mode 100644
index 0000000..0f9fa7c
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticIntegerDecoder.java
@@ -0,0 +1,155 @@
+/**
+ * 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.arithmetic;
+
+import java.io.IOException;
+
+/**
+ * This class represents the arithmetic integer decoder, described in ISO/IEC 14492:2001 (Annex A).
+ */
+public class ArithmeticIntegerDecoder {
+
+  private final ArithmeticDecoder decoder;
+
+  private int prev;
+
+  public ArithmeticIntegerDecoder(ArithmeticDecoder decoder) {
+    this.decoder = decoder;
+  }
+
+  /**
+   * Arithmetic Integer Decoding Procedure, Annex A.2.
+   * 
+   * @return Decoded value.
+   * @throws IOException
+   */
+  public long decode(CX cxIAx) throws IOException {
+    int v = 0;
+    int d, s;
+
+    int bitsToRead;
+    int offset;
+
+    if (cxIAx == null) {
+      cxIAx = new CX(512, 1);
+    }
+
+    prev = 1;
+
+    cxIAx.setIndex(prev);
+    s = decoder.decode(cxIAx);
+    setPrev(s);
+
+    cxIAx.setIndex(prev);
+    d = decoder.decode(cxIAx);
+    setPrev(d);
+
+    if (d == 1) {
+      cxIAx.setIndex(prev);
+      d = decoder.decode(cxIAx);
+      setPrev(d);
+
+      if (d == 1) {
+        cxIAx.setIndex(prev);
+        d = decoder.decode(cxIAx);
+        setPrev(d);
+
+        if (d == 1) {
+          cxIAx.setIndex(prev);
+          d = decoder.decode(cxIAx);
+          setPrev(d);
+
+          if (d == 1) {
+            cxIAx.setIndex(prev);
+            d = decoder.decode(cxIAx);
+            setPrev(d);
+
+            if (d == 1) {
+              bitsToRead = 32;
+              offset = 4436;
+            } else {
+              bitsToRead = 12;
+              offset = 340;
+            }
+          } else {
+            bitsToRead = 8;
+            offset = 84;
+          }
+        } else {
+          bitsToRead = 6;
+          offset = 20;
+        }
+      } else {
+        bitsToRead = 4;
+        offset = 4;
+      }
+    } else {
+      bitsToRead = 2;
+      offset = 0;
+    }
+
+    for (int i = 0; i < bitsToRead; i++) {
+      cxIAx.setIndex(prev);
+      d = decoder.decode(cxIAx);
+      setPrev(d);
+      v = (v << 1) | d;
+    }
+
+    v += offset;
+
+    if (s == 0) {
+      return v;
+    } else if (s == 1 && v > 0) {
+      return -v;
+    }
+
+    return Long.MAX_VALUE;
+  }
+
+  private void setPrev(int bit) {
+    if (prev < 256) {
+      prev = ((prev << 1) | bit) & 0x1ff;
+    } else {
+      prev = ((((prev << 1) | bit) & 511) | 256) & 0x1ff;
+    }
+  }
+
+  /**
+   * The IAID decoding procedure, Annex A.3.
+   * 
+   * @param cxIAID - The contexts and statistics for decoding procedure.
+   * @param symCodeLen - Symbol code length.
+   * 
+   * @return The decoded value.
+   * 
+   * @throws IOException
+   */
+  public int decodeIAID(CX cxIAID, long symCodeLen) throws IOException {
+    // A.3 1)
+    prev = 1;
+
+    // A.3 2)
+    for (int i = 0; i < symCodeLen; i++) {
+      cxIAID.setIndex(prev);
+      prev = (prev << 1) | decoder.decode(cxIAID);
+    }
+
+    // A.3 3) & 4)
+    return (prev - (1 << symCodeLen));
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/arithmetic/CX.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/arithmetic/CX.java
new file mode 100644
index 0000000..6d03f58
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/arithmetic/CX.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.decoder.arithmetic;
+
+/**
+ * CX represents the context used by arithmetic decoding and arithmetic integer decoding. It selects
+ * the probability estimate and statistics used during decoding procedure.
+ */
+public final class CX {
+  private int index;
+
+  private final byte cx[];
+  private final byte mps[];
+
+  /**
+   * @param size - Amount of context values.
+   * @param index - Start index.
+   */
+  public CX(int size, int index) {
+    this.index = index;
+    cx = new byte[size];
+    mps = new byte[size];
+  }
+
+  protected int cx() {
+    return cx[index] & 0x7f;
+  }
+
+  protected void setCx(int value) {
+    cx[index] = (byte) (value & 0x7f);
+  }
+
+  /**
+   * @return The decision. Possible values are {@code 0} or {@code 1}.
+   */
+  protected byte mps() {
+    return mps[index];
+  }
+
+  /**
+   * Flips the bit in actual "more predictable symbol" array element.
+   */
+  protected void toggleMps() {
+    mps[index] ^= 1;
+  }
+
+  protected int getIndex() {
+    return index;
+  }
+
+  public void setIndex(int index) {
+    this.index = index;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/EncodedTable.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/EncodedTable.java
new file mode 100644
index 0000000..77e71c6
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/EncodedTable.java
@@ -0,0 +1,91 @@
+/**
+ * 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.huffman;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.segments.Table;
+
+/**
+ * This class represents a encoded huffman table.
+ */
+public class EncodedTable extends HuffmanTable {
+
+  private Table table;
+
+  public EncodedTable(Table table) throws IOException {
+    this.table = table;
+    parseTable();
+  }
+
+  public void parseTable() throws IOException {
+
+    SubInputStream sis = table.getSubInputStream();
+
+    List<Code> codeTable = new ArrayList<Code>();
+
+    int prefLen, rangeLen, rangeLow;
+    int curRangeLow = table.getHtLow();
+
+    /* Annex B.2 5) - decode table lines */
+    while (curRangeLow < table.getHtHigh()) {
+      prefLen = (int) sis.readBits(table.getHtPS());
+      rangeLen = (int) sis.readBits(table.getHtRS());
+      rangeLow = curRangeLow;
+
+      codeTable.add(new Code(prefLen, rangeLen, rangeLow, false));
+
+      curRangeLow += 1 << rangeLen;
+    }
+
+    /* Annex B.2 6) */
+    prefLen = (int) sis.readBits(table.getHtPS());
+
+    /*
+     * Annex B.2 7) - lower range table line
+     * 
+     * Made some correction. Spec specifies an incorrect variable -> Replaced highPrefLen with
+     * lowPrefLen
+     */
+    rangeLen = 32;
+    rangeLow = table.getHtHigh() - 1;
+    codeTable.add(new Code(prefLen, rangeLen, rangeLow, true));
+    // }
+
+    /* Annex B.2 8) */
+    prefLen = (int) sis.readBits(table.getHtPS());
+
+    /* Annex B.2 9) - upper range table line */
+    rangeLen = 32;
+    rangeLow = table.getHtHigh();
+    codeTable.add(new Code(prefLen, rangeLen, rangeLow, false));
+
+    /* Annex B.2 10) - out-of-band table line */
+    if (table.getHtOOB() == 1) {
+      prefLen = (int) sis.readBits(table.getHtPS());
+      codeTable.add(new Code(prefLen, -1, -1, false));
+    }
+
+    System.out.println(codeTableToString(codeTable));
+
+    initTree(codeTable);
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/FixedSizeTable.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/FixedSizeTable.java
new file mode 100644
index 0000000..b6f8822
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/FixedSizeTable.java
@@ -0,0 +1,29 @@
+/**
+ * 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.huffman;
+
+import java.util.List;
+
+/**
+ * This class represents a fixed size huffman table.
+ */
+public class FixedSizeTable extends HuffmanTable {
+  public FixedSizeTable(List<Code> runCodeTable) {
+    initTree(runCodeTable);
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/HuffmanTable.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/HuffmanTable.java
new file mode 100644
index 0000000..1e0f661
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/HuffmanTable.java
@@ -0,0 +1,117 @@
+/**
+ * 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.huffman;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.JBIG2ImageReader;
+
+/**
+ * This abstract class is the base class for all types of huffman tables.
+ */
+public abstract class HuffmanTable {
+
+  /**
+   * This static class represents a code for use in huffman tables.
+   */
+  public static class Code {
+    final int prefixLength;
+    final int rangeLength;
+    final int rangeLow;
+    final boolean isLowerRange;
+    int code = -1;
+
+    public Code(int prefixLength, int rangeLength, int rangeLow, boolean isLowerRange) {
+      this.prefixLength = prefixLength;
+      this.rangeLength = rangeLength;
+      this.rangeLow = rangeLow;
+      this.isLowerRange = isLowerRange;
+    }
+
+    @Override
+    public String toString() {
+      return (code != -1 ? ValueNode.bitPattern(code, prefixLength) : "?") + "/" + prefixLength + "/" + rangeLength
+          + "/" + rangeLow;
+    }
+  }
+
+  private InternalNode rootNode = new InternalNode();
+
+  public void initTree(List<Code> codeTable) {
+    preprocessCodes(codeTable);
+
+    for (Code c : codeTable) {
+      rootNode.append(c);
+    }
+  }
+
+  public long decode(ImageInputStream iis) throws IOException {
+    return rootNode.decode(iis);
+  }
+
+  @Override
+  public String toString() {
+    return rootNode + "\n";
+  }
+
+  public static String codeTableToString(List<Code> codeTable) {
+    StringBuilder sb = new StringBuilder();
+
+    for (Code c : codeTable) {
+      sb.append(c.toString()).append("\n");
+    }
+
+    return sb.toString();
+  }
+
+  private void preprocessCodes(List<Code> codeTable) {
+    /* Annex B.3 1) - build the histogram */
+    int maxPrefixLength = 0;
+
+    for (Code c : codeTable) {
+      maxPrefixLength = Math.max(maxPrefixLength, c.prefixLength);
+    }
+
+    int lenCount[] = new int[maxPrefixLength + 1];
+    for (Code c : codeTable) {
+      lenCount[c.prefixLength]++;
+    }
+
+    int curCode;
+    int firstCode[] = new int[lenCount.length + 1];
+    lenCount[0] = 0;
+
+    /* Annex B.3 3) */
+    for (int curLen = 1; curLen <= lenCount.length; curLen++) {
+      firstCode[curLen] = (firstCode[curLen - 1] + (lenCount[curLen - 1]) << 1);
+      curCode = firstCode[curLen];
+      for (Code code : codeTable) {
+        if (code.prefixLength == curLen) {
+          code.code = curCode;
+          curCode++;
+        }
+      }
+    }
+
+    if (JBIG2ImageReader.DEBUG)
+      System.out.println(codeTableToString(codeTable));
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/InternalNode.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/InternalNode.java
new file mode 100644
index 0000000..ea4a4dd
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/InternalNode.java
@@ -0,0 +1,119 @@
+/**
+ * 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.huffman;
+
+import java.io.IOException;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.JBIG2ImageReader;
+import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable.Code;
+
+/**
+ * This class represents an internal node of a huffman tree. It contains two child nodes.
+ */
+class InternalNode extends Node {
+  private final int depth;
+
+  private Node zero;
+  private Node one;
+
+  protected InternalNode() {
+    depth = 0;
+  }
+
+  protected InternalNode(int depth) {
+    this.depth = depth;
+  }
+
+  protected void append(Code c) {
+    if (JBIG2ImageReader.DEBUG)
+      System.out.println("I'm working on " + c.toString());
+
+    // ignore unused codes
+    if (c.prefixLength == 0)
+      return;
+
+    int shift = c.prefixLength - 1 - depth;
+
+    if (shift < 0)
+      throw new IllegalArgumentException("Negative shifting is not possible.");
+
+    int bit = (c.code >> shift) & 1;
+    if (shift == 0) {
+      if (c.rangeLength == -1) {
+        // the child will be a OutOfBand
+        if (bit == 1) {
+          if (one != null)
+            throw new IllegalStateException("already have a OOB for " + c);
+          one = new OutOfBandNode(c);
+        } else {
+          if (zero != null)
+            throw new IllegalStateException("already have a OOB for " + c);
+          zero = new OutOfBandNode(c);
+        }
+      } else {
+        // the child will be a ValueNode
+        if (bit == 1) {
+          if (one != null)
+            throw new IllegalStateException("already have a ValueNode for " + c);
+          one = new ValueNode(c);
+        } else {
+          if (zero != null)
+            throw new IllegalStateException("already have a ValueNode for " + c);
+          zero = new ValueNode(c);
+        }
+      }
+    } else {
+      // the child will be an InternalNode
+      if (bit == 1) {
+        if (one == null)
+          one = new InternalNode(depth + 1);
+        ((InternalNode) one).append(c);
+      } else {
+        if (zero == null)
+          zero = new InternalNode(depth + 1);
+        ((InternalNode) zero).append(c);
+      }
+    }
+  }
+
+  @Override
+  protected long decode(ImageInputStream iis) throws IOException {
+    int b = iis.readBit();
+    Node n = b == 0 ? zero : one;
+    return n.decode(iis);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("\n");
+
+    pad(sb);
+    sb.append("0: ").append(zero).append("\n");
+    pad(sb);
+    sb.append("1: ").append(one).append("\n");
+
+    return sb.toString();
+  }
+
+  private void pad(StringBuilder sb) {
+    for (int i = 0; i < depth; i++)
+      sb.append("   ");
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/Node.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/Node.java
new file mode 100644
index 0000000..1adbf8c
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/Node.java
@@ -0,0 +1,29 @@
+/**
+ * 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.huffman;
+
+import java.io.IOException;
+
+import javax.imageio.stream.ImageInputStream;
+
+/**
+ * Base class for all nodes in a huffman tree.
+ */
+abstract class Node {
+  protected abstract long decode(ImageInputStream iis) throws IOException;
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/OutOfBandNode.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/OutOfBandNode.java
new file mode 100644
index 0000000..c379c89
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/OutOfBandNode.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.decoder.huffman;
+
+import java.io.IOException;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable.Code;
+
+
+/**
+ * Represents a out of band node in a huffman tree.
+ */
+class OutOfBandNode extends Node {
+
+  protected OutOfBandNode(Code c) {
+  }
+
+  @Override
+  protected long decode(ImageInputStream iis) throws IOException {
+    return Long.MAX_VALUE;
+  }
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/StandardTables.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/StandardTables.java
new file mode 100644
index 0000000..70452b8
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/StandardTables.java
@@ -0,0 +1,277 @@
+/**
+ * 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.huffman;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.jbig2.*;
+
+public class StandardTables {
+	static class StandardTable extends HuffmanTable {
+		private StandardTable(int table[][]) {
+
+			List<Code> codeTable = new ArrayList<Code>();
+
+			for (int i = 0; i < table.length; i++) {
+				int prefixLength = table[i][0];
+				int rangeLength = table[i][1];
+				int rangeLow = table[i][2];
+				boolean isLowerRange = false;
+				if (table[i].length > 3)
+					isLowerRange = true;
+				codeTable.add(new Code(prefixLength, rangeLength, rangeLow,
+						isLowerRange));
+			}
+
+			if (JBIG2ImageReader.DEBUG)
+				System.out.println(HuffmanTable.codeTableToString(codeTable));
+
+			initTree(codeTable);
+		}
+	}
+
+	// Fourth Value (999) is used for the LowerRange-line
+	private static final int TABLES[][][] = {
+	// B1
+			{   { 1, 4, 0 }, //
+					{ 2, 8, 16 }, //
+					{ 3, 16, 272 }, //
+					{ 3, 32, 65808 } /* high */
+			},
+			// B2
+			{   { 1, 0, 0 }, //
+					{ 2, 0, 1 }, //
+					{ 3, 0, 2 }, //
+					{ 4, 3, 3 }, //
+					{ 5, 6, 11 }, //
+					{ 6, 32, 75 }, /* high */
+					{ 6, -1, 0 } /* OOB */
+			},
+			// B3
+			{   { 8, 8, -256 }, //
+					{ 1, 0, 0 }, //
+					{ 2, 0, 1 }, //
+					{ 3, 0, 2 }, //
+					{ 4, 3, 3 }, //
+					{ 5, 6, 11 }, //
+					{ 8, 32, -257, 999 }, /* low */
+					{ 7, 32, 75 }, /* high */
+					{ 6, -1, 0 } /* OOB */
+			},
+			// B4
+			{   { 1, 0, 1 }, //
+					{ 2, 0, 2 }, //
+					{ 3, 0, 3 }, //
+					{ 4, 3, 4 }, //
+					{ 5, 6, 12 }, //
+					{ 5, 32, 76 } /* high */
+			},
+			// B5
+			{   { 7, 8, -255 }, //
+					{ 1, 0, 1 }, //
+					{ 2, 0, 2 }, //
+					{ 3, 0, 3 }, //
+					{ 4, 3, 4 }, //
+					{ 5, 6, 12 }, //
+					{ 7, 32, -256, 999 }, /* low */
+					{ 6, 32, 76 } /* high */
+			},
+			// B6
+			{   { 5, 10, -2048 }, //
+					{ 4, 9, -1024 }, //
+					{ 4, 8, -512 }, //
+					{ 4, 7, -256 }, //
+					{ 5, 6, -128 }, //
+					{ 5, 5, -64 }, //
+					{ 4, 5, -32 }, //
+					{ 2, 7, 0 }, //
+					{ 3, 7, 128 }, //
+					{ 3, 8, 256 }, //
+					{ 4, 9, 512 }, //
+					{ 4, 10, 1024 }, //
+					{ 6, 32, -2049, 999 }, /* low */
+					{ 6, 32, 2048 } /* high */
+			},
+			// B7
+			{   { 4, 9, -1024 }, //
+					{ 3, 8, -512 }, //
+					{ 4, 7, -256 }, //
+					{ 5, 6, -128 }, //
+					{ 5, 5, -64 }, //
+					{ 4, 5, -32 }, //
+					{ 4, 5, 0 }, //
+					{ 5, 5, 32 }, //
+					{ 5, 6, 64 }, //
+					{ 4, 7, 128 }, //
+					{ 3, 8, 256 }, //
+					{ 3, 9, 512 }, //
+					{ 3, 10, 1024 }, //
+					{ 5, 32, -1025, 999 }, /* low */
+					{ 5, 32, 2048 } /* high */
+			},
+			// B8
+			{   { 8, 3, -15 }, //
+					{ 9, 1, -7 }, //
+					{ 8, 1, -5 }, //
+					{ 9, 0, -3 }, //
+					{ 7, 0, -2 }, //
+					{ 4, 0, -1 }, //
+					{ 2, 1, 0 }, //
+					{ 5, 0, 2 }, //
+					{ 6, 0, 3 }, //
+					{ 3, 4, 4 }, //
+					{ 6, 1, 20 }, //
+					{ 4, 4, 22 }, //
+					{ 4, 5, 38 }, //
+					{ 5, 6, 70 }, //
+					{ 5, 7, 134 }, //
+					{ 6, 7, 262 }, //
+					{ 7, 8, 390 }, //
+					{ 6, 10, 646 }, //
+					{ 9, 32, -16, 999 }, /* low */
+					{ 9, 32, 1670 }, /* high */
+					{ 2, -1, 0 } /* OOB */
+			},
+			// B9
+			{   { 8, 4, -31 }, //
+					{ 9, 2, -15 }, //
+					{ 8, 2, -11 }, //
+					{ 9, 1, -7 }, //
+					{ 7, 1, -5 }, //
+					{ 4, 1, -3 }, //
+					{ 3, 1, -1 }, //
+					{ 3, 1, 1 }, //
+					{ 5, 1, 3 }, //
+					{ 6, 1, 5 }, //
+					{ 3, 5, 7 }, //
+					{ 6, 2, 39 }, //
+					{ 4, 5, 43 }, //
+					{ 4, 6, 75 }, //
+					{ 5, 7, 139 }, //
+					{ 5, 8, 267 }, //
+					{ 6, 8, 523 }, //
+					{ 7, 9, 779 }, //
+					{ 6, 11, 1291 }, //
+					{ 9, 32, -32, 999 }, /* low */
+					{ 9, 32, 3339 }, /* high */
+					{ 2, -1, 0 } /* OOB */
+			},
+			// B10
+			{   { 7, 4, -21 }, //
+					{ 8, 0, -5 }, //
+					{ 7, 0, -4 }, //
+					{ 5, 0, -3 }, //
+					{ 2, 2, -2 }, //
+					{ 5, 0, 2 }, //
+					{ 6, 0, 3 }, //
+					{ 7, 0, 4 }, //
+					{ 8, 0, 5 }, //
+					{ 2, 6, 6 }, //
+					{ 5, 5, 70 }, //
+					{ 6, 5, 102 }, //
+					{ 6, 6, 134 }, //
+					{ 6, 7, 198 }, //
+					{ 6, 8, 326 }, //
+					{ 6, 9, 582 }, //
+					{ 6, 10, 1094 }, //
+					{ 7, 11, 2118 }, //
+					{ 8, 32, -22, 999 }, /* low */
+					{ 8, 32, 4166 }, /* high */
+					{ 2, -1, 0 } /* OOB */
+			},
+			// B11
+			{   { 1, 0, 1 }, //
+					{ 2, 1, 2 }, //
+					{ 4, 0, 4 }, //
+					{ 4, 1, 5 }, //
+					{ 5, 1, 7 }, //
+					{ 5, 2, 9 }, //
+					{ 6, 2, 13 }, //
+					{ 7, 2, 17 }, //
+					{ 7, 3, 21 }, //
+					{ 7, 4, 29 }, //
+					{ 7, 5, 45 }, //
+					{ 7, 6, 77 }, //
+					{ 7, 32, 141 } /* high */
+			},
+			// B12
+			{   { 1, 0, 1 }, //
+					{ 2, 0, 2 }, //
+					{ 3, 1, 3 }, //
+					{ 5, 0, 5 }, //
+					{ 5, 1, 6 }, //
+					{ 6, 1, 8 }, //
+					{ 7, 0, 10 }, //
+					{ 7, 1, 11 }, //
+					{ 7, 2, 13 }, //
+					{ 7, 3, 17 }, //
+					{ 7, 4, 25 }, //
+					{ 8, 5, 41 }, //
+					{ 8, 32, 73 } //
+			},
+			// B13
+			{   { 1, 0, 1 }, //
+					{ 3, 0, 2 }, //
+					{ 4, 0, 3 }, //
+					{ 5, 0, 4 }, //
+					{ 4, 1, 5 }, //
+					{ 3, 3, 7 }, //
+					{ 6, 1, 15 }, //
+					{ 6, 2, 17 }, //
+					{ 6, 3, 21 }, //
+					{ 6, 4, 29 }, //
+					{ 6, 5, 45 }, //
+					{ 7, 6, 77 }, //
+					{ 7, 32, 141 } /* high */
+			},
+			// B14
+			{   { 3, 0, -2 }, //
+					{ 3, 0, -1 }, //
+					{ 1, 0, 0 }, //
+					{ 3, 0, 1 }, //
+					{ 3, 0, 2 } //
+			},
+			// B15
+			{   { 7, 4, -24 }, //
+					{ 6, 2, -8 }, //
+					{ 5, 1, -4 }, //
+					{ 4, 0, -2 }, //
+					{ 3, 0, -1 }, //
+					{ 1, 0, 0 }, //
+					{ 3, 0, 1 }, //
+					{ 4, 0, 2 }, //
+					{ 5, 1, 3 }, //
+					{ 6, 2, 5 }, //
+					{ 7, 4, 9 }, //
+					{ 7, 32, -25, 999 }, /* low */
+					{ 7, 32, 25 } /* high */
+			} };
+
+	private static HuffmanTable STANDARD_TABLES[] = new HuffmanTable[TABLES.length];
+
+	public static HuffmanTable getTable(int number) {
+		HuffmanTable table = STANDARD_TABLES[number - 1];
+		if (table == null) {
+			table = new StandardTable(TABLES[number - 1]);
+			STANDARD_TABLES[number - 1] = table;
+		}
+
+		return table;
+	}
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/ValueNode.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/ValueNode.java
new file mode 100644
index 0000000..7543f7e
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/huffman/ValueNode.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.decoder.huffman;
+
+import java.io.IOException;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable.Code;
+
+/**
+ * Represents a value node in a huffman tree. It is a leaf of a tree.
+ */
+class ValueNode extends Node {
+  private int rangeLen;
+  private int rangeLow;
+  private boolean isLowerRange;
+
+  protected ValueNode(Code c) {
+    rangeLen = c.rangeLength;
+    rangeLow = c.rangeLow;
+    isLowerRange = c.isLowerRange;
+  }
+
+  @Override
+  protected long decode(ImageInputStream iis) throws IOException {
+
+    if (isLowerRange) {
+      /* B.4 4) */
+      return (rangeLow - iis.readBits(rangeLen));
+    } else {
+      /* B.4 5) */
+      return rangeLow + iis.readBits(rangeLen);
+    }
+  }
+
+  static String bitPattern(int v, int len) {
+    char result[] = new char[len];
+    for (int i = 1; i <= len; i++)
+      result[i - 1] = (v >> (len - i) & 1) != 0 ? '1' : '0';
+
+    return new String(result);
+  }
+}
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;
+}
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
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);
+  }
+
+}
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);
+  }
+
+}
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);
+  }
+
+}
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
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));
+        }
+      }
+    }
+  }
+
+
+}
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;
+  }
+}
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
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
diff --git a/src/main/java/org/apache/pdfbox/jbig2/image/Resizer.java b/src/main/java/org/apache/pdfbox/jbig2/image/Resizer.java
new file mode 100644
index 0000000..3afc75f
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/image/Resizer.java
@@ -0,0 +1,404 @@
+/**
+ * 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.Rectangle;
+import java.awt.image.WritableRaster;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.util.Utils;
+
+class Resizer {
+
+  static final class Mapping {
+    /** x and y scales */
+    final double scale;
+
+    /** x and y offset used by MAP, private fields */
+    final double offset = .5;
+
+    private final double a0;
+    private final double b0;
+
+    Mapping(double a0, double aw, double b0, double bw) {
+      this.a0 = a0;
+      this.b0 = b0;
+      scale = bw / aw;
+
+      if (scale <= 0.)
+        throw new IllegalArgumentException("Negative scales are not allowed");
+    }
+
+    Mapping(double scaleX) {
+      scale = scaleX;
+      a0 = b0 = 0;
+    }
+
+    double mapPixelCenter(final int b) {
+      return (b + offset - b0) / scale + a0;
+    }
+
+    double dstToSrc(final double b) {
+      return (b - b0) / scale + a0;
+    }
+
+    double srcToDst(final double a) {
+      return (a - a0) * scale + b0;
+    }
+  }
+
+  /**
+   * Order in which to apply filter
+   */
+  private enum Order {
+    AUTO, XY, YX
+  }
+
+  /** Error tolerance */
+  private static final double EPSILON = 1e-7;
+
+  /** Number of bits in filter coefficients */
+  private int weightBits = 14;
+
+  private int weightOne = 1 << weightBits;
+
+  /** Number of bits per channel */
+  private int bitsPerChannel[] = new int[]{
+      8, 8, 8
+  };
+
+  private static final int NO_SHIFT[] = new int[16];
+
+  private int finalShift[] = new int[]{
+      2 * weightBits - bitsPerChannel[0], 2 * weightBits - bitsPerChannel[1], 2 * weightBits - bitsPerChannel[2]
+  };
+
+  /**
+   * Is x an integer?
+   * 
+   * @param x the double to check
+   * @return <code>true</code> if x is an integer, <code>false</code> if not.
+   */
+  private static boolean isInteger(final double x) {
+    return Math.abs(x - Math.floor(x + .5)) < EPSILON;
+  }
+
+  static final boolean debug = false;
+
+  /**
+   * Should filters be simplified if possible?
+   */
+  private final boolean coerce = true;
+
+  /**
+   * The order in which data is processed.
+   * 
+   * @see Order
+   */
+  private final Order order = Order.AUTO;
+
+  /**
+   * Should zeros be trimmed in x filter weight tables?
+   */
+  private final boolean trimZeros = true;
+
+  private final Mapping mappingX;
+  private final Mapping mappingY;
+
+  /**
+   * Creates an instance of {@link Resizer} with one scale factor for both x and y directions.
+   * 
+   * @param scale the scale factor for x and y direction
+   */
+  public Resizer(double scale) {
+    this(scale, scale);
+  }
+
+  /**
+   * Creates an instance of {@link Resizer} with a scale factor for each direction.
+   * 
+   * @param scaleX the scale factor for x direction
+   * @param scaleY the scale factor for y direction
+   */
+  public Resizer(double scaleX, double scaleY) {
+    mappingX = new Mapping(scaleX);
+    mappingY = new Mapping(scaleY);
+  }
+
+  private Weighttab[] createXWeights(Rectangle srcBounds, final Rectangle dstBounds, final ParameterizedFilter filter) {
+    final int srcX0 = srcBounds.x;
+    final int srcX1 = srcBounds.x + srcBounds.width;
+
+    final int dstX0 = dstBounds.x;
+    final int dstX1 = dstBounds.x + dstBounds.width;
+
+    final Weighttab tabs[] = new Weighttab[dstBounds.width];
+    for (int dstX = dstX0; dstX < dstX1; dstX++) {
+      final double center = mappingX.mapPixelCenter(dstX);
+      tabs[dstX - dstX0] = new Weighttab(filter, weightOne, center, srcX0, srcX1 - 1, trimZeros);
+    }
+
+    return tabs;
+  }
+
+  /**
+   * Checks if our discrete sampling of an arbitrary continuous filter, parameterized by the filter
+   * spacing ({@link ParameterizedFilter#scale}), its radius ({@link ParameterizedFilter#support}),
+   * and the scale and offset of the coordinate mapping, causes the filter to reduce to point
+   * sampling.
+   * <p>
+   * It reduces if support is less than 1 pixel or if integer scale and translation, and filter is
+   * cardinal.
+   * 
+   * @param filter the parameterized filter instance to be simplified
+   * @param scale the scale of the coordinate mapping
+   * @param offset the offset of the coordinate mapping
+   */
+  private ParameterizedFilter simplifyFilter(final ParameterizedFilter filter, final double scale, final double offset) {
+    if (coerce
+        && (filter.support <= .5 || filter.filter.cardinal && isInteger(1. / filter.scale)
+            && isInteger(1. / (scale * filter.scale)) && isInteger((offset / scale - .5) / filter.scale)))
+      return new ParameterizedFilter(new Filter.Point(), 1., .5, 1);
+
+    return filter;
+  }
+
+  /**
+   * Filtered zoom, x direction filtering before y direction filtering
+   * <p>
+   * Note: when calling {@link Resizer#createXWeights(Rectangle, Rectangle, ParameterizedFilter)},
+   * we can trim leading and trailing zeros from the x weight buffers as an optimization, but not
+   * for y weight buffers since the split formula is anticipating a constant amount of buffering of
+   * source scanlines; trimming zeros in y weight could cause feedback.
+   */
+  private void resizeXfirst(final Object src, final Rectangle srcBounds, final Object dst, final Rectangle dstBounds,
+      final ParameterizedFilter xFilter, final ParameterizedFilter yFilter) {
+    // source scanline buffer
+    final Scanline buffer = createScanline(src, dst, srcBounds.width);
+
+    // accumulator buffer
+    final Scanline accumulator = createScanline(src, dst, dstBounds.width);
+
+    // a sampled filter for source pixels for each dest x position
+    final Weighttab xWeights[] = createXWeights(srcBounds, dstBounds, xFilter);
+
+    // Circular buffer of active lines
+    final int yBufferSize = yFilter.width + 2;
+    final Scanline lineBuffer[] = new Scanline[yBufferSize];
+    for (int y = 0; y < yBufferSize; y++) {
+      lineBuffer[y] = createScanline(src, dst, dstBounds.width);
+      lineBuffer[y].y = -1; /* mark scanline as unread */
+    }
+
+    // range of source and destination scanlines in regions
+    final int srcY0 = srcBounds.y;
+    final int srcY1 = srcBounds.y + srcBounds.height;
+    final int dstY0 = dstBounds.y;
+    final int dstY1 = dstBounds.y + dstBounds.height;
+
+    int yFetched = -1; // used to assert no backtracking
+
+    // loop over dest scanlines
+    for (int dstY = dstY0; dstY < dstY1; dstY++) {
+      // a sampled filter for source pixels for each dest x position
+      final Weighttab yWeight = new Weighttab(yFilter, weightOne, mappingY.mapPixelCenter(dstY), srcY0, srcY1 - 1, true);
+
+      accumulator.clear();
+
+      // loop over source scanlines that contribute to this dest scanline
+      for (int srcY = yWeight.i0; srcY <= yWeight.i1; srcY++) {
+        final Scanline srcBuffer = lineBuffer[srcY % yBufferSize];
+
+        if (debug)
+          System.out.println("  abuf.y / ayf " + srcBuffer.y + " / " + srcY);
+
+        if (srcBuffer.y != srcY) {
+          // scanline needs to be fetched from src raster
+          srcBuffer.y = srcY;
+
+          if (srcY0 + srcY <= yFetched)
+            throw new AssertionError("Backtracking from line " + yFetched + " to " + (srcY0 + srcY));
+
+          buffer.fetch(srcBounds.x, srcY0 + srcY);
+
+          yFetched = srcY0 + srcY;
+
+          // filter it into the appropriate line of linebuf (xfilt)
+          buffer.filter(NO_SHIFT, bitsPerChannel, xWeights, srcBuffer);
+        }
+
+        // add weighted tbuf into accum (these do yfilt)
+        srcBuffer.accumulate(yWeight.weights[srcY - yWeight.i0], accumulator);
+      }
+
+      accumulator.shift(finalShift);
+      accumulator.store(dstBounds.x, dstY);
+      if (debug)
+        System.out.printf("\n");
+    }
+  }
+
+  /**
+   * Filtered zoom, y direction filtering before x direction filtering
+   * */
+  private void resizeYfirst(final Object src, final Rectangle srcBounds, final Object dst, final Rectangle dstBounds,
+      final ParameterizedFilter xFilter, final ParameterizedFilter yFilter) {
+    // destination scanline buffer
+    final Scanline buffer = createScanline(src, dst, dstBounds.width);
+
+    // accumulator buffer
+    final Scanline accumulator = createScanline(src, dst, srcBounds.width);
+
+    // a sampled filter for source pixels for each destination x position
+    final Weighttab xWeights[] = createXWeights(srcBounds, dstBounds, xFilter);
+
+    // Circular buffer of active lines
+    final int yBufferSize = yFilter.width + 2;
+    final Scanline lineBuffer[] = new Scanline[yBufferSize];
+    for (int y = 0; y < yBufferSize; y++) {
+      lineBuffer[y] = createScanline(src, dst, srcBounds.width);
+
+      // mark scanline as unread
+      lineBuffer[y].y = -1;
+    }
+
+    // range of source and destination scanlines in regions
+    final int srcY0 = srcBounds.y;
+    final int srcY1 = srcBounds.y + srcBounds.height;
+    final int dstY0 = dstBounds.y;
+    final int dstY1 = dstBounds.y + dstBounds.height;
+
+    // used to assert no backtracking
+    int yFetched = -1;
+
+    // loop over destination scanlines
+    for (int dstY = dstY0; dstY < dstY1; dstY++) {
+      // prepare a weighttab for destination y position by a single sampled filter for current y
+      // position
+      final Weighttab yWeight = new Weighttab(yFilter, weightOne, mappingY.mapPixelCenter(dstY), srcY0, srcY1 - 1, true);
+
+      accumulator.clear();
+
+      // loop over source scanlines that contribute to this destination scanline
+      for (int srcY = yWeight.i0; srcY <= yWeight.i1; srcY++) {
+        final Scanline srcBuffer = lineBuffer[srcY % yBufferSize];
+        if (srcBuffer.y != srcY) {
+          // scanline needs to be fetched from source raster
+          srcBuffer.y = srcY;
+
+          if (srcY0 + srcY <= yFetched)
+            throw new AssertionError("Backtracking from line " + yFetched + " to " + (srcY0 + srcY));
+
+          srcBuffer.fetch(srcBounds.x, srcY0 + srcY);
+
+          yFetched = srcY0 + srcY;
+        }
+
+        if (debug)
+          System.out.println(dstY + "[] += " + srcY + "[] * " + yWeight.weights[srcY - yWeight.i0]);
+
+        // add weighted source buffer into accumulator (these do y filter)
+        srcBuffer.accumulate(yWeight.weights[srcY - yWeight.i0], accumulator);
+      }
+
+      // and filter it into the appropriate line of line buffer (x filter)
+      accumulator.filter(bitsPerChannel, finalShift, xWeights, buffer);
+
+      // store destination scanline into destination raster
+      buffer.store(dstBounds.x, dstY);
+      if (debug)
+        System.out.printf("\n");
+    }
+  }
+
+  /**
+   * @param src Source object
+   * @param srcBounds Bounds of the source object
+   * @param dst Destination object
+   * @param dstBounds Bounds of the destination object
+   * @param xFilter The filter used for x direction filtering
+   * @param yFilter The filter used for y direction filtering
+   */
+  public void resize(final Object src, final Rectangle srcBounds, final Object dst, Rectangle dstBounds,
+      Filter xFilter, Filter yFilter) {
+    /*
+     * find scale of filter in a space (source space) when minifying, source scale=1/scale, but when
+     * magnifying, source scale=1
+     */
+    ParameterizedFilter xFilterParameterized = new ParameterizedFilter(xFilter, mappingX.scale);
+    ParameterizedFilter yFilterParameterized = new ParameterizedFilter(yFilter, mappingY.scale);
+
+    /* find valid destination window (transformed source + support margin) */
+    final Rectangle dstRegion = new Rectangle();
+    final int x1 = Utils.ceil(mappingX.srcToDst(srcBounds.x - xFilterParameterized.support) + EPSILON);
+    final int y1 = Utils.ceil(mappingY.srcToDst(srcBounds.y - yFilterParameterized.support) + EPSILON);
+    final int x2 = Utils.floor(mappingX.srcToDst(srcBounds.x + srcBounds.width + xFilterParameterized.support)
+        - EPSILON);
+    final int y2 = Utils.floor(mappingY.srcToDst(srcBounds.y + srcBounds.height + yFilterParameterized.support)
+        - EPSILON);
+    dstRegion.setFrameFromDiagonal(x1, y1, x2, y2);
+
+    if (dstBounds.x < dstRegion.x || dstBounds.getMaxX() > dstRegion.getMaxX() || dstBounds.y < dstRegion.y
+        || dstBounds.getMaxY() > dstRegion.getMaxY()) {
+      /* requested destination window lies outside the valid destination, so clip destination */
+      dstBounds = dstBounds.intersection(dstRegion);
+    }
+
+    if (srcBounds.isEmpty() || dstBounds.width <= 0 || dstBounds.height <= 0) {
+      return;
+    }
+
+    /* check for high-level simplifications of filter */
+    xFilterParameterized = simplifyFilter(xFilterParameterized, mappingX.scale, mappingX.offset);
+    yFilterParameterized = simplifyFilter(yFilterParameterized, mappingY.scale, mappingY.offset);
+
+    /*
+     * decide which filtering order (x->y or y->x) is faster for this mapping by counting
+     * convolution multiplies
+     */
+    final boolean orderXY = order != Order.AUTO
+        ? order == Order.XY
+        : dstBounds.width
+            * (srcBounds.height * xFilterParameterized.width + dstBounds.height * yFilterParameterized.width) < dstBounds.height
+            * (dstBounds.width * xFilterParameterized.width + srcBounds.width * yFilterParameterized.width);
+
+    // choose most efficient filtering order
+    if (orderXY) {
+      resizeXfirst(src, srcBounds, dst, dstBounds, xFilterParameterized, yFilterParameterized);
+    } else {
+      resizeYfirst(src, srcBounds, dst, dstBounds, xFilterParameterized, yFilterParameterized);
+    }
+  }
+
+  private static Scanline createScanline(final Object src, Object dst, final int length) {
+    if (src == null)
+      throw new IllegalArgumentException("src must not be null");
+
+    if (!(src instanceof Bitmap))
+      throw new IllegalArgumentException("src must be from type " + Bitmap.class.getName());
+
+    if (dst == null)
+      throw new IllegalArgumentException("dst must not be null");
+
+    if (!(dst instanceof WritableRaster))
+      throw new IllegalArgumentException("dst must be from type " + WritableRaster.class.getName());
+
+    return new BitmapScanline((Bitmap) src, (WritableRaster) dst, length);
+  }
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/image/Scanline.java b/src/main/java/org/apache/pdfbox/jbig2/image/Scanline.java
new file mode 100644
index 0000000..19f3565
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/image/Scanline.java
@@ -0,0 +1,558 @@
+/**
+ * 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.MultiPixelPackedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.SinglePixelPackedSampleModel;
+import java.awt.image.WritableRaster;
+
+
+abstract class Scanline {
+  public interface ScanlineFilter {
+    public void filter(int x, int y, int componentIndex, Object data, int length);
+  }
+
+
+  /**
+   * A Scanline for pixel interleaved byte data with three components. Although its name contains
+   * "BGR" it doesn't really matter how the components are organized, als long as there are three of
+   * them.
+   */
+  protected static final class ByteBGRScanline extends Scanline {
+    private final Raster srcRaster;
+    private final WritableRaster dstRaster;
+
+    private final int data[];
+
+    protected ByteBGRScanline(Raster src, WritableRaster dst, final int length) {
+      super(length);
+      srcRaster = src;
+      dstRaster = dst;
+
+      data = new int[3 * length];
+    }
+
+    @Override
+    protected void accumulate(final int weight, final Scanline dst) {
+      final ByteBGRScanline bcs = (ByteBGRScanline) dst;
+
+      final int abuf[] = data;
+      final int bbuf[] = bcs.data;
+
+      for (int b = 0; b < bbuf.length; b++)
+        bbuf[b] += weight * abuf[b];
+    }
+
+    @Override
+    protected void clear() {
+      final int[] b = data;
+      for (int j = 0; j < b.length; j++)
+        b[j] = 0;
+    }
+
+    @Override
+    protected void fetch(final int x, final int y) {
+      srcRaster.getPixels(x, y, length, 1, data);
+    }
+
+    @Override
+    protected void filter(final int[] preShift, final int[] postShift, final Weighttab[] tabs, final Scanline dst) {
+      final ByteBGRScanline bcs = (ByteBGRScanline) dst;
+      final int nx = dst.length;
+
+      // start sum at 1<<shift-1 for rounding
+      final int start[] = new int[]{
+          1 << postShift[0] - 1, 1 << postShift[1] - 1, 1 << postShift[2] - 1
+      };
+      final int abuf[] = data;
+      final int bbuf[] = bcs.data;
+
+      // the next two blocks are duplicated except for the missing shift
+      // operation if preShift==0.
+      if (preShift[0] != 0 || preShift[1] != 0 || preShift[2] != 0)
+        for (int bp = 0, b = 0; b < nx; b++) {
+          final Weighttab wtab = tabs[b];
+          final int an = wtab.weights.length;
+
+          int sumr = start[0];
+          int sumg = start[1];
+          int sumb = start[2];
+          for (int wp = 0, ap = wtab.i0 * 3; wp < an && ap < abuf.length; wp++) {
+            final int w = wtab.weights[wp];
+            sumr += w * (abuf[ap++] >> preShift[0]);
+            sumg += w * (abuf[ap++] >> preShift[1]);
+            sumb += w * (abuf[ap++] >> preShift[2]);
+          }
+
+          int t = sumr >> postShift[0];
+          bbuf[bp++] = t < 0 ? 0 : t > 255 ? 255 : t;
+          t = sumg >> postShift[1];
+          bbuf[bp++] = t < 0 ? 0 : t > 255 ? 255 : t;
+          t = sumb >> postShift[2];
+          bbuf[bp++] = t < 0 ? 0 : t > 255 ? 255 : t;
+        }
+      else
+        for (int bp = 0, b = 0; b < nx; b++) {
+          final Weighttab wtab = tabs[b];
+          final int an = wtab.weights.length;
+
+          int sumr = start[0];
+          int sumg = start[1];
+          int sumb = start[2];
+          for (int wp = 0, ap = wtab.i0 * 3; wp < an && ap < abuf.length; wp++) {
+            final int w = wtab.weights[wp];
+            sumr += w * abuf[ap++];
+            sumg += w * abuf[ap++];
+            sumb += w * abuf[ap++];
+          }
+
+          bbuf[bp++] = sumr >> postShift[0];
+          bbuf[bp++] = sumg >> postShift[1];
+          bbuf[bp++] = sumb >> postShift[2];
+        }
+    }
+
+    @Override
+    protected void shift(final int[] shift) {
+      final int half[] = new int[]{
+          1 << shift[0] - 1, 1 << shift[1] - 1, 1 << shift[2] - 1
+      };
+
+      final int abuf[] = data;
+
+      for (int b = 0; b < abuf.length;) {
+        for (int c = 0; c < 3; c++, b++) {
+          final int t = abuf[b] + half[c] >> shift[c];
+          abuf[b] = t < 0 ? 0 : t > 255 ? 255 : t;
+        }
+      }
+    }
+
+    @Override
+    protected void store(final int x, final int y) {
+      dstRaster.setPixels(x, y, length, 1, data);
+    }
+  }
+
+  /**
+   * A Scanline for packed integer pixels.
+   */
+  protected static final class IntegerSinglePixelPackedScanline extends Scanline {
+    private final Raster srcRaster;
+    private final WritableRaster dstRaster;
+
+    private final int data[];
+    private final int[] bitMasks;
+    private final int[] bitOffsets;
+    private final int componentCount;
+    private final SinglePixelPackedSampleModel srcSM;
+    private final int tmp[];
+
+    protected IntegerSinglePixelPackedScanline(Raster src, WritableRaster dst, final int length) {
+      super(length);
+      srcRaster = src;
+      dstRaster = dst;
+
+      srcSM = (SinglePixelPackedSampleModel) srcRaster.getSampleModel();
+
+      bitMasks = srcSM.getBitMasks();
+      bitOffsets = srcSM.getBitOffsets();
+      componentCount = bitMasks.length;
+
+      if (componentCount != bitOffsets.length || bitOffsets.length != srcSM.getNumBands())
+        throw new IllegalArgumentException("weird: getBitMasks().length != getBitOffsets().length");
+
+      tmp = new int[componentCount];
+
+      data = new int[componentCount * length];
+    }
+
+    @Override
+    protected void accumulate(final int weight, final Scanline dst) {
+      final IntegerSinglePixelPackedScanline ispps = (IntegerSinglePixelPackedScanline) dst;
+
+      final int abuf[] = data;
+      final int bbuf[] = ispps.data;
+
+      for (int b = 0; b < bbuf.length; b++)
+        bbuf[b] += weight * abuf[b];
+    }
+
+    @Override
+    protected void clear() {
+      final int[] b = data;
+      for (int j = 0; j < b.length; j++)
+        b[j] = 0;
+    }
+
+    @Override
+    protected void fetch(final int x, final int y) {
+      srcRaster.getPixels(x, y, length, 1, data);
+    }
+
+    @Override
+    protected void filter(final int[] preShift, final int[] postShift, final Weighttab[] tabs, final Scanline dst) {
+      final IntegerSinglePixelPackedScanline ispps = (IntegerSinglePixelPackedScanline) dst;
+      final int nx = dst.length;
+
+      // start sum at 1<<shift-1 for rounding
+      final int start[] = tmp;
+      for (int c = 0; c < componentCount; c++)
+        start[c] = 1 << postShift[c] - 1;
+
+      final int abuf[] = data;
+      final int bbuf[] = ispps.data;
+
+      // the next two blocks are duplicated except for the missing shift
+      // operation if preShift==0.
+      boolean hasPreShift = false;
+      for (int c = 0; c < componentCount && !hasPreShift; c++)
+        hasPreShift |= preShift[c] != 0;
+      if (hasPreShift)
+        for (int bp = 0, b = 0; b < nx; b++) {
+          final Weighttab wtab = tabs[b];
+          final int an = wtab.weights.length;
+
+          for (int c = 0; c < componentCount; c++) {
+            int sum = start[c];
+            for (int wp = 0, ap = wtab.i0 * componentCount + c; wp < an && ap < abuf.length; wp++, ap += componentCount)
+              sum += wtab.weights[wp] * (abuf[ap] >> preShift[c]);
+
+            final int t = sum >> postShift[c];
+            bbuf[bp++] = t < 0 ? 0 : t > 255 ? 255 : t;
+          }
+        }
+      else
+        for (int bp = 0, b = 0; b < nx; b++) {
+          final Weighttab wtab = tabs[b];
+          final int an = wtab.weights.length;
+
+          for (int c = 0; c < componentCount; c++) {
+            int sum = start[c];
+            for (int wp = 0, ap = wtab.i0 * componentCount + c; wp < an && ap < abuf.length; wp++, ap += componentCount)
+              sum += wtab.weights[wp] * abuf[ap];
+
+            bbuf[bp++] = sum >> postShift[c];
+          }
+        }
+    }
+
+    @Override
+    protected void shift(final int[] shift) {
+      final int half[] = tmp;
+      for (int c = 0; c < componentCount; c++)
+        half[c] = 1 << shift[c] - 1;
+
+      final int abuf[] = data;
+
+      for (int b = 0; b < abuf.length;) {
+        for (int c = 0; c < componentCount; c++, b++) {
+          final int t = abuf[b] + half[c] >> shift[c];
+          abuf[b] = t < 0 ? 0 : t > 255 ? 255 : t;
+        }
+      }
+    }
+
+    @Override
+    protected void store(final int x, final int y) {
+      dstRaster.setPixels(x, y, length, 1, data);
+    }
+  }
+
+  /**
+   * A Scanline for packed integer pixels.
+   */
+  protected static final class GenericRasterScanline extends Scanline {
+    private final Raster srcRaster;
+    private final WritableRaster dstRaster;
+
+    private final int componentCount;
+    private final int data[][];
+    private final SampleModel srcSM;
+    private final SampleModel dstSM;
+    private final int channelMask[];
+    private final int[] tmp;
+    private final ScanlineFilter inputFilter;
+
+    protected GenericRasterScanline(Raster src, WritableRaster dst, final int length, int bitsPerChannel[],
+        ScanlineFilter inputFilter) {
+      super(length);
+      srcRaster = src;
+      dstRaster = dst;
+      this.inputFilter = inputFilter;
+
+      srcSM = srcRaster.getSampleModel();
+      dstSM = dstRaster.getSampleModel();
+
+      componentCount = srcSM.getNumBands();
+
+      if (componentCount != dstSM.getNumBands())
+        throw new IllegalArgumentException("weird: src raster num bands != dst raster num bands");
+
+      tmp = new int[componentCount];
+
+      data = new int[componentCount][];
+      for (int i = 0; i < data.length; i++)
+        data[i] = new int[length];
+
+      channelMask = new int[componentCount];
+      for (int c = 0; c < componentCount; c++)
+        channelMask[c] = (1 << bitsPerChannel[c]) - 1;
+    }
+
+    @Override
+    protected void accumulate(final int weight, final Scanline dst) {
+      final GenericRasterScanline grs = (GenericRasterScanline) dst;
+
+      final int l = grs.data[0].length;
+      for (int c = 0; c < componentCount; c++) {
+        final int ac[] = data[c];
+        final int bc[] = grs.data[c];
+
+        for (int b = 0; b < l; b++)
+          bc[b] += weight * ac[b];
+      }
+    }
+
+    @Override
+    protected void clear() {
+      for (int c = 0; c < componentCount; c++) {
+        final int[] b = data[c];
+        for (int x = 0; x < b.length; x++)
+          b[x] = 0;
+      }
+    }
+
+    @Override
+    protected void fetch(final int x, final int y) {
+      for (int c = 0; c < componentCount; c++) {
+        srcRaster.getSamples(x, y, length, 1, c, data[c]);
+        if (null != inputFilter)
+          inputFilter.filter(x, y, c, data[c], length);
+      }
+    }
+
+    @Override
+    protected void filter(final int[] preShift, final int[] postShift, final Weighttab[] tabs, final Scanline dst) {
+      final GenericRasterScanline grs = (GenericRasterScanline) dst;
+      final int nx = dst.length;
+
+      // start sum at 1<<shift-1 for rounding
+      final int start[] = tmp;
+      for (int c = 0; c < componentCount; c++)
+        start[c] = 1 << postShift[c] - 1;
+
+      final int l = data[0].length;
+
+      // the next two blocks are duplicated except for the missing shift
+      // operation if preShift==0.
+      boolean hasPreShift = false;
+      for (int c = 0; c < componentCount && !hasPreShift; c++)
+        hasPreShift |= preShift[c] != 0;
+      if (hasPreShift)
+        for (int c = 0; c < componentCount; c++) {
+          final int ac[] = data[c];
+          final int bc[] = grs.data[c];
+          final int m = channelMask[c];
+          for (int b = 0; b < nx; b++) {
+            final Weighttab wtab = tabs[b];
+            final int an = wtab.weights.length;
+
+            int sum = start[c];
+            for (int wp = 0, ap = wtab.i0; wp < an && ap < l; wp++, ap++)
+              sum += wtab.weights[wp] * (ac[ap] >> preShift[c]);
+
+            final int t = sum >> postShift[c];
+            bc[b] = t < 0 ? 0 : t > m ? m : t;
+          }
+        }
+      else
+        for (int c = 0; c < componentCount; c++) {
+          final int ac[] = data[c];
+          final int bc[] = grs.data[c];
+
+          for (int b = 0; b < nx; b++) {
+            final Weighttab wtab = tabs[b];
+            final int an = wtab.weights.length;
+
+            int sum = start[c];
+            for (int wp = 0, ap = wtab.i0; wp < an && ap < l; wp++, ap++)
+              sum += wtab.weights[wp] * ac[ap];
+
+            bc[b] = sum >> postShift[c];
+          }
+        }
+    }
+
+    @Override
+    protected void shift(final int[] shift) {
+      final int half[] = tmp;
+      for (int c = 0; c < componentCount; c++)
+        half[c] = 1 << shift[c] - 1;
+
+      final int abuf[][] = data;
+
+      final int l = abuf[0].length;
+      for (int c = 0; c < componentCount; c++) {
+        final int ac[] = data[c];
+        final int m = channelMask[c];
+
+        for (int a = 0; a < l; a++) {
+          final int t = ac[a] + half[c] >> shift[c];
+          ac[a] = t < 0 ? 0 : t > m ? m : t;
+        }
+      }
+    }
+
+    @Override
+    protected void store(final int x, final int y) {
+      final int nx = length;
+      for (int c = 0; c < componentCount; c++)
+        dstRaster.setSamples(x, y, nx, 1, c, data[c]);
+    }
+  }
+  
+  /**
+   * A Scanline for BiLevel input data ({@link MultiPixelPackedSampleModel}) to indexed output data
+   * (<code>sun.awt.image.ByteInterleavedRaster</code>).
+   */
+  protected static final class ByteBiLevelPackedScanline extends Scanline {
+    private final Raster srcRaster;
+    private final WritableRaster dstRaster;
+
+    private final int data[];
+
+    protected ByteBiLevelPackedScanline(Raster src, WritableRaster dst, final int length) {
+      super(length);
+      srcRaster = src;
+      dstRaster = dst;
+
+      data = new int[length];
+    }
+
+    @Override
+    protected void accumulate(final int weight, final Scanline dst) {
+      final ByteBiLevelPackedScanline bblps = (ByteBiLevelPackedScanline) dst;
+
+      final int abuf[] = data;
+      final int bbuf[] = bblps.data;
+
+      for (int b = 0; b < bbuf.length; b++)
+        bbuf[b] += weight * abuf[b];
+    }
+
+    @Override
+    protected void clear() {
+      final int[] b = data;
+      for (int j = 0; j < b.length; j++)
+        b[j] = 0;
+    }
+
+    @Override
+    protected void fetch(final int x, final int y) {
+      srcRaster.getPixels(x, y, length, 1, data);
+      for (int i = 0; i < length; i++)
+        if (data[i] != 0)
+          data[i] = 255;
+    }
+
+    @Override
+    protected void filter(final int[] preShift, final int[] postShift, final Weighttab[] tabs, final Scanline dst) {
+      final ByteBiLevelPackedScanline bblps = (ByteBiLevelPackedScanline) dst;
+      final int nx = dst.length;
+
+      // start sum at 1<<shift-1 for rounding
+      final int start = 1 << postShift[0] - 1;
+      final int abuf[] = data;
+      final int bbuf[] = bblps.data;
+
+      // 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 bp = 0, b = 0; b < nx; b++) {
+          final Weighttab wtab = tabs[b];
+          final int an = wtab.weights.length;
+
+          int sum = start;
+          for (int wp = 0, ap = wtab.i0; wp < an && ap < abuf.length; wp++) {
+            sum += wtab.weights[wp] * (abuf[ap++] >> preShift0);
+          }
+
+          final int t = sum >> postShift0;
+          bbuf[bp++] = t < 0 ? 0 : t > 255 ? 255 : t;
+        }
+      else
+        for (int bp = 0, b = 0; b < nx; b++) {
+          final Weighttab wtab = tabs[b];
+          final int an = wtab.weights.length;
+
+          int sum = start;
+          for (int wp = 0, ap = wtab.i0; wp < an && ap < abuf.length; wp++) {
+            sum += wtab.weights[wp] * abuf[ap++];
+          }
+
+          bbuf[bp++] = sum >> postShift0;
+        }
+    }
+
+    @Override
+    protected void shift(final int[] shift) {
+      final int shift0 = shift[0];
+      final int half = 1 << shift0 - 1;
+
+      final int abuf[] = data;
+
+      for (int b = 0; b < abuf.length; b++) {
+        final int t = abuf[b] + half >> shift0;
+        abuf[b] = t < 0 ? 0 : t > 255 ? 255 : t;
+      }
+    }
+
+    @Override
+    protected void store(final int x, final int y) {
+      dstRaster.setPixels(x, y, length, 1, data);
+    }
+  }
+
+  int y;
+  protected final int length;
+
+  protected Scanline(final int width) {
+    length = width;
+  }
+
+  protected final int getWidth() {
+    return length;
+  }
+
+  protected abstract void clear();
+
+  protected abstract void fetch(int x, int y);
+
+  protected abstract void filter(int[] preShift, int[] postShift, Weighttab[] xweights, Scanline dst);
+
+  protected abstract void accumulate(int weight, Scanline dst);
+
+  protected abstract void shift(int[] finalshift);
+
+  protected abstract void store(int x, int y);
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/pdfbox/jbig2/image/Weighttab.java b/src/main/java/org/apache/pdfbox/jbig2/image/Weighttab.java
new file mode 100644
index 0000000..7cbaa5f
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/image/Weighttab.java
@@ -0,0 +1,114 @@
+/**
+ * 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 static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import org.apache.pdfbox.jbig2.util.Utils;
+
+final class Weighttab {
+  final int weights[]; /* weight[i] goes with pixel at i0+i */
+  final int i0, i1; /* range of samples is [i0..i1-1] */
+
+  /*
+   * make_weighttab: sample the continuous filter, scaled by ap.scale and positioned at continuous
+   * source coordinate cen, for source coordinates in the range [0..len-1], writing the weights into
+   * wtab. Scale the weights so they sum to WEIGHTONE, and trim leading and trailing zeros if
+   * trimzeros!=0. b is the dest coordinate (for diagnostics).
+   */
+  public Weighttab(ParameterizedFilter pf, int weightOne, final double center, int a0, final int a1,
+      final boolean trimzeros) {
+    // find the source coord range of this positioned filter: [i0..i1-1] and clamp to input range
+    int i0 = max(pf.minIndex(center), a0);
+    int i1 = min(pf.maxIndex(center), a1);
+
+    // find scale factor sc to normalize the filter
+    double den = 0;
+    for (int i = i0; i <= i1; i++)
+      den += pf.eval(center, i);
+
+    // set sc so that sum of sc*func() is approximately WEIGHTONE
+    final double scale = den == 0. ? weightOne : weightOne / den;
+
+    // find range of non-zero samples
+    if (trimzeros) {
+      boolean stillzero = trimzeros;
+      int lastnonzero = 0;
+      for (int i = i0; i <= i1; i++) {
+        /* evaluate the filter function at p */
+        final double tr = Utils.clamp(scale * pf.eval(center, i), Short.MIN_VALUE, Short.MAX_VALUE);
+
+        final int t = (int) Math.floor(tr + .5);
+        if (stillzero && t == 0)
+          i0++; /* find first nonzero */
+        else {
+          stillzero = false;
+          if (t != 0)
+            lastnonzero = i; /* find last nonzero */
+        }
+      }
+
+      i1 = max(lastnonzero, i0);
+    }
+
+    // initialize weight table of appropriate length
+    weights = new int[i1 - i0 + 1];
+
+    // compute the discrete, sampled filter coefficients
+    int sum = 0;
+    for (int idx = 0, i = i0; i <= i1; i++) {
+      /* evaluate the filter function at p */
+      final double tr = Utils.clamp(scale * pf.eval(center, i), Short.MIN_VALUE, Short.MAX_VALUE);
+
+      final int t = (int) Math.floor(tr + .5);
+      weights[idx++] = t; /* add weight to table */
+      sum += t;
+    }
+
+    if (sum == 0) {
+      i1 = i0;
+      weights[0] = weightOne;
+    } else if (sum != weightOne) {
+      /*
+       * Fudge the center slightly to make sum=WEIGHTONE exactly. Is this the best way to normalize
+       * a discretely sampled continuous filter?
+       */
+      int i = (int) (center + .5);
+      if (i >= i1)
+        i = i1 - 1;
+      if (i < i0)
+        i = i0;
+      final int t = weightOne - sum;
+      if (Resizer.debug)
+        System.out.printf("[%d]+=%d ", i, t);
+      weights[i - i0] += t; /* fudge center sample */
+    }
+
+    this.i0 = i0 - a0;
+    this.i1 = i1 - a0;
+
+    if (Resizer.debug) {
+      System.out.printf("\t");
+      for (int idx = 0, i = i0; i < i1; i++, idx++)
+        System.out.printf("%5d ", weights[idx]);
+      System.out.printf("\n");
+    }
+  }
+  
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/pdfbox/jbig2/io/DefaultInputStreamFactory.java b/src/main/java/org/apache/pdfbox/jbig2/io/DefaultInputStreamFactory.java
new file mode 100644
index 0000000..b92b033
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/io/DefaultInputStreamFactory.java
@@ -0,0 +1,37 @@
+/**
+ * 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.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.FileCacheImageInputStream;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+
+public class DefaultInputStreamFactory implements InputStreamFactory {
+
+  public ImageInputStream getInputStream(InputStream is) {
+    try {
+      return new FileCacheImageInputStream(is, null);
+    } catch (IOException e) {
+      return new MemoryCacheImageInputStream(is);
+    }
+  }
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/io/InputStreamFactory.java b/src/main/java/org/apache/pdfbox/jbig2/io/InputStreamFactory.java
new file mode 100644
index 0000000..7b2e5c5
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/io/InputStreamFactory.java
@@ -0,0 +1,27 @@
+/**
+ * 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.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+
+public interface InputStreamFactory {
+  public ImageInputStream getInputStream(InputStream is) throws IOException;
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/io/SubInputStream.java b/src/main/java/org/apache/pdfbox/jbig2/io/SubInputStream.java
new file mode 100644
index 0000000..a7782bc
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/io/SubInputStream.java
@@ -0,0 +1,150 @@
+/**
+ * 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.io;
+
+import java.io.IOException;
+
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageInputStreamImpl;
+
+/**
+ * A wrapper for an {@link ImageInputStream} which is able to provide a view of a specific part of
+ * the wrapped stream. Read accesses to the wrapped stream are synchronized, so that users of this
+ * stream need to deal with synchronization against other users of the same instance, but not
+ * against other users of the wrapped stream.
+ */
+public class SubInputStream extends ImageInputStreamImpl {
+
+  protected final ImageInputStream wrappedStream;
+
+  /**
+   * The position in the wrapped stream at which the window starts. Offset is an absolut value.
+   */
+  protected final long offset;
+
+  /**
+   * The length of the window. Length is an relative value.
+   */
+  protected final long length;
+
+  /**
+   * A buffer which is used to improve read performance.
+   */
+  private final byte buffer[] = new byte[4096];
+
+  /**
+   * Location of the first byte in the buffer with respect to the start of the stream.
+   */
+  long bufferBase;
+
+  /**
+   * Location of the last byte in the buffer with respect to the start of the stream.
+   */
+  long bufferTop;
+
+  /**
+   * Construct a new SubInputStream which provides a view of the wrapped stream.
+   * 
+   * @param iis - The stream to be wrapped.
+   * @param offset - The absolute position in the wrapped stream at which the sub-stream starts.
+   * @param length - The length of the sub-stream.
+   */
+  public SubInputStream(ImageInputStream iis, long offset, long length) {
+    assert null != iis;
+    assert length >= 0;
+    assert offset >= 0;
+
+    this.wrappedStream = iis;
+    this.offset = offset;
+    this.length = length;
+  }
+
+  @Override
+  public int read() throws IOException {
+    if (streamPos >= length) {
+      return -1;
+    }
+
+    if (streamPos >= bufferTop || streamPos < bufferBase) {
+      if (!fillBuffer()) {
+        return -1;
+      }
+    }
+
+    int read = 0xff & buffer[(int) (streamPos - bufferBase)];
+
+    streamPos++;
+
+    return read;
+  }
+
+  @Override
+  public int read(byte[] b, int off, int len) throws IOException {
+    if (streamPos >= length) {
+      return -1;
+    }
+
+    synchronized (wrappedStream) {
+      if (wrappedStream.getStreamPosition() != streamPos + offset) {
+        wrappedStream.seek(streamPos + offset);
+      }
+
+      int toRead = (int) Math.min(len, length - streamPos);
+      int read = wrappedStream.read(b, off, toRead);
+      streamPos += read;
+
+      return read;
+    }
+  }
+
+  /**
+   * Fill the buffer at the current stream position.
+   * 
+   * @throws IOException
+   * @return Boolean flag. {@code true} if successful, {@code false} if not.
+   */
+  private boolean fillBuffer() throws IOException {
+    synchronized (wrappedStream) {
+      if (wrappedStream.getStreamPosition() != streamPos + offset) {
+        wrappedStream.seek(streamPos + offset);
+      }
+
+      bufferBase = streamPos;
+      int toRead = (int) Math.min(buffer.length, length - streamPos);
+      int read = wrappedStream.read(buffer, 0, toRead);
+      bufferTop = bufferBase + read;
+
+      return read > 0;
+    }
+  }
+
+  @Override
+  public long length() {
+    return length;
+  }
+
+  /**
+   * Skips remaining bits in the current byte.
+   */
+  public void skipBits() {
+    if (bitOffset != 0) {
+      bitOffset = 0;
+      streamPos++;
+    }
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/EndOfStripe.java b/src/main/java/org/apache/pdfbox/jbig2/segments/EndOfStripe.java
new file mode 100644
index 0000000..704137e
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/EndOfStripe.java
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.jbig2.SegmentData;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+
+/**
+ * This segment flags an end of stripe (see JBIG2 ISO standard, 7.4.9).
+ */
+public class EndOfStripe implements SegmentData {
+
+  private SubInputStream subInputStream;
+  private int lineNumber;
+
+  private void parseHeader() throws IOException, IntegerMaxValueException, InvalidHeaderValueException {
+    lineNumber = (int) (subInputStream.readBits(32) & 0xffffffff);
+  }
+
+  public void init(SegmentHeader header, SubInputStream sis) throws IntegerMaxValueException,
+      InvalidHeaderValueException, IOException {
+    this.subInputStream = sis;
+    parseHeader();
+  }
+
+  public int getLineNumber() {
+    return lineNumber;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/GenericRefinementRegion.java b/src/main/java/org/apache/pdfbox/jbig2/segments/GenericRefinementRegion.java
new file mode 100644
index 0000000..af59e7d
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/GenericRefinementRegion.java
@@ -0,0 +1,743 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.Region;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.CX;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represents a generic refinement region and implements the procedure described in JBIG2
+ * ISO standard, 6.3 and 7.4.7.
+ */
+public class GenericRefinementRegion implements Region {
+  private static final Logger log = LoggerFactory.getLogger(GenericRefinementRegion.class);
+
+  public static abstract class Template {
+    protected abstract short form(short c1, short c2, short c3, short c4, short c5);
+
+    protected abstract void setIndex(CX cx);
+  }
+
+  private static class Template0 extends Template {
+
+    @Override
+    protected short form(short c1, short c2, short c3, short c4, short c5) {
+      return (short) ((c1 << 10) | (c2 << 7) | (c3 << 4) | (c4 << 1) | c5);
+    }
+
+    @Override
+    protected void setIndex(CX cx) {
+      // Figure 14, page 22
+      cx.setIndex(0x100);
+    }
+
+  }
+
+  private static class Template1 extends Template {
+
+    @Override
+    protected short form(short c1, short c2, short c3, short c4, short c5) {
+      return (short) (((c1 & 0x02) << 8) | (c2 << 6) | ((c3 & 0x03) << 4) | (c4 << 1) | c5);
+    }
+
+    @Override
+    protected void setIndex(CX cx) {
+      // Figure 15, page 22
+      cx.setIndex(0x080);
+    }
+
+  }
+
+  private static final Template T0 = new Template0();
+  private static final Template T1 = new Template1();
+
+  private SubInputStream subInputStream;
+
+  private SegmentHeader segmentHeader;
+
+  /** Region segment information flags, 7.4.1 */
+  private RegionSegmentInformation regionInfo;
+
+  /** Generic refinement region segment flags, 7.4.7.2 */
+  private boolean isTPGROn;
+  private short templateID;
+
+  private Template template;
+  /** Generic refinement region segment AT flags, 7.4.7.3 */
+  private short grAtX[];
+  private short grAtY[];
+
+  /** Decoded data as pixel values (use row stride/width to wrap line) */
+  private Bitmap regionBitmap;
+
+  /** Variables for decoding */
+  private Bitmap referenceBitmap;
+  private int referenceDX;
+  private int referenceDY;
+
+  private ArithmeticDecoder arithDecoder;
+  private CX cx;
+
+  /**
+   * If true, AT pixels are not on their nominal location and have to be overridden.
+   */
+  private boolean override;
+  private boolean[] grAtOverride;
+  public GenericRefinementRegion() {
+  }
+
+  public GenericRefinementRegion(final SubInputStream subInputStream) {
+    this.subInputStream = subInputStream;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+  }
+
+  public GenericRefinementRegion(final SubInputStream subInputStream, final SegmentHeader segmentHeader) {
+    this.subInputStream = subInputStream;
+    this.segmentHeader = segmentHeader;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+  }
+
+  /**
+   * Parses the flags described in JBIG2 ISO standard:
+   * <ul>
+   * <li>7.4.7.2 Generic refinement region segment flags</li>
+   * <li>7.4.7.3 Generic refinement refion segment AT flags</li>
+   * </ul>
+   * 
+   * @throws IOException
+   */
+  private void parseHeader() throws IOException {
+    regionInfo.parseHeader();
+
+    /* Bit 2-7 */
+    subInputStream.readBits(6); // Dirty read...
+
+    /* Bit 1 */
+    if (subInputStream.readBit() == 1) {
+      isTPGROn = true;
+    }
+
+    /* Bit 0 */
+    templateID = (short) subInputStream.readBit();
+
+    switch (templateID){
+      case 0 :
+        template = T0;
+        readAtPixels();
+        break;
+      case 1 :
+        template = T1;
+        break;
+    }
+  }
+
+  private void readAtPixels() throws IOException {
+    grAtX = new short[2];
+    grAtY = new short[2];
+
+    /* Byte 0 */
+    grAtX[0] = subInputStream.readByte();
+    /* Byte 1 */
+    grAtY[0] = subInputStream.readByte();
+    /* Byte 2 */
+    grAtX[1] = subInputStream.readByte();
+    /* Byte 3 */
+    grAtY[1] = subInputStream.readByte();
+  }
+
+  /**
+   * Decode using a template and arithmetic coding, as described in 6.3.5.6
+   * 
+   * @throws IOException
+   * @throws InvalidHeaderValueException
+   * @throws IntegerMaxValueException
+   */
+  public Bitmap getRegionBitmap() throws IOException, IntegerMaxValueException, InvalidHeaderValueException {
+    if (null == regionBitmap) {
+      /* 6.3.5.6 - 1) */
+      int isLineTypicalPredicted = 0;
+
+      if (referenceBitmap == null) {
+        // Get the reference bitmap, which is the base of refinement process
+        referenceBitmap = getGrReference();
+      }
+
+      if (arithDecoder == null) {
+        arithDecoder = new ArithmeticDecoder(subInputStream);
+      }
+
+      if (cx == null) {
+        cx = new CX(8192, 1);
+      }
+
+      /* 6.3.5.6 - 2) */
+      regionBitmap = new Bitmap(regionInfo.getBitmapWidth(), regionInfo.getBitmapHeight());
+
+      if (templateID == 0) {
+        // AT pixel may only occur in template 0
+        updateOverride();
+      }
+
+      final int paddedWidth = (regionBitmap.getWidth() + 7) & -8;
+      final int deltaRefStride = isTPGROn ? -referenceDY * referenceBitmap.getRowStride() : 0;
+      final int yOffset = deltaRefStride + 1;
+
+      /* 6.3.5.6 - 3 */
+      for (int y = 0; y < regionBitmap.getHeight(); y++) {
+        /* 6.3.5.6 - 3 b) */
+        if (isTPGROn) {
+          isLineTypicalPredicted ^= decodeSLTP();
+        }
+
+        if (isLineTypicalPredicted == 0) {
+          /* 6.3.5.6 - 3 c) */
+          decodeOptimized(y, regionBitmap.getWidth(), regionBitmap.getRowStride(), referenceBitmap.getRowStride(),
+              paddedWidth, deltaRefStride, yOffset);
+        } else {
+          /* 6.3.5.6 - 3 d) */
+          decodeTypicalPredictedLine(y, regionBitmap.getWidth(), regionBitmap.getRowStride(),
+              referenceBitmap.getRowStride(), paddedWidth, deltaRefStride);
+        }
+      }
+    }
+    /* 6.3.5.6 - 4) */
+    return regionBitmap;
+  }
+
+  private int decodeSLTP() throws IOException {
+    template.setIndex(cx);
+    return arithDecoder.decode(cx);
+  }
+
+  private Bitmap getGrReference() throws IntegerMaxValueException, InvalidHeaderValueException, IOException {
+    final SegmentHeader[] segments = segmentHeader.getRtSegments();
+    final Region region = (Region) segments[0].getSegmentData();
+
+    return region.getRegionBitmap();
+  }
+
+  private void decodeOptimized(final int lineNumber, final int width, final int rowStride, final int refRowStride,
+      final int paddedWidth, final int deltaRefStride, final int lineOffset) throws IOException {
+
+    // Offset of the reference bitmap with respect to the bitmap being decoded
+    // For example: if referenceDY = -1, y is 1 HIGHER that currY
+    final int currentLine = lineNumber - referenceDY;
+    final int referenceByteIndex = referenceBitmap.getByteIndex(Math.max(0, -referenceDX), currentLine);
+
+    final int byteIndex = regionBitmap.getByteIndex(Math.max(0, referenceDX), lineNumber);
+
+    switch (templateID){
+      case 0 :
+        decodeTemplate(lineNumber, width, rowStride, refRowStride, paddedWidth, deltaRefStride, lineOffset, byteIndex,
+            currentLine, referenceByteIndex, T0);
+        break;
+      case 1 :
+        decodeTemplate(lineNumber, width, rowStride, refRowStride, paddedWidth, deltaRefStride, lineOffset, byteIndex,
+            currentLine, referenceByteIndex, T1);
+        break;
+    }
+
+  }
+
+  private void decodeTemplate(final int lineNumber, final int width, final int rowStride, final int refRowStride,
+      final int paddedWidth, final int deltaRefStride, final int lineOffset, int byteIndex, final int currentLine,
+      int refByteIndex, Template templateFormation) throws IOException {
+    short c1, c2, c3, c4, c5;
+
+    int w1, w2, w3, w4;
+    w1 = w2 = w3 = w4 = 0;
+
+    if (currentLine >= 1 && (currentLine - 1) < referenceBitmap.getHeight())
+      w1 = referenceBitmap.getByteAsInteger(refByteIndex - refRowStride);
+    if (currentLine >= 0 && currentLine < referenceBitmap.getHeight())
+      w2 = referenceBitmap.getByteAsInteger(refByteIndex);
+    if (currentLine >= -1 && currentLine + 1 < referenceBitmap.getHeight())
+      w3 = referenceBitmap.getByteAsInteger(refByteIndex + refRowStride);
+    refByteIndex++;
+
+    if (lineNumber >= 1) {
+      w4 = regionBitmap.getByteAsInteger(byteIndex - rowStride);
+    }
+    byteIndex++;
+
+    final int modReferenceDX = referenceDX % 8;
+    final int shiftOffset = 6 + modReferenceDX;
+    final int modRefByteIdx = refByteIndex % refRowStride;
+
+    if (shiftOffset >= 0) {
+      c1 = (short) ((shiftOffset >= 8 ? 0 : w1 >>> shiftOffset) & 0x07);
+      c2 = (short) ((shiftOffset >= 8 ? 0 : w2 >>> shiftOffset) & 0x07);
+      c3 = (short) ((shiftOffset >= 8 ? 0 : w3 >>> shiftOffset) & 0x07);
+      if (shiftOffset == 6 && modRefByteIdx > 1) {
+        if (currentLine >= 1 && (currentLine - 1) < referenceBitmap.getHeight()) {
+          c1 |= referenceBitmap.getByteAsInteger(refByteIndex - refRowStride - 2) << 2 & 0x04;
+        }
+        if (currentLine >= 0 && currentLine < referenceBitmap.getHeight()) {
+          c2 |= referenceBitmap.getByteAsInteger(refByteIndex - 2) << 2 & 0x04;
+        }
+        if (currentLine >= -1 && currentLine + 1 < referenceBitmap.getHeight()) {
+          c3 |= referenceBitmap.getByteAsInteger(refByteIndex + refRowStride - 2) << 2 & 0x04;
+        }
+      }
+      if (shiftOffset == 0) {
+        w1 = w2 = w3 = 0;
+        if (modRefByteIdx < refRowStride - 1) {
+          if (currentLine >= 1 && (currentLine - 1) < referenceBitmap.getHeight())
+            w1 = referenceBitmap.getByteAsInteger(refByteIndex - refRowStride);
+          if (currentLine >= 0 && currentLine < referenceBitmap.getHeight())
+            w2 = referenceBitmap.getByteAsInteger(refByteIndex);
+          if (currentLine >= -1 && currentLine + 1 < referenceBitmap.getHeight())
+            w3 = referenceBitmap.getByteAsInteger(refByteIndex + refRowStride);
+        }
+        refByteIndex++;
+      }
+    } else {
+      c1 = (short) ((w1 << 1) & 0x07);
+      c2 = (short) ((w2 << 1) & 0x07);
+      c3 = (short) ((w3 << 1) & 0x07);
+      w1 = w2 = w3 = 0;
+      if (modRefByteIdx < refRowStride - 1) {
+        if (currentLine >= 1 && (currentLine - 1) < referenceBitmap.getHeight())
+          w1 = referenceBitmap.getByteAsInteger(refByteIndex - refRowStride);
+        if (currentLine >= 0 && currentLine < referenceBitmap.getHeight())
+          w2 = referenceBitmap.getByteAsInteger(refByteIndex);
+        if (currentLine >= -1 && currentLine + 1 < referenceBitmap.getHeight())
+          w3 = referenceBitmap.getByteAsInteger(refByteIndex + refRowStride);
+        refByteIndex++;
+      }
+      c1 |= (short) ((w1 >>> 7) & 0x07);
+      c2 |= (short) ((w2 >>> 7) & 0x07);
+      c3 |= (short) ((w3 >>> 7) & 0x07);
+    }
+
+    c4 = (short) (w4 >>> 6);
+    c5 = 0;
+
+    final int modBitsToTrim = (2 - modReferenceDX) % 8;
+    w1 <<= modBitsToTrim;
+    w2 <<= modBitsToTrim;
+    w3 <<= modBitsToTrim;
+
+    w4 <<= 2;
+
+    for (int x = 0; x < width; x++) {
+      final int minorX = x & 0x07;
+
+      final short tval = templateFormation.form(c1, c2, c3, c4, c5);
+
+      if (override) {
+        cx.setIndex(overrideAtTemplate0(tval, x, lineNumber,
+            regionBitmap.getByte(regionBitmap.getByteIndex(x, lineNumber)), minorX));
+      } else {
+        cx.setIndex(tval);
+      }
+      final int bit = arithDecoder.decode(cx);
+      regionBitmap.setPixel(x, lineNumber, (byte) bit);
+
+      c1 = (short) (((c1 << 1) | 0x01 & (w1 >>> 7)) & 0x07);
+      c2 = (short) (((c2 << 1) | 0x01 & (w2 >>> 7)) & 0x07);
+      c3 = (short) (((c3 << 1) | 0x01 & (w3 >>> 7)) & 0x07);
+      c4 = (short) (((c4 << 1) | 0x01 & (w4 >>> 7)) & 0x07);
+      c5 = (short) bit;
+
+      if ((x - referenceDX) % 8 == 5) {
+        if (((x - referenceDX) / 8) + 1 >= referenceBitmap.getRowStride()) {
+          w1 = w2 = w3 = 0;
+        } else {
+          if (currentLine >= 1 && (currentLine - 1 < referenceBitmap.getHeight())) {
+            w1 = referenceBitmap.getByteAsInteger(refByteIndex - refRowStride);
+          } else {
+            w1 = 0;
+          }
+          if (currentLine >= 0 && currentLine < referenceBitmap.getHeight()) {
+            w2 = referenceBitmap.getByteAsInteger(refByteIndex);
+          } else {
+            w2 = 0;
+          }
+          if (currentLine >= -1 && (currentLine + 1) < referenceBitmap.getHeight()) {
+            w3 = referenceBitmap.getByteAsInteger(refByteIndex + refRowStride);
+          } else {
+            w3 = 0;
+          }
+        }
+        refByteIndex++;
+      } else {
+        w1 <<= 1;
+        w2 <<= 1;
+        w3 <<= 1;
+      }
+
+      if (minorX == 5 && lineNumber >= 1) {
+        if ((x >> 3) + 1 >= regionBitmap.getRowStride()) {
+          w4 = 0;
+        } else {
+          w4 = regionBitmap.getByteAsInteger(byteIndex - rowStride);
+        }
+        byteIndex++;
+      } else {
+        w4 <<= 1;
+      }
+
+    }
+  }
+
+  private void updateOverride() {
+    if (grAtX == null || grAtY == null) {
+      log.info("AT pixels not set");
+      return;
+    }
+
+    if (grAtX.length != grAtY.length) {
+      log.info("AT pixel inconsistent");
+      return;
+    }
+
+    grAtOverride = new boolean[grAtX.length];
+
+    switch (templateID){
+      case 0 :
+        if (grAtX[0] != -1 && grAtY[0] != -1) {
+          grAtOverride[0] = true;
+          override = true;
+        }
+
+        if (grAtX[1] != -1 && grAtY[1] != -1) {
+          grAtOverride[1] = true;
+          override = true;
+        }
+        break;
+      case 1 :
+        override = false;
+        break;
+    }
+  }
+
+  private void decodeTypicalPredictedLine(final int lineNumber, final int width, final int rowStride,
+      final int refRowStride, final int paddedWidth, final int deltaRefStride) throws IOException {
+
+    // Offset of the reference bitmap with respect to the bitmap being
+    // decoded
+    // For example: if grReferenceDY = -1, y is 1 HIGHER that currY
+    final int currentLine = lineNumber - referenceDY;
+    final int refByteIndex = referenceBitmap.getByteIndex(0, currentLine);
+
+    final int byteIndex = regionBitmap.getByteIndex(0, lineNumber);
+
+    switch (templateID){
+      case 0 :
+        decodeTypicalPredictedLineTemplate0(lineNumber, width, rowStride, refRowStride, paddedWidth, deltaRefStride,
+            byteIndex, currentLine, refByteIndex);
+        break;
+      case 1 :
+        decodeTypicalPredictedLineTemplate1(lineNumber, width, rowStride, refRowStride, paddedWidth, deltaRefStride,
+            byteIndex, currentLine, refByteIndex);
+        break;
+    }
+  }
+
+  private void decodeTypicalPredictedLineTemplate0(final int lineNumber, final int width, final int rowStride,
+      final int refRowStride, final int paddedWidth, final int deltaRefStride, int byteIndex, final int currentLine,
+      int refByteIndex) throws IOException {
+    int context;
+    int overriddenContext;
+
+    int previousLine;
+    int previousReferenceLine;
+    int currentReferenceLine;
+    int nextReferenceLine;
+
+    if (lineNumber > 0) {
+      previousLine = regionBitmap.getByteAsInteger(byteIndex - rowStride);
+    } else {
+      previousLine = 0;
+    }
+
+    if (currentLine > 0 && currentLine <= referenceBitmap.getHeight()) {
+      previousReferenceLine = referenceBitmap.getByteAsInteger(refByteIndex - refRowStride + deltaRefStride) << 4;
+    } else {
+      previousReferenceLine = 0;
+    }
+
+    if (currentLine >= 0 && currentLine < referenceBitmap.getHeight()) {
+      currentReferenceLine = referenceBitmap.getByteAsInteger(refByteIndex + deltaRefStride) << 1;
+    } else {
+      currentReferenceLine = 0;
+    }
+
+    if (currentLine > -2 && currentLine < (referenceBitmap.getHeight() - 1)) {
+      nextReferenceLine = referenceBitmap.getByteAsInteger(refByteIndex + refRowStride + deltaRefStride);
+    } else {
+      nextReferenceLine = 0;
+    }
+
+    context = ((previousLine >> 5) & 0x6) | ((nextReferenceLine >> 2) & 0x30) | (currentReferenceLine & 0x180)
+        | (previousReferenceLine & 0xc00);
+
+    int nextByte;
+    for (int x = 0; x < paddedWidth; x = nextByte) {
+      byte result = 0;
+      nextByte = x + 8;
+      final int minorWidth = width - x > 8 ? 8 : width - x;
+      final boolean readNextByte = nextByte < width;
+      final boolean refReadNextByte = nextByte < referenceBitmap.getWidth();
+
+      final int yOffset = deltaRefStride + 1;
+
+      if (lineNumber > 0) {
+        previousLine = (previousLine << 8)
+            | (readNextByte ? regionBitmap.getByteAsInteger(byteIndex - rowStride + 1) : 0);
+      }
+
+      if (currentLine > 0 && currentLine <= referenceBitmap.getHeight()) {
+        previousReferenceLine = (previousReferenceLine << 8)
+            | (refReadNextByte ? referenceBitmap.getByteAsInteger(refByteIndex - refRowStride + yOffset) << 4 : 0);
+      }
+
+      if (currentLine >= 0 && currentLine < referenceBitmap.getHeight()) {
+        currentReferenceLine = (currentReferenceLine << 8)
+            | (refReadNextByte ? referenceBitmap.getByteAsInteger(refByteIndex + yOffset) << 1 : 0);
+      }
+
+      if (currentLine > -2 && currentLine < (referenceBitmap.getHeight() - 1)) {
+        nextReferenceLine = (nextReferenceLine << 8)
+            | (refReadNextByte ? referenceBitmap.getByteAsInteger(refByteIndex + refRowStride + yOffset) : 0);
+      }
+
+      for (int minorX = 0; minorX < minorWidth; minorX++) {
+        boolean isPixelTypicalPredicted = false;
+        int bit = 0;
+
+        // i)
+        final int bitmapValue = (context >> 4) & 0x1FF;
+
+        if (bitmapValue == 0x1ff) {
+          isPixelTypicalPredicted = true;
+          bit = 1;
+        } else if (bitmapValue == 0x00) {
+          isPixelTypicalPredicted = true;
+          bit = 0;
+        }
+
+        if (!isPixelTypicalPredicted) {
+          // iii) - is like 3 c) but for one pixel only
+
+          if (override) {
+            overriddenContext = overrideAtTemplate0(context, x + minorX, lineNumber, result, minorX);
+            cx.setIndex(overriddenContext);
+          } else {
+            cx.setIndex(context);
+          }
+          bit = arithDecoder.decode(cx);
+        }
+
+        final int toShift = 7 - minorX;
+        result |= bit << toShift;
+
+        context = ((context & 0xdb6) << 1) | bit | ((previousLine >> toShift + 5) & 0x002)
+            | ((nextReferenceLine >> toShift + 2) & 0x010) | ((currentReferenceLine >> toShift) & 0x080)
+            | ((previousReferenceLine >> toShift) & 0x400);
+      }
+      regionBitmap.setByte(byteIndex++, result);
+      refByteIndex++;
+    }
+  }
+
+  private void decodeTypicalPredictedLineTemplate1(final int lineNumber, final int width, final int rowStride,
+      final int refRowStride, final int paddedWidth, final int deltaRefStride, int byteIndex, final int currentLine,
+      int refByteIndex) throws IOException {
+    int context;
+    int grReferenceValue;
+
+    int previousLine;
+    int previousReferenceLine;
+    int currentReferenceLine;
+    int nextReferenceLine;
+
+    if (lineNumber > 0) {
+      previousLine = regionBitmap.getByteAsInteger(byteIndex - rowStride);
+    } else {
+      previousLine = 0;
+    }
+
+    if (currentLine > 0 && currentLine <= referenceBitmap.getHeight()) {
+      previousReferenceLine = referenceBitmap.getByteAsInteger(byteIndex - refRowStride + deltaRefStride) << 2;
+    } else {
+      previousReferenceLine = 0;
+    }
+
+    if (currentLine >= 0 && currentLine < referenceBitmap.getHeight()) {
+      currentReferenceLine = referenceBitmap.getByteAsInteger(byteIndex + deltaRefStride);
+    } else {
+      currentReferenceLine = 0;
+    }
+
+    if (currentLine > -2 && currentLine < (referenceBitmap.getHeight() - 1)) {
+      nextReferenceLine = referenceBitmap.getByteAsInteger(byteIndex + refRowStride + deltaRefStride);
+    } else {
+      nextReferenceLine = 0;
+    }
+
+    context = ((previousLine >> 5) & 0x6) | ((nextReferenceLine >> 2) & 0x30) | (currentReferenceLine & 0xc0)
+        | (previousReferenceLine & 0x200);
+
+    grReferenceValue = ((nextReferenceLine >> 2) & 0x70) | (currentReferenceLine & 0xc0)
+        | (previousReferenceLine & 0x700);
+
+    int nextByte;
+    for (int x = 0; x < paddedWidth; x = nextByte) {
+      byte result = 0;
+      nextByte = x + 8;
+      final int minorWidth = width - x > 8 ? 8 : width - x;
+      final boolean readNextByte = nextByte < width;
+      final boolean refReadNextByte = nextByte < referenceBitmap.getWidth();
+
+      final int yOffset = deltaRefStride + 1;
+
+      if (lineNumber > 0) {
+        previousLine = (previousLine << 8)
+            | (readNextByte ? regionBitmap.getByteAsInteger(byteIndex - rowStride + 1) : 0);
+      }
+
+      if (currentLine > 0 && currentLine <= referenceBitmap.getHeight()) {
+        previousReferenceLine = (previousReferenceLine << 8)
+            | (refReadNextByte ? referenceBitmap.getByteAsInteger(refByteIndex - refRowStride + yOffset) << 2 : 0);
+      }
+
+      if (currentLine >= 0 && currentLine < referenceBitmap.getHeight()) {
+        currentReferenceLine = (currentReferenceLine << 8)
+            | (refReadNextByte ? referenceBitmap.getByteAsInteger(refByteIndex + yOffset) : 0);
+      }
+
+      if (currentLine > -2 && currentLine < (referenceBitmap.getHeight() - 1)) {
+        nextReferenceLine = (nextReferenceLine << 8)
+            | (refReadNextByte ? referenceBitmap.getByteAsInteger(refByteIndex + refRowStride + yOffset) : 0);
+      }
+
+      for (int minorX = 0; minorX < minorWidth; minorX++) {
+        int bit = 0;
+
+        // i)
+        final int bitmapValue = (grReferenceValue >> 4) & 0x1ff;
+
+        if (bitmapValue == 0x1ff) {
+          bit = 1;
+        } else if (bitmapValue == 0x00) {
+          bit = 0;
+        } else {
+          cx.setIndex(context);
+          bit = arithDecoder.decode(cx);
+        }
+
+        final int toShift = 7 - minorX;
+        result |= bit << toShift;
+
+        context = ((context & 0x0d6) << 1) | bit | ((previousLine >> toShift + 5) & 0x002)
+            | ((nextReferenceLine >> toShift + 2) & 0x010) | ((currentReferenceLine >> toShift) & 0x040)
+            | ((previousReferenceLine >> toShift) & 0x200);
+
+        grReferenceValue = ((grReferenceValue & 0x0db) << 1) | ((nextReferenceLine >> toShift + 2) & 0x010)
+            | ((currentReferenceLine >> toShift) & 0x080) | ((previousReferenceLine >> toShift) & 0x400);
+      }
+      regionBitmap.setByte(byteIndex++, result);
+      refByteIndex++;
+    }
+  }
+
+  private int overrideAtTemplate0(int context, final int x, final int y, final int result, final int minorX)
+      throws IOException {
+    if (grAtOverride[0]) {
+      context &= 0xfff7;
+      if (grAtY[0] == 0 && grAtX[0] >= -minorX) {
+        context |= (result >> (7 - (minorX + grAtX[0])) & 0x1) << 3;
+      } else {
+        context |= getPixel(regionBitmap, x + grAtX[0], y + grAtY[0]) << 3;
+      }
+    }
+
+    if (grAtOverride[1]) {
+      context &= 0xefff;
+      if (grAtY[1] == 0 && grAtX[1] >= -minorX) {
+        context |= (result >> (7 - (minorX + grAtX[1])) & 0x1) << 12;
+      } else {
+        context |= getPixel(referenceBitmap, x + grAtX[1] + referenceDX, y + grAtY[1] + referenceDY) << 12;
+      }
+    }
+    return context;
+  }
+
+  private byte getPixel(final Bitmap b, final int x, final int y) throws IOException {
+    if (x < 0 || x >= b.getWidth()) {
+      return 0;
+    }
+    if (y < 0 || y >= b.getHeight()) {
+      return 0;
+    }
+
+    return b.getPixel(x, y);
+  }
+
+  public void init(final SegmentHeader header, final SubInputStream sis) throws IOException {
+    this.segmentHeader = header;
+    this.subInputStream = sis;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+    parseHeader();
+  }
+
+  protected void setParameters(final CX cx, final ArithmeticDecoder arithmeticDecoder, final short grTemplate,
+      final int regionWidth, final int regionHeight, final Bitmap grReference, final int grReferenceDX,
+      final int grReferenceDY, final boolean isTPGRon, final short[] grAtX, final short[] grAtY) {
+
+    if (null != cx) {
+      this.cx = cx;
+    }
+
+    if (null != arithmeticDecoder) {
+      this.arithDecoder = arithmeticDecoder;
+    }
+
+    this.templateID = grTemplate;
+
+    this.regionInfo.setBitmapWidth(regionWidth);
+    this.regionInfo.setBitmapHeight(regionHeight);
+
+    this.referenceBitmap = grReference;
+    this.referenceDX = grReferenceDX;
+    this.referenceDY = grReferenceDY;
+
+    this.isTPGROn = isTPGRon;
+
+    this.grAtX = grAtX;
+    this.grAtY = grAtY;
+
+    this.regionBitmap = null;
+  }
+
+  public RegionSegmentInformation getRegionInfo() {
+    return regionInfo;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/GenericRegion.java b/src/main/java/org/apache/pdfbox/jbig2/segments/GenericRegion.java
new file mode 100644
index 0000000..9c42f04
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/GenericRegion.java
@@ -0,0 +1,906 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.Region;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.CX;
+import org.apache.pdfbox.jbig2.decoder.mmr.MMRDecompressor;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represents a generic region segment.<br>
+ * Parsing is done as described in 7.4.5.<br>
+ * Decoding procedure is done as described in 6.2.5.7 and 7.4.6.4.
+ */
+public class GenericRegion implements Region {
+
+  private final Logger log = LoggerFactory.getLogger(GenericRegion.class);
+
+  private SubInputStream subInputStream;
+  private long dataHeaderOffset;
+  private long dataHeaderLength;
+  private long dataOffset;
+  private long dataLength;
+
+  /** Region segment information field, 7.4.1 */
+  private RegionSegmentInformation regionInfo;
+
+  /** Generic region segment flags, 7.4.6.2 */
+  private boolean useExtTemplates;
+  private boolean isTPGDon;
+  private byte gbTemplate;
+  private boolean isMMREncoded;
+
+  /** Generic region segment AT flags, 7.4.6.3 */
+  private short[] gbAtX;
+  private short[] gbAtY;
+  private boolean[] gbAtOverride;
+
+  /**
+   * If true, AT pixels are not on their nominal location and have to be overridden
+   */
+  private boolean override;
+
+  /** Decoded data as pixel values (use row stride/width to wrap line) */
+  private Bitmap regionBitmap;
+
+  private ArithmeticDecoder arithDecoder;
+  private CX cx;
+
+  private MMRDecompressor mmrDecompressor;
+
+  public GenericRegion() {
+  }
+
+  public GenericRegion(final SubInputStream subInputStream) {
+    this.subInputStream = subInputStream;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+  }
+
+  private void parseHeader() throws IOException, InvalidHeaderValueException {
+    regionInfo.parseHeader();
+
+    /* Bit 5-7 */
+    subInputStream.readBits(3); // Dirty read...
+
+    /* Bit 4 */
+    if (subInputStream.readBit() == 1) {
+      useExtTemplates = true;
+    }
+
+    /* Bit 3 */
+    if (subInputStream.readBit() == 1) {
+      isTPGDon = true;
+    }
+
+    /* Bit 1-2 */
+    gbTemplate = (byte) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 0 */
+    if (subInputStream.readBit() == 1) {
+      isMMREncoded = true;
+    }
+
+    if (!isMMREncoded) {
+      final int amountOfGbAt;
+      if (gbTemplate == 0) {
+        if (useExtTemplates) {
+          amountOfGbAt = 12;
+        } else {
+          amountOfGbAt = 4;
+        }
+      } else {
+        amountOfGbAt = 1;
+      }
+
+      readGbAtPixels(amountOfGbAt);
+    }
+
+    /* Segment data structure */
+    computeSegmentDataStructure();
+
+    this.checkInput();
+  }
+
+  private void readGbAtPixels(final int amountOfGbAt) throws IOException {
+    gbAtX = new short[amountOfGbAt];
+    gbAtY = new short[amountOfGbAt];
+
+    for (int i = 0; i < amountOfGbAt; i++) {
+      gbAtX[i] = subInputStream.readByte();
+      gbAtY[i] = subInputStream.readByte();
+    }
+  }
+
+  private void computeSegmentDataStructure() throws IOException {
+    dataOffset = subInputStream.getStreamPosition();
+    dataHeaderLength = dataOffset - dataHeaderOffset;
+    dataLength = subInputStream.length() - dataHeaderLength;
+  }
+
+  private void checkInput() throws InvalidHeaderValueException {
+    if (isMMREncoded) {
+      if (gbTemplate != 0) {
+        log.info("gbTemplate should contain the value 0");
+      }
+    }
+  }
+
+  /**
+   * The procedure is described in 6.2.5.7, page 17.
+   * 
+   * @returns The decoded {@link Bitmap} of this region.
+   */
+  public Bitmap getRegionBitmap() throws IOException {
+    if (null == regionBitmap) {
+
+      if (isMMREncoded) {
+
+        /*
+         * MMR DECODER CALL
+         */
+        if (null == mmrDecompressor) {
+          mmrDecompressor = new MMRDecompressor(regionInfo.getBitmapWidth(), regionInfo.getBitmapHeight(),
+              new SubInputStream(subInputStream, dataOffset, dataLength));
+        }
+
+        /* 6.2.6 */
+        regionBitmap = mmrDecompressor.uncompress();
+
+      } else {
+
+        /*
+         * ARITHMETIC DECODER PROCEDURE for generic region segments
+         */
+
+        updateOverrideFlags();
+
+        /* 6.2.5.7 - 1) */
+        int ltp = 0;
+
+        if (arithDecoder == null) {
+          arithDecoder = new ArithmeticDecoder(subInputStream);
+        }
+        if (cx == null) {
+          cx = new CX(65536, 1);
+        }
+
+        /* 6.2.5.7 - 2) */
+        regionBitmap = new Bitmap(regionInfo.getBitmapWidth(), regionInfo.getBitmapHeight());
+
+        final int paddedWidth = (regionBitmap.getWidth() + 7) & -8;
+
+        /* 6.2.5.7 - 3 */
+        for (int line = 0; line < regionBitmap.getHeight(); line++) {
+
+          /* 6.2.5.7 - 3 b) */
+          if (isTPGDon) {
+            ltp ^= decodeSLTP();
+          }
+
+          /* 6.2.5.7 - 3 c) */
+          if (ltp == 1) {
+            if (line > 0) {
+              copyLineAbove(line);
+            }
+          } else {
+            /* 3 d) */
+            // NOT USED ATM - If corresponding pixel of SKIP bitmap is 0, set
+            // current pixel to 0. Something like that:
+            // if (useSkip) {
+            // for (int i = 1; i < rowstride; i++) {
+            // if (skip[pixel] == 1) {
+            // gbReg[pixel] = 0;
+            // }
+            // pixel++;
+            // }
+            // } else {
+            decodeLine(line, regionBitmap.getWidth(), regionBitmap.getRowStride(), paddedWidth);
+            // }
+          }
+        }
+      }
+    }
+
+    // if (JBIG2ImageReader.DEBUG)
+    // if (header != null && header.getSegmentNr() == 3)
+    // new Testbild(gbReg.getByteArray(), (int) gbReg.getWidth(), (int) gbReg.getHeight(),
+    // gbReg.getRowStride());
+
+    /* 4 */
+    return regionBitmap;
+  }
+
+  private int decodeSLTP() throws IOException {
+    switch (gbTemplate){
+      case 0 :
+        cx.setIndex(0x9b25);
+        break;
+      case 1 :
+        cx.setIndex(0x795);
+        break;
+      case 2 :
+        cx.setIndex(0xe5);
+        break;
+      case 3 :
+        cx.setIndex(0x195);
+        break;
+    }
+    return arithDecoder.decode(cx);
+  }
+
+  private void decodeLine(final int lineNumber, final int width, final int rowStride, final int paddedWidth)
+      throws IOException {
+    final int byteIndex = regionBitmap.getByteIndex(0, lineNumber);
+    final int idx = byteIndex - rowStride;
+
+    switch (gbTemplate){
+      case 0 :
+        if (!useExtTemplates) {
+          decodeTemplate0a(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
+        } else {
+          decodeTemplate0b(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
+        }
+        break;
+      case 1 :
+        decodeTemplate1(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
+        break;
+      case 2 :
+        decodeTemplate2(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
+        break;
+      case 3 :
+        decodeTemplate3(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
+        break;
+    }
+  }
+
+  /**
+   * Each pixel gets the value from the corresponding pixel of the row above. Line 0 cannot get
+   * copied values (source will be -1, doesn't exist).
+   * 
+   * @param lineNumber - Coordinate of the row that should be set.
+   */
+  private void copyLineAbove(final int lineNumber) {
+    int targetByteIndex = lineNumber * regionBitmap.getRowStride();
+    int sourceByteIndex = targetByteIndex - regionBitmap.getRowStride();
+
+    for (int i = 0; i < regionBitmap.getRowStride(); i++) {
+      // Get the byte that should be copied and put it into Bitmap
+      regionBitmap.setByte(targetByteIndex++, regionBitmap.getByte(sourceByteIndex++));
+    }
+  }
+
+  private void decodeTemplate0a(final int lineNumber, final int width, final int rowStride, final int paddedWidth,
+      int byteIndex, int idx) throws IOException {
+    int context;
+    int overriddenContext = 0;
+
+    int line1 = 0;
+    int line2 = 0;
+
+    if (lineNumber >= 1) {
+      line1 = regionBitmap.getByteAsInteger(idx);
+    }
+
+    if (lineNumber >= 2) {
+      line2 = regionBitmap.getByteAsInteger(idx - rowStride) << 6;
+    }
+
+    context = (line1 & 0xf0) | (line2 & 0x3800);
+
+    int nextByte;
+    for (int x = 0; x < paddedWidth; x = nextByte) {
+      /* 6.2.5.7 3d */
+      byte result = 0;
+      nextByte = x + 8;
+      final int minorWidth = width - x > 8 ? 8 : width - x;
+
+      if (lineNumber > 0) {
+        line1 = (line1 << 8) | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
+      }
+
+      if (lineNumber > 1) {
+        line2 = (line2 << 8) | (nextByte < width ? regionBitmap.getByteAsInteger(idx - rowStride + 1) << 6 : 0);
+      }
+
+      for (int minorX = 0; minorX < minorWidth; minorX++) {
+        final int toShift = 7 - minorX;
+        if (override) {
+          overriddenContext = overrideAtTemplate0a(context, (x + minorX), lineNumber, result, minorX, toShift);
+          cx.setIndex(overriddenContext);
+        } else {
+          cx.setIndex(context);
+        }
+
+        int bit = arithDecoder.decode(cx);
+
+        result |= bit << toShift;
+
+        context = ((context & 0x7bf7) << 1) | bit | ((line1 >> toShift) & 0x10) | ((line2 >> toShift) & 0x800);
+      }
+
+      regionBitmap.setByte(byteIndex++, result);
+      idx++;
+    }
+  }
+
+  private void decodeTemplate0b(final int lineNumber, final int width, final int rowStride, final int paddedWidth,
+      int byteIndex, int idx) throws IOException {
+    int context;
+    int overriddenContext = 0;
+
+    int line1 = 0;
+    int line2 = 0;
+
+    if (lineNumber >= 1) {
+      line1 = regionBitmap.getByteAsInteger(idx);
+    }
+
+    if (lineNumber >= 2) {
+      line2 = regionBitmap.getByteAsInteger(idx - rowStride) << 6;
+    }
+
+    context = (line1 & 0xf0) | (line2 & 0x3800);
+
+    int nextByte;
+    for (int x = 0; x < paddedWidth; x = nextByte) {
+      /* 6.2.5.7 3d */
+      byte result = 0;
+      nextByte = x + 8;
+      final int minorWidth = width - x > 8 ? 8 : width - x;
+
+      if (lineNumber > 0) {
+        line1 = (line1 << 8) | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
+      }
+
+      if (lineNumber > 1) {
+        line2 = (line2 << 8) | (nextByte < width ? regionBitmap.getByteAsInteger(idx - rowStride + 1) << 6 : 0);
+      }
+
+      for (int minorX = 0; minorX < minorWidth; minorX++) {
+        final int toShift = 7 - minorX;
+        if (override) {
+          overriddenContext = overrideAtTemplate0b(context, (x + minorX), lineNumber, result, minorX, toShift);
+          cx.setIndex(overriddenContext);
+        } else {
+          cx.setIndex(context);
+        }
+
+        final int bit = arithDecoder.decode(cx);
+
+        result |= bit << toShift;
+
+        context = ((context & 0x7bf7) << 1) | bit | ((line1 >> toShift) & 0x10) | ((line2 >> toShift) & 0x800);
+      }
+
+      regionBitmap.setByte(byteIndex++, result);
+      idx++;
+    }
+  }
+
+  private void decodeTemplate1(final int lineNumber, int width, final int rowStride, final int paddedWidth,
+      int byteIndex, int idx) throws IOException {
+    int context;
+    int overriddenContext;
+
+    int line1 = 0;
+    int line2 = 0;
+
+    if (lineNumber >= 1) {
+      line1 = regionBitmap.getByteAsInteger(idx);
+    }
+
+    if (lineNumber >= 2) {
+      line2 = regionBitmap.getByteAsInteger(idx - rowStride) << 5;
+    }
+
+    context = ((line1 >> 1) & 0x1f8) | ((line2 >> 1) & 0x1e00);
+
+    int nextByte;
+    for (int x = 0; x < paddedWidth; x = nextByte) {
+      /* 6.2.5.7 3d */
+      byte result = 0;
+      nextByte = x + 8;
+      final int minorWidth = width - x > 8 ? 8 : width - x;
+
+      if (lineNumber >= 1) {
+        line1 = (line1 << 8) | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
+      }
+
+      if (lineNumber >= 2) {
+        line2 = (line2 << 8) | (nextByte < width ? regionBitmap.getByteAsInteger(idx - rowStride + 1) << 5 : 0);
+      }
+
+      for (int minorX = 0; minorX < minorWidth; minorX++) {
+        if (override) {
+          overriddenContext = overrideAtTemplate1(context, x + minorX, lineNumber, result, minorX);
+          cx.setIndex(overriddenContext);
+        } else {
+          cx.setIndex(context);
+        }
+
+        final int bit = arithDecoder.decode(cx);
+
+        result |= bit << 7 - minorX;
+
+        final int toShift = 8 - minorX;
+        context = ((context & 0xefb) << 1) | bit | ((line1 >> toShift) & 0x8) | ((line2 >> toShift) & 0x200);
+      }
+
+      regionBitmap.setByte(byteIndex++, result);
+      idx++;
+    }
+  }
+
+  private void decodeTemplate2(final int lineNumber, final int width, final int rowStride, final int paddedWidth,
+      int byteIndex, int idx) throws IOException {
+    int context;
+    int overriddenContext;
+
+    int line1 = 0;
+    int line2 = 0;
+
+    if (lineNumber >= 1) {
+      line1 = regionBitmap.getByteAsInteger(idx);
+    }
+
+    if (lineNumber >= 2) {
+      line2 = regionBitmap.getByteAsInteger(idx - rowStride) << 4;
+    }
+
+    context = ((line1 >> 3) & 0x7c) | ((line2 >> 3) & 0x380);
+
+    int nextByte;
+    for (int x = 0; x < paddedWidth; x = nextByte) {
+      /* 6.2.5.7 3d */
+      byte result = 0;
+      nextByte = x + 8;
+      final int minorWidth = width - x > 8 ? 8 : width - x;
+
+      if (lineNumber >= 1) {
+        line1 = (line1 << 8) | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
+      }
+
+      if (lineNumber >= 2) {
+        line2 = (line2 << 8) | (nextByte < width ? regionBitmap.getByteAsInteger(idx - rowStride + 1) << 4 : 0);
+      }
+
+      for (int minorX = 0; minorX < minorWidth; minorX++) {
+
+        if (override) {
+          overriddenContext = overrideAtTemplate2(context, x + minorX, lineNumber, result, minorX);
+          cx.setIndex(overriddenContext);
+        } else {
+          cx.setIndex(context);
+        }
+
+        final int bit = arithDecoder.decode(cx);
+
+        result |= bit << (7 - minorX);
+
+        final int toShift = 10 - minorX;
+        context = ((context & 0x1bd) << 1) | bit | ((line1 >> toShift) & 0x4) | ((line2 >> toShift) & 0x80);
+      }
+
+      regionBitmap.setByte(byteIndex++, result);
+      idx++;
+    }
+  }
+
+  private void decodeTemplate3(final int lineNumber, final int width, final int rowStride, final int paddedWidth,
+      int byteIndex, int idx) throws IOException {
+    int context;
+    int overriddenContext;
+
+    int line1 = 0;
+
+    if (lineNumber >= 1) {
+      line1 = regionBitmap.getByteAsInteger(idx);
+    }
+
+    context = (line1 >> 1) & 0x70;
+
+    int nextByte;
+    for (int x = 0; x < paddedWidth; x = nextByte) {
+      /* 6.2.5.7 3d */
+      byte result = 0;
+      nextByte = x + 8;
+      final int minorWidth = width - x > 8 ? 8 : width - x;
+
+      if (lineNumber >= 1) {
+        line1 = (line1 << 8) | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
+      }
+
+      for (int minorX = 0; minorX < minorWidth; minorX++) {
+
+        if (override) {
+          overriddenContext = overrideAtTemplate3(context, x + minorX, lineNumber, result, minorX);
+          cx.setIndex(overriddenContext);
+        } else {
+          cx.setIndex(context);
+        }
+
+        final int bit = arithDecoder.decode(cx);
+
+        result |= bit << (7 - minorX);
+        context = ((context & 0x1f7) << 1) | bit | ((line1 >> (8 - minorX)) & 0x010);
+      }
+
+      regionBitmap.setByte(byteIndex++, result);
+      idx++;
+    }
+  }
+
+  private void updateOverrideFlags() {
+    if (gbAtX == null || gbAtY == null) {
+      log.info("AT pixels not set");
+      return;
+    }
+
+    if (gbAtX.length != gbAtY.length) {
+      log.info("AT pixel inconsistent, amount of x pixels: " + gbAtX.length + ", amount of y pixels:" + gbAtY.length);
+      return;
+    }
+
+    gbAtOverride = new boolean[gbAtX.length];
+
+    switch (gbTemplate){
+      case 0 :
+        if (!useExtTemplates) {
+          if (gbAtX[0] != 3 || gbAtY[0] != -1)
+            setOverrideFlag(0);
+
+          if (gbAtX[1] != -3 || gbAtY[1] != -1)
+            setOverrideFlag(1);
+
+          if (gbAtX[2] != 2 || gbAtY[2] != -2)
+            setOverrideFlag(2);
+
+          if (gbAtX[3] != -2 || gbAtY[3] != -2)
+            setOverrideFlag(3);
+
+        } else {
+          if (gbAtX[0] != -2 || gbAtY[0] != 0)
+            setOverrideFlag(0);
+
+          if (gbAtX[1] != 0 || gbAtY[1] != -2)
+            setOverrideFlag(1);
+
+          if (gbAtX[2] != -2 || gbAtY[2] != -1)
+            setOverrideFlag(2);
+
+          if (gbAtX[3] != -1 || gbAtY[3] != -2)
+            setOverrideFlag(3);
+
+          if (gbAtX[4] != 1 || gbAtY[4] != -2)
+            setOverrideFlag(4);
+
+          if (gbAtX[5] != 2 || gbAtY[5] != -1)
+            setOverrideFlag(5);
+
+          if (gbAtX[6] != -3 || gbAtY[6] != 0)
+            setOverrideFlag(6);
+
+          if (gbAtX[7] != -4 || gbAtY[7] != 0)
+            setOverrideFlag(7);
+
+          if (gbAtX[8] != 2 || gbAtY[8] != -2)
+            setOverrideFlag(8);
+
+          if (gbAtX[9] != 3 || gbAtY[9] != -1)
+            setOverrideFlag(9);
+
+          if (gbAtX[10] != -2 || gbAtY[10] != -2)
+            setOverrideFlag(10);
+
+          if (gbAtX[11] != -3 || gbAtY[11] != -1)
+            setOverrideFlag(11);
+        }
+        break;
+      case 1 :
+        if (gbAtX[0] != 3 || gbAtY[0] != -1)
+          setOverrideFlag(0);
+        break;
+      case 2 :
+        if (gbAtX[0] != 2 || gbAtY[0] != -1)
+          setOverrideFlag(0);
+        break;
+      case 3 :
+        if (gbAtX[0] != 2 || gbAtY[0] != -1)
+          setOverrideFlag(0);
+        break;
+    }
+
+  }
+
+  private void setOverrideFlag(final int index) {
+    gbAtOverride[index] = true;
+    override = true;
+  }
+
+  private int overrideAtTemplate0a(int context, final int x, final int y, final int result, final int minorX,
+      final int toShift) throws IOException {
+    if (gbAtOverride[0]) {
+      context &= 0xffef;
+      if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
+        context |= (result >> (toShift - gbAtX[0]) & 0x1) << 4;
+      else
+        context |= getPixel(x + gbAtX[0], y + gbAtY[0]) << 4;
+    }
+
+    if (gbAtOverride[1]) {
+      context &= 0xfbff;
+      if (gbAtY[1] == 0 && gbAtX[1] >= -minorX)
+        context |= (result >> (toShift - gbAtX[1]) & 0x1) << 10;
+      else
+        context |= getPixel(x + gbAtX[1], y + gbAtY[1]) << 10;
+    }
+
+    if (gbAtOverride[2]) {
+      context &= 0xf7ff;
+      if (gbAtY[2] == 0 && gbAtX[2] >= -minorX)
+        context |= (result >> (toShift - gbAtX[2]) & 0x1) << 11;
+      else
+        context |= getPixel(x + gbAtX[2], y + gbAtY[2]) << 11;
+    }
+
+    if (gbAtOverride[3]) {
+      context &= 0x7fff;
+      if (gbAtY[3] == 0 && gbAtX[3] >= -minorX)
+        context |= (result >> (toShift - gbAtX[3]) & 0x1) << 15;
+      else
+        context |= getPixel(x + gbAtX[3], y + gbAtY[3]) << 15;
+    }
+    return context;
+  }
+
+  private int overrideAtTemplate0b(int context, final int x, final int y, final int result, final int minorX,
+      final int toShift) throws IOException {
+    if (gbAtOverride[0]) {
+      context &= 0xfffd;
+      if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
+        context |= (result >> (toShift - gbAtX[0]) & 0x1) << 1;
+      else
+        context |= getPixel(x + gbAtX[0], y + gbAtY[0]) << 1;
+    }
+
+    if (gbAtOverride[1]) {
+      context &= 0xdfff;
+      if (gbAtY[1] == 0 && gbAtX[1] >= -minorX)
+        context |= (result >> (toShift - gbAtX[1]) & 0x1) << 13;
+      else
+        context |= getPixel(x + gbAtX[1], y + gbAtY[1]) << 13;
+    }
+    if (gbAtOverride[2]) {
+      context &= 0xfdff;
+      if (gbAtY[2] == 0 && gbAtX[2] >= -minorX)
+        context |= (result >> (toShift - gbAtX[2]) & 0x1) << 9;
+      else
+        context |= getPixel(x + gbAtX[2], y + gbAtY[2]) << 9;
+    }
+    if (gbAtOverride[3]) {
+      context &= 0xbfff;
+      if (gbAtY[3] == 0 && gbAtX[3] >= -minorX)
+        context |= (result >> (toShift - gbAtX[3]) & 0x1) << 14;
+      else
+        context |= getPixel(x + gbAtX[3], y + gbAtY[3]) << 14;
+    }
+    if (gbAtOverride[4]) {
+      context &= 0xefff;
+      if (gbAtY[4] == 0 && gbAtX[4] >= -minorX)
+        context |= (result >> (toShift - gbAtX[4]) & 0x1) << 12;
+      else
+        context |= getPixel(x + gbAtX[4], y + gbAtY[4]) << 12;
+    }
+    if (gbAtOverride[5]) {
+      context &= 0xffdf;
+      if (gbAtY[5] == 0 && gbAtX[5] >= -minorX)
+        context |= (result >> (toShift - gbAtX[5]) & 0x1) << 5;
+      else
+        context |= getPixel(x + gbAtX[5], y + gbAtY[5]) << 5;
+    }
+    if (gbAtOverride[6]) {
+      context &= 0xfffb;
+      if (gbAtY[6] == 0 && gbAtX[6] >= -minorX)
+        context |= (result >> (toShift - gbAtX[6]) & 0x1) << 2;
+      else
+        context |= getPixel(x + gbAtX[6], y + gbAtY[6]) << 2;
+    }
+    if (gbAtOverride[7]) {
+      context &= 0xfff7;
+      if (gbAtY[7] == 0 && gbAtX[7] >= -minorX)
+        context |= (result >> (toShift - gbAtX[7]) & 0x1) << 3;
+      else
+        context |= getPixel(x + gbAtX[7], y + gbAtY[7]) << 3;
+    }
+    if (gbAtOverride[8]) {
+      context &= 0xf7ff;
+      if (gbAtY[8] == 0 && gbAtX[8] >= -minorX)
+        context |= (result >> (toShift - gbAtX[8]) & 0x1) << 11;
+      else
+        context |= getPixel(x + gbAtX[8], y + gbAtY[8]) << 11;
+    }
+    if (gbAtOverride[9]) {
+      context &= 0xffef;
+      if (gbAtY[9] == 0 && gbAtX[9] >= -minorX)
+        context |= (result >> (toShift - gbAtX[9]) & 0x1) << 4;
+      else
+        context |= getPixel(x + gbAtX[9], y + gbAtY[9]) << 4;
+    }
+    if (gbAtOverride[10]) {
+      context &= 0x7fff;
+      if (gbAtY[10] == 0 && gbAtX[10] >= -minorX)
+        context |= (result >> (toShift - gbAtX[10]) & 0x1) << 15;
+      else
+        context |= getPixel(x + gbAtX[10], y + gbAtY[10]) << 15;
+    }
+    if (gbAtOverride[11]) {
+      context &= 0xfdff;
+      if (gbAtY[11] == 0 && gbAtX[11] >= -minorX)
+        context |= (result >> (toShift - gbAtX[11]) & 0x1) << 10;
+      else
+        context |= getPixel(x + gbAtX[11], y + gbAtY[11]) << 10;
+    }
+
+    return context;
+  }
+
+  private int overrideAtTemplate1(int context, final int x, final int y, final int result, final int minorX)
+      throws IOException {
+    context &= 0x1ff7;
+    if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
+      return (context | (result >> (7 - (minorX + gbAtX[0])) & 0x1) << 3);
+    else
+      return (context | getPixel(x + gbAtX[0], y + gbAtY[0]) << 3);
+  }
+
+  private int overrideAtTemplate2(int context, final int x, final int y, final int result, final int minorX)
+      throws IOException {
+    context &= 0x3fb;
+    if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
+      return (context | (result >> (7 - (minorX + gbAtX[0])) & 0x1) << 2);
+    else
+      return (context | getPixel(x + gbAtX[0], y + gbAtY[0]) << 2);
+  }
+
+  private int overrideAtTemplate3(int context, final int x, final int y, final int result, final int minorX)
+      throws IOException {
+    context &= 0x3ef;
+    if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
+      return (context | (result >> (7 - (minorX + gbAtX[0])) & 0x1) << 4);
+    else
+      return (context | getPixel(x + gbAtX[0], y + gbAtY[0]) << 4);
+  }
+
+  private byte getPixel(final int x, final int y) throws IOException {
+    if (x < 0 || x >= regionBitmap.getWidth())
+      return 0;
+
+    if (y < 0 || y >= regionBitmap.getHeight())
+      return 0;
+
+    return regionBitmap.getPixel(x, y);
+  }
+
+  /**
+   * Used by {@link SymbolDictionary}.
+   */
+  protected void setParameters(final boolean isMMREncoded, final long dataOffset, final long dataLength, final int gbh,
+      final int gbw) {
+    this.isMMREncoded = isMMREncoded;
+    this.dataOffset = dataOffset;
+    this.dataLength = dataLength;
+    this.regionInfo.setBitmapHeight(gbh);
+    this.regionInfo.setBitmapWidth(gbw);
+
+    this.mmrDecompressor = null;
+    resetBitmap();
+  }
+
+  /**
+   * Used by {@link SymbolDictionary}.
+   */
+  protected void setParameters(final boolean isMMREncoded, final byte sdTemplate, final boolean isTPGDon,
+      final boolean useSkip, final short[] sdATX, final short[] sdATY, final int symWidth, final int hcHeight,
+      final CX cx, final ArithmeticDecoder arithmeticDecoder) {
+    this.isMMREncoded = isMMREncoded;
+    this.gbTemplate = sdTemplate;
+    this.isTPGDon = isTPGDon;
+    this.gbAtX = sdATX;
+    this.gbAtY = sdATY;
+    this.regionInfo.setBitmapWidth(symWidth);
+    this.regionInfo.setBitmapHeight(hcHeight);
+    if (null != cx)
+      this.cx = cx;
+    if (null != arithmeticDecoder)
+      this.arithDecoder = arithmeticDecoder;
+
+    this.mmrDecompressor = null;
+    resetBitmap();
+  }
+
+  /**
+   * Used by {@link PatternDictionary} and {@link HalftoneRegion}.
+   */
+  protected void setParameters(final boolean isMMREncoded, final long dataOffset, final long dataLength, final int gbh,
+      final int gbw, final byte gbTemplate, final boolean isTPGDon, final boolean useSkip, final short[] gbAtX,
+      final short[] gbAtY) {
+    this.dataOffset = dataOffset;
+    this.dataLength = dataLength;
+
+    this.regionInfo = new RegionSegmentInformation();
+    this.regionInfo.setBitmapHeight(gbh);
+    this.regionInfo.setBitmapWidth(gbw);
+    this.gbTemplate = gbTemplate;
+
+    this.isMMREncoded = isMMREncoded;
+    this.isTPGDon = isTPGDon;
+    this.gbAtX = gbAtX;
+    this.gbAtY = gbAtY;
+  }
+
+  /**
+   * Simply sets the memory-critical bitmap of this region to {@code null}.
+   */
+  protected void resetBitmap() {
+    this.regionBitmap = null;
+  }
+
+  public void init(final SegmentHeader header, final SubInputStream sis) throws InvalidHeaderValueException,
+      IOException {
+    this.subInputStream = sis;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+    parseHeader();
+  }
+
+  public RegionSegmentInformation getRegionInfo() {
+    return regionInfo;
+  }
+
+  protected boolean useExtTemplates() {
+    return useExtTemplates;
+  }
+
+  protected boolean isTPGDon() {
+    return isTPGDon;
+  }
+
+  protected byte getGbTemplate() {
+    return gbTemplate;
+  }
+
+  protected boolean isMMREncoded() {
+    return isMMREncoded;
+  }
+
+  protected short[] getGbAtX() {
+    return gbAtX;
+  }
+
+  protected short[] getGbAtY() {
+    return gbAtY;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/HalftoneRegion.java b/src/main/java/org/apache/pdfbox/jbig2/segments/HalftoneRegion.java
new file mode 100644
index 0000000..384f09f
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/HalftoneRegion.java
@@ -0,0 +1,406 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.Region;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represents the data of segment type "Halftone region". Parsing is described in 7.4.5,
+ * page 67. Decoding procedure in 6.6.5 and 7.4.5.2.
+ */
+public class HalftoneRegion implements Region {
+
+  private final Logger log = LoggerFactory.getLogger(HalftoneRegion.class);
+
+  private SubInputStream subInputStream;
+  private SegmentHeader segmentHeader;
+  private long dataHeaderOffset;
+  private long dataHeaderLength;
+  private long dataOffset;
+  private long dataLength;
+
+  /** Region segment information field, 7.4.1 */
+  private RegionSegmentInformation regionInfo;
+
+  /** Halftone segment information field, 7.4.5.1.1 */
+  private byte hDefaultPixel;
+  private CombinationOperator hCombinationOperator;
+  private boolean hSkipEnabled;
+  private byte hTemplate;
+  private boolean isMMREncoded;
+
+  /** Halftone grid position and size, 7.4.5.1.2 */
+  /** Width of the gray-scale image, 7.4.5.1.2.1 */
+  private int hGridWidth;
+  /** Height of the gray-scale image, 7.4.5.1.2.2 */
+  private int hGridHeight;
+  /** Horizontal offset of the grid, 7.4.5.1.2.3 */
+  private int hGridX;
+  /** Vertical offset of the grid, 7.4.5.1.2.4 */
+  private int hGridY;
+
+  /** Halftone grid vector, 7.4.5.1.3 */
+  /** Horizontal coordinate of the halftone grid vector, 7.4.5.1.3.1 */
+  private int hRegionX;
+  /** Vertical coordinate of the halftone grod vector, 7.4.5.1.3.2 */
+  private int hRegionY;
+
+  /** Decoded data */
+  private Bitmap halftoneRegionBitmap;
+
+  /**
+   * Previously decoded data from other regions or dictionaries, stored to use as patterns in this
+   * region.
+   */
+  private ArrayList<Bitmap> patterns;
+
+  public HalftoneRegion() {
+  }
+
+  public HalftoneRegion(final SubInputStream subInputStream) {
+    this.subInputStream = subInputStream;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+  }
+
+  public HalftoneRegion(final SubInputStream subInputStream, final SegmentHeader segmentHeader) {
+    this.subInputStream = subInputStream;
+    this.segmentHeader = segmentHeader;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+  }
+
+  private void parseHeader() throws IOException, InvalidHeaderValueException {
+    regionInfo.parseHeader();
+
+    /* Bit 7 */
+    hDefaultPixel = (byte) subInputStream.readBit();
+
+    /* Bit 4-6 */
+    hCombinationOperator = CombinationOperator.translateOperatorCodeToEnum((short) (subInputStream.readBits(3) & 0xf));
+
+    /* Bit 3 */
+    if (subInputStream.readBit() == 1) {
+      hSkipEnabled = true;
+    }
+
+    /* Bit 1-2 */
+    hTemplate = (byte) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 0 */
+    if (subInputStream.readBit() == 1) {
+      isMMREncoded = true;
+    }
+
+    hGridWidth = (int) (subInputStream.readBits(32) & 0xffffffff);
+    hGridHeight = (int) (subInputStream.readBits(32) & 0xffffffff);
+
+    hGridX = (int) subInputStream.readBits(32);
+    hGridY = (int) subInputStream.readBits(32);
+
+    hRegionX = (int) subInputStream.readBits(16) & 0xffff;
+    hRegionY = (int) subInputStream.readBits(16) & 0xffff;
+
+    /* Segment data structure */
+    computeSegmentDataStructure();
+
+    this.checkInput();
+  }
+
+  private void computeSegmentDataStructure() throws IOException {
+    dataOffset = subInputStream.getStreamPosition();
+    dataHeaderLength = dataOffset - dataHeaderOffset;
+    dataLength = subInputStream.length() - dataHeaderLength;
+  }
+
+  private void checkInput() throws InvalidHeaderValueException {
+    if (isMMREncoded) {
+      if (hTemplate != 0) {
+        log.info("hTemplate = " + hTemplate + " (should contain the value 0)");
+      }
+
+      if (hSkipEnabled) {
+        log.info("hSkipEnabled 0 " + hSkipEnabled + " (should contain the value false)");
+      }
+    }
+  }
+
+  /**
+   * The procedure is described in JBIG2 ISO standard, 6.6.5.
+   * 
+   * @returns The decoded {@link Bitmap} of this region.
+   */
+  public Bitmap getRegionBitmap() throws IOException, InvalidHeaderValueException {
+    if (null == halftoneRegionBitmap) {
+
+      /* 6.6.5, page 40 */
+      /* 1) */
+      halftoneRegionBitmap = new Bitmap(regionInfo.getBitmapWidth(), regionInfo.getBitmapHeight());
+
+      if (patterns == null) {
+        patterns = getPatterns();
+      }
+
+      if (hDefaultPixel == 1) {
+        Arrays.fill(halftoneRegionBitmap.getByteArray(), (byte) 0xff);
+      }
+
+      /* 2) */
+      /*
+       * 6.6.5.1 Computing hSkip - At the moment SKIP is not used... we are not able to test it.
+       */
+      // Bitmap hSkip;
+      // if (hSkipEnabled) {
+      // int hPatternHeight = (int) hPats.get(0).getHeight();
+      // int hPatternWidth = (int) hPats.get(0).getWidth();
+      // TODO: Set or get pattern width and height from referred
+      // pattern segments. The method is called like this:
+      // hSkip = computeHSkip(hPatternHeight, hPatternWidth);
+      // }
+
+      /* 3) */
+      final int bitsPerValue = (int) Math.ceil(Math.log(patterns.size()) / Math.log(2));
+
+      /* 4) */
+      final int[][] grayScaleValues = grayScaleDecoding(bitsPerValue);
+
+      /* 5), rendering the pattern, described in 6.6.5.2 */
+      renderPattern(grayScaleValues);
+    }
+    /* 6) */
+    return halftoneRegionBitmap;
+  }
+
+  /**
+   * This method draws the pattern into the region bitmap ({@code htReg}), as described in 6.6.5.2,
+   * page 42
+   */
+  private void renderPattern(final int[][] grayScaleValues) {
+    int x = 0, y = 0;
+
+    // 1)
+    for (int m = 0; m < hGridHeight; m++) {
+      // a)
+      for (int n = 0; n < hGridWidth; n++) {
+        // i)
+        x = computeX(m, n);
+        y = computeY(m, n);
+
+        // ii)
+        final Bitmap patternBitmap = patterns.get(grayScaleValues[m][n]);
+        Bitmaps.blit(patternBitmap, halftoneRegionBitmap, (x + hGridX), (y + hGridY), hCombinationOperator);
+      }
+    }
+  }
+
+  /**
+   * @throws IOException
+   * @throws InvalidHeaderValueException
+   * 
+   */
+  private ArrayList<Bitmap> getPatterns() throws InvalidHeaderValueException, IOException {
+    final ArrayList<Bitmap> patterns = new ArrayList<Bitmap>();
+
+    for (SegmentHeader s : segmentHeader.getRtSegments()) {
+      final PatternDictionary patternDictionary = (PatternDictionary) s.getSegmentData();
+      patterns.addAll(patternDictionary.getDictionary());
+    }
+
+    return patterns;
+  }
+
+  /**
+   * Gray-scale image decoding procedure is special for halftone region decoding and is described in
+   * Annex C.5 on page 98.
+   */
+  private int[][] grayScaleDecoding(final int bitsPerValue) throws IOException {
+
+    short[] gbAtX = null;
+    short[] gbAtY = null;
+
+    if (!isMMREncoded) {
+      gbAtX = new short[4];
+      gbAtY = new short[4];
+      // Set AT pixel values
+      if (hTemplate <= 1)
+        gbAtX[0] = 3;
+      else if (hTemplate >= 2)
+        gbAtX[0] = 2;
+
+      gbAtY[0] = -1;
+      gbAtX[1] = -3;
+      gbAtY[1] = -1;
+      gbAtX[2] = 2;
+      gbAtY[2] = -2;
+      gbAtX[3] = -2;
+      gbAtY[3] = -2;
+    }
+
+    Bitmap[] grayScalePlanes = new Bitmap[bitsPerValue];
+
+    // 1)
+    GenericRegion genericRegion = new GenericRegion(subInputStream);
+    genericRegion.setParameters(isMMREncoded, dataOffset, dataLength, hGridHeight, hGridWidth, hTemplate, false,
+        hSkipEnabled, gbAtX, gbAtY);
+
+    // 2)
+    int j = bitsPerValue - 1;
+
+    grayScalePlanes[j] = genericRegion.getRegionBitmap();
+
+    while (j > 0) {
+      j--;
+      genericRegion.resetBitmap();
+      // 3) a)
+      grayScalePlanes[j] = genericRegion.getRegionBitmap();
+      // 3) b)
+      grayScalePlanes = combineGrayScalePlanes(grayScalePlanes, j);
+    }
+
+    // 4)
+    return computeGrayScaleValues(grayScalePlanes, bitsPerValue);
+  }
+
+  private Bitmap[] combineGrayScalePlanes(Bitmap[] grayScalePlanes, int j) {
+    int byteIndex = 0;
+    for (int y = 0; y < grayScalePlanes[j].getHeight(); y++) {
+
+      for (int x = 0; x < grayScalePlanes[j].getWidth(); x += 8) {
+        final byte newValue = grayScalePlanes[j + 1].getByte(byteIndex);
+        final byte oldValue = grayScalePlanes[j].getByte(byteIndex);
+
+        grayScalePlanes[j].setByte(byteIndex++, Bitmaps.combineBytes(oldValue, newValue, CombinationOperator.XOR));
+      }
+    }
+    return grayScalePlanes;
+  }
+
+  private int[][] computeGrayScaleValues(final Bitmap[] grayScalePlanes, final int bitsPerValue) {
+    // Gray-scale decoding procedure, page 98
+    final int[][] grayScaleValues = new int[hGridHeight][hGridWidth];
+
+    // 4)
+    for (int y = 0; y < hGridHeight; y++) {
+      for (int x = 0; x < hGridWidth; x += 8) {
+        final int minorWidth = hGridWidth - x > 8 ? 8 : hGridWidth - x;
+        int byteIndex = grayScalePlanes[0].getByteIndex(x, y);
+
+        for (int minorX = 0; minorX < minorWidth; minorX++) {
+          final int i = minorX + x;
+          grayScaleValues[y][i] = 0;
+
+          for (int j = 0; j < bitsPerValue; j++) {
+            grayScaleValues[y][i] += ((grayScalePlanes[j].getByte(byteIndex) >> (7 - i & 7)) & 1) * (1 << j);
+          }
+        }
+      }
+    }
+    return grayScaleValues;
+  }
+
+  private int computeX(final int m, final int n) {
+    return shiftAndFill((hGridX + m * hRegionY + n * hRegionX));
+  }
+
+  private int computeY(final int m, final int n) {
+    return shiftAndFill((hGridY + m * hRegionX - n * hRegionY));
+  }
+
+  private int shiftAndFill(int value) {
+    // shift value by 8 and let the leftmost 8 bits be 0
+    value >>= 8;
+
+    if (value < 0) {
+      // fill the leftmost 8 bits with 1
+      final int bitPosition = (int) (Math.log(Integer.highestOneBit(value)) / Math.log(2));
+
+      for (int i = 1; i < 31 - bitPosition; i++) {
+        // bit flip
+        value |= 1 << (31 - i);
+      }
+    }
+
+    return value;
+  }
+
+  public void init(final SegmentHeader header, final SubInputStream sis) throws InvalidHeaderValueException,
+      IOException {
+    this.segmentHeader = header;
+    this.subInputStream = sis;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+    parseHeader();
+  }
+
+  public CombinationOperator getCombinationOperator() {
+    return hCombinationOperator;
+  }
+
+  public RegionSegmentInformation getRegionInfo() {
+    return regionInfo;
+  }
+
+  protected byte getHTemplate() {
+    return hTemplate;
+  }
+
+  protected boolean isHSkipEnabled() {
+    return hSkipEnabled;
+  }
+
+  protected boolean isMMREncoded() {
+    return isMMREncoded;
+  }
+
+  protected int getHGridWidth() {
+    return hGridWidth;
+  }
+
+  protected int getHGridHeight() {
+    return hGridHeight;
+  }
+
+  protected int getHGridX() {
+    return hGridX;
+  }
+
+  protected int getHGridY() {
+    return hGridY;
+  }
+
+  protected int getHRegionX() {
+    return hRegionX;
+  }
+
+  protected int getHRegionY() {
+    return hRegionY;
+  }
+
+  protected byte getHDefaultPixel() {
+    return hDefaultPixel;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/PageInformation.java b/src/main/java/org/apache/pdfbox/jbig2/segments/PageInformation.java
new file mode 100644
index 0000000..513dcca
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/PageInformation.java
@@ -0,0 +1,226 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.jbig2.SegmentData;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represents the segment type "Page information", 7.4.8 (page 73).
+ */
+public class PageInformation implements SegmentData {
+
+  private final Logger log = LoggerFactory.getLogger(PageInformation.class);
+
+  private SubInputStream subInputStream;
+
+  /** Page bitmap width, four byte, 7.4.8.1 */
+  private int bitmapWidth;
+
+  /** Page bitmap height, four byte, 7.4.8.2 */
+  private int bitmapHeight;
+
+  /** Page X resolution, four byte, 7.4.8.3 */
+  private int resolutionX;
+
+  /** Page Y resolution, four byte, 7.4.8.4 */
+  private int resolutionY;
+
+  /** Page segment flags, one byte, 7.4.8.5 */
+  private boolean combinationOperatorOverrideAllowed;
+  private CombinationOperator combinationOperator;
+  private boolean requiresAuxiliaryBuffer;
+  private short defaultPixelValue;
+  private boolean mightContainRefinements;
+  private boolean isLossless;
+
+  /** Page striping information, two byte, 7.4.8.6 */
+  private boolean isStriped;
+  private short maxStripeSize;
+
+  private void parseHeader() throws IOException, InvalidHeaderValueException {
+
+    readWidthAndHeight();
+    readResolution();
+
+    /* Bit 7 */
+    subInputStream.readBit(); // dirty read
+
+    /* Bit 6 */
+    readCombinationOperatorOverrideAllowed();
+
+    /* Bit 5 */
+    readRequiresAuxiliaryBuffer();
+
+    /* Bit 3-4 */
+    readCombinationOperator();
+
+    /* Bit 2 */
+    readDefaultPixelvalue();
+
+    /* Bit 1 */
+    readContainsRefinement();
+
+    /* Bit 0 */
+    readIsLossless();
+
+    /* Bit 15 */
+    readIsStriped();
+
+    /* Bit 0-14 */
+    readMaxStripeSize();
+
+    this.checkInput();
+
+  }
+
+  private void readResolution() throws IOException {
+    resolutionX = (int) subInputStream.readBits(32) & 0xffffffff;
+    resolutionY = (int) subInputStream.readBits(32) & 0xffffffff;
+  }
+
+  private void checkInput() throws InvalidHeaderValueException {
+    if (bitmapHeight == 0xffffffffL)
+      if (!isStriped)
+        log.info("isStriped should contaion the value true");
+  }
+
+  private void readCombinationOperatorOverrideAllowed() throws IOException {
+    /* Bit 6 */
+    if (subInputStream.readBit() == 1) {
+      combinationOperatorOverrideAllowed = true;
+    }
+  }
+
+  private void readRequiresAuxiliaryBuffer() throws IOException {
+    /* Bit 5 */
+    if (subInputStream.readBit() == 1) {
+      requiresAuxiliaryBuffer = true;
+    }
+  }
+
+  private void readCombinationOperator() throws IOException {
+    /* Bit 3-4 */
+    combinationOperator = CombinationOperator.translateOperatorCodeToEnum((short) (subInputStream.readBits(2) & 0xf));
+  }
+
+  private void readDefaultPixelvalue() throws IOException {
+    /* Bit 2 */
+    defaultPixelValue = (short) subInputStream.readBit();
+  }
+
+  private void readContainsRefinement() throws IOException {
+    /* Bit 1 */
+    if (subInputStream.readBit() == 1) {
+      mightContainRefinements = true;
+    }
+  }
+
+  private void readIsLossless() throws IOException {
+    /* Bit 0 */
+    if (subInputStream.readBit() == 1) {
+      isLossless = true;
+    }
+  }
+
+  private void readIsStriped() throws IOException {
+    /* Bit 15 */
+    if (subInputStream.readBit() == 1) {
+      isStriped = true;
+    }
+  }
+
+  private void readMaxStripeSize() throws IOException {
+    /* Bit 0-14 */
+    maxStripeSize = (short) (subInputStream.readBits(15) & 0xffff);
+  }
+
+  private void readWidthAndHeight() throws IOException {
+    bitmapWidth = (int) subInputStream.readBits(32); // & 0xffffffff;
+    bitmapHeight = (int) subInputStream.readBits(32); // & 0xffffffff;
+  }
+
+  public void init(final SegmentHeader header, final SubInputStream sis) throws InvalidHeaderValueException, IOException {
+    subInputStream = sis;
+
+    parseHeader();
+  }
+
+  public int getWidth() {
+    return bitmapWidth;
+  }
+
+  public int getHeight() {
+    return bitmapHeight;
+  }
+
+  public int getResolutionX() {
+    return resolutionX;
+  }
+
+  public int getResolutionY() {
+    return resolutionY;
+  }
+
+  public short getDefaultPixelValue() {
+    return defaultPixelValue;
+  }
+
+  public boolean isCombinationOperatorOverrideAllowed() {
+    return combinationOperatorOverrideAllowed;
+  }
+
+  public CombinationOperator getCombinationOperator() {
+    return combinationOperator;
+  }
+
+  public boolean isStriped() {
+    return isStriped;
+  }
+
+  public short getMaxStripeSize() {
+    return maxStripeSize;
+  }
+
+  public boolean isAuxiliaryBufferRequired() {
+    return requiresAuxiliaryBuffer;
+  }
+
+  public boolean mightContainRefinements() {
+    return mightContainRefinements;
+  }
+
+  public boolean isLossless() {
+    return isLossless;
+  }
+
+  protected int getBitmapWidth() {
+    return bitmapWidth;
+  }
+
+  protected int getBitmapHeight() {
+    return bitmapHeight;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/PatternDictionary.java b/src/main/java/org/apache/pdfbox/jbig2/segments/PatternDictionary.java
new file mode 100644
index 0000000..160d8fa
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/PatternDictionary.java
@@ -0,0 +1,220 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.awt.Rectangle;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.Dictionary;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represents the segment type "Pattern dictionary", 7.4.4.
+ */
+public class PatternDictionary implements Dictionary {
+
+  private final Logger log = LoggerFactory.getLogger(PatternDictionary.class);
+
+  private SubInputStream subInputStream;
+
+  /** Segment data structure (only necessary if MMR is used) */
+  private long dataHeaderOffset;
+  private long dataHeaderLength;
+  private long dataOffset;
+  private long dataLength;
+
+  private short[] gbAtX = null;
+  private short[] gbAtY = null;
+
+  /** Pattern dictionary flags, 7.4.4.1.1 */
+  private boolean isMMREncoded;
+  private byte hdTemplate;
+
+  /** Width of the patterns in the pattern dictionary, 7.4.4.1.2 */
+  private short hdpWidth;
+
+  /** Height of the patterns in the pattern dictionary, 7.4.4.1.3 */
+  private short hdpHeight;
+
+  /** Decoded bitmaps, stored to be used by segments, that refer to it */
+  private ArrayList<Bitmap> patterns;
+
+  /**
+   * Largest gray-scale value, 7.4.4.1.4
+   * 
+   * Value: one less than the number of patterns defined in this pattern dictionary
+   */
+  private int grayMax;
+
+  private void parseHeader() throws IOException, InvalidHeaderValueException {
+    /* Bit 3-7 */
+    subInputStream.readBits(5); // Dirty read ...
+
+    /* Bit 1-2 */
+    readTemplate();
+
+    /* Bit 0 */
+    readIsMMREncoded();
+
+    readPatternWidthAndHeight();
+
+    readGrayMax();
+
+    /* Segment data structure */
+    computeSegmentDataStructure();
+
+    this.checkInput();
+  }
+
+  private void readTemplate() throws IOException {
+    /* Bit 1-2 */
+    hdTemplate = (byte) subInputStream.readBits(2);
+  }
+
+  private void readIsMMREncoded() throws IOException {
+    /* Bit 0 */
+    if (subInputStream.readBit() == 1) {
+      isMMREncoded = true;
+    }
+  }
+
+  private void readPatternWidthAndHeight() throws IOException {
+    hdpWidth = subInputStream.readByte();
+    hdpHeight = subInputStream.readByte();
+  }
+
+  private void readGrayMax() throws IOException {
+    grayMax = (int) (subInputStream.readBits(32) & 0xffffffff);
+  }
+
+  private void computeSegmentDataStructure() throws IOException {
+    dataOffset = subInputStream.getStreamPosition();
+    dataHeaderLength = dataOffset - dataHeaderOffset;
+    dataLength = subInputStream.length() - dataHeaderLength;
+  }
+
+  private void checkInput() throws InvalidHeaderValueException {
+    if (hdpHeight < 1 || hdpWidth < 1) {
+      throw new InvalidHeaderValueException("Width/Heigth must be greater than zero.");
+    }
+
+    if (isMMREncoded) {
+      if (hdTemplate != 0) {
+        log.info("hdTemplate should contain the value 0");
+      }
+    }
+  }
+
+  /**
+   * This method decodes a pattern dictionary segment and returns an array of {@link Bitmap} s. Each
+   * of this {@link Bitmap}s is a pattern.<br>
+   * The procedure is described in 6.7.5 (page 43).
+   * 
+   * @return An array of {@link Bitmap}s as result of the decoding procedure.
+   */
+  public ArrayList<Bitmap> getDictionary() throws IOException, InvalidHeaderValueException {
+    if (null == patterns) {
+
+      if (!isMMREncoded) {
+        setGbAtPixels();
+      }
+
+      // 2)
+      final GenericRegion genericRegion = new GenericRegion(subInputStream);
+      genericRegion.setParameters(isMMREncoded, dataOffset, dataLength, hdpHeight, (grayMax + 1) * hdpWidth,
+          hdTemplate, false, false, gbAtX, gbAtY);
+
+      final Bitmap collectiveBitmap = genericRegion.getRegionBitmap();
+
+      // 4)
+      extractPatterns(collectiveBitmap);
+    }
+
+    return patterns;
+  }
+
+  private void extractPatterns(Bitmap collectiveBitmap) {
+    // 3)
+    int gray = 0;
+    patterns = new ArrayList<Bitmap>(grayMax + 1);
+
+    // 4)
+    while (gray <= grayMax) {
+      // 4) a) Retrieve a pattern bitmap by extracting it out of the collective bitmap
+      final Rectangle roi = new Rectangle(hdpWidth * gray, 0, hdpWidth, hdpHeight);
+      final Bitmap patternBitmap = Bitmaps.extract(roi, collectiveBitmap);
+      patterns.add(patternBitmap);
+
+      // 4) b)
+      gray++;
+    }
+  }
+
+  private void setGbAtPixels() {
+    if (hdTemplate == 0) {
+      gbAtX = new short[4];
+      gbAtY = new short[4];
+      gbAtX[0] = (short) -hdpWidth;
+      gbAtY[0] = 0;
+      gbAtX[1] = -3;
+      gbAtY[1] = -1;
+      gbAtX[2] = 2;
+      gbAtY[2] = -2;
+      gbAtX[3] = -2;
+      gbAtY[3] = -2;
+
+    } else {
+      gbAtX = new short[1];
+      gbAtY = new short[1];
+      gbAtX[0] = (short) -hdpWidth;
+      gbAtY[0] = 0;
+    }
+  }
+
+  public void init(SegmentHeader header, SubInputStream sis) throws InvalidHeaderValueException, IOException {
+    this.subInputStream = sis;
+    parseHeader();
+  }
+
+  protected boolean isMMREncoded() {
+    return isMMREncoded;
+  }
+
+  protected byte getHdTemplate() {
+    return hdTemplate;
+  }
+
+  protected short getHdpWidth() {
+    return hdpWidth;
+  }
+
+  protected short getHdpHeight() {
+    return hdpHeight;
+  }
+
+  protected int getGrayMax() {
+    return grayMax;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/Profiles.java b/src/main/java/org/apache/pdfbox/jbig2/segments/Profiles.java
new file mode 100644
index 0000000..e5eee40
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/Profiles.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import org.apache.pdfbox.jbig2.SegmentData;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+
+/**
+ * <b>TODO:</b> This class is not implemented yet and empty. Wait for use cases.
+ */
+public class Profiles implements SegmentData {
+
+  public void init(SegmentHeader header, SubInputStream sis) {
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/RegionSegmentInformation.java b/src/main/java/org/apache/pdfbox/jbig2/segments/RegionSegmentInformation.java
new file mode 100644
index 0000000..6cbd9a2
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/RegionSegmentInformation.java
@@ -0,0 +1,107 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.jbig2.SegmentData;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+
+/**
+ * This class represents the "Region segment information" field, 7.4.1 (page 50). <br>
+ * Every region segment data starts with this part.
+ */
+public class RegionSegmentInformation implements SegmentData {
+
+  private SubInputStream subInputStream;
+
+  /** Region segment bitmap width, 7.4.1.1 */
+  private int bitmapWidth;
+
+  /** Region segment bitmap height, 7.4.1.2 */
+  private int bitmapHeight;
+
+  /** Region segment bitmap X location, 7.4.1.3 */
+  private int xLocation;
+
+  /** Region segment bitmap Y location, 7.4.1.4 */
+  private int yLocation;
+
+  /** Region segment flags, 7.4.1.5 */
+  private CombinationOperator combinationOperator;
+
+  public RegionSegmentInformation(SubInputStream subInputStream) {
+    this.subInputStream = subInputStream;
+  }
+
+  public RegionSegmentInformation() {
+  }
+
+  public void parseHeader() throws IOException {
+    this.bitmapWidth = ((int) (subInputStream.readBits(32) & 0xffffffff));
+    this.bitmapHeight = ((int) (subInputStream.readBits(32) & 0xffffffff));
+    this.xLocation = ((int) (subInputStream.readBits(32) & 0xffffffff));
+    this.yLocation = ((int) (subInputStream.readBits(32) & 0xffffffff));
+
+    /* Bit 3-7 */
+    subInputStream.readBits(5); // Dirty read... reserved bits are 0
+
+    /* Bit 0-2 */
+    readCombinationOperator();
+  }
+
+  private void readCombinationOperator() throws IOException {
+    this.combinationOperator = (CombinationOperator.translateOperatorCodeToEnum((short) (subInputStream.readBits(3) & 0xf)));
+  }
+
+  public void init(SegmentHeader header, SubInputStream sis) throws InvalidHeaderValueException,
+      IntegerMaxValueException, IOException {
+  }
+
+  public void setBitmapWidth(final int bitmapWidth) {
+    this.bitmapWidth = bitmapWidth;
+  }
+
+  public int getBitmapWidth() {
+    return bitmapWidth;
+  }
+
+  public void setBitmapHeight(final int bitmapHeight) {
+    this.bitmapHeight = bitmapHeight;
+  }
+
+  public int getBitmapHeight() {
+    return bitmapHeight;
+  }
+
+  public int getXLocation() {
+    return xLocation;
+  }
+
+  public int getYLocation() {
+    return yLocation;
+  }
+
+  public CombinationOperator getCombinationOperator() {
+    return combinationOperator;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/SymbolDictionary.java b/src/main/java/org/apache/pdfbox/jbig2/segments/SymbolDictionary.java
new file mode 100644
index 0000000..a3a6394
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/SymbolDictionary.java
@@ -0,0 +1,843 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.awt.Rectangle;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.Dictionary;
+import org.apache.pdfbox.jbig2.JBIG2ImageReader;
+import org.apache.pdfbox.jbig2.Region;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticIntegerDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.CX;
+import org.apache.pdfbox.jbig2.decoder.huffman.EncodedTable;
+import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable;
+import org.apache.pdfbox.jbig2.decoder.huffman.StandardTables;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represents the data of segment type "Symbol dictionary". Parsing is described in
+ * 7.4.2.1.1 - 7.4.1.1.5 and decoding procedure is described in 6.5.
+ */
+public class SymbolDictionary implements Dictionary {
+
+  private final Logger log = LoggerFactory.getLogger(SymbolDictionary.class);
+
+  private SubInputStream subInputStream;
+
+  /** Symbol dictionary flags, 7.4.2.1.1 */
+  private short sdrTemplate;
+  private byte sdTemplate;
+  private boolean isCodingContextRetained;
+  private boolean isCodingContextUsed;
+  private short sdHuffAggInstanceSelection;
+  private short sdHuffBMSizeSelection;
+  private short sdHuffDecodeWidthSelection;
+  private short sdHuffDecodeHeightSelection;
+  private boolean useRefinementAggregation;
+  private boolean isHuffmanEncoded;
+
+  /** Symbol dictionary AT flags, 7.4.2.1.2 */
+  private short[] sdATX;
+  private short[] sdATY;
+
+  /** Symbol dictionary refinement AT flags, 7.4.2.1.3 */
+  private short[] sdrATX;
+  private short[] sdrATY;
+
+  /** Number of exported symbols, 7.4.2.1.4 */
+  private int amountOfExportSymbolss;
+
+  /** Number of new symbols, 7.4.2.1.5 */
+  private int amountOfNewSymbolss;
+
+  /** Further parameters */
+  private SegmentHeader segmentHeader;
+  private int amountOfImportedSymbolss;
+  private ArrayList<Bitmap> importSymbols;
+  private int amountOfDecodedSymbols;
+  private Bitmap[] newSymbols;
+
+  /** User-supplied tables * */
+  private HuffmanTable dhTable;
+  private HuffmanTable dwTable;
+  private HuffmanTable bmSizeTable;
+  private HuffmanTable aggInstTable;
+
+  /** Return value of that segment */
+  private ArrayList<Bitmap> exportSymbols;
+  private ArrayList<Bitmap> sbSymbols;
+
+  private ArithmeticDecoder arithmeticDecoder;
+  private ArithmeticIntegerDecoder iDecoder;
+
+  private TextRegion textRegion;
+  private GenericRegion genericRegion;
+  private GenericRefinementRegion genericRefinementRegion;
+  private CX cx;
+
+  private CX cxIADH;
+  private CX cxIADW;
+  private CX cxIAAI;
+  private CX cxIAEX;
+  private CX cxIARDX;
+  private CX cxIARDY;
+  private CX cxIADT;
+
+  protected CX cxIAID;
+  private int sbSymCodeLen;
+
+  public SymbolDictionary() {
+  }
+
+  public SymbolDictionary(SubInputStream subInputStream, SegmentHeader segmentHeader) throws IOException {
+    this.subInputStream = subInputStream;
+    this.segmentHeader = segmentHeader;
+  }
+
+  private void parseHeader() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    readRegionFlags();
+    setAtPixels();
+    setRefinementAtPixels();
+    readAmountOfExportedSymbols();
+    readAmountOfNewSymbols();
+    setInSyms();
+
+    if (isCodingContextUsed) {
+      SegmentHeader[] rtSegments = segmentHeader.getRtSegments();
+
+      for (int i = rtSegments.length - 1; i >= 0; i--) {
+
+        if (rtSegments[i].getSegmentType() == 0) {
+          SymbolDictionary symbolDictionary = (SymbolDictionary) rtSegments[i].getSegmentData();
+
+          if (symbolDictionary.isCodingContextRetained) {
+            /* 7.4.2.2 3) */
+            setRetainedCodingContexts(symbolDictionary);
+          }
+          break;
+        }
+      }
+    }
+
+    this.checkInput();
+  }
+
+  private void readRegionFlags() throws IOException {
+    /* Bit 13-15 */
+    subInputStream.readBits(3); // Dirty read... reserved bits must be 0
+
+    /* Bit 12 */
+    sdrTemplate = (short) subInputStream.readBit();
+
+    /* Bit 10-11 */
+    sdTemplate = (byte) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 9 */
+    if (subInputStream.readBit() == 1) {
+      isCodingContextRetained = true;
+    }
+
+    /* Bit 8 */
+    if (subInputStream.readBit() == 1) {
+      isCodingContextUsed = true;
+    }
+
+    /* Bit 7 */
+    sdHuffAggInstanceSelection = (short) subInputStream.readBit();
+
+    /* Bit 6 */
+    sdHuffBMSizeSelection = (short) subInputStream.readBit();
+
+    /* Bit 4-5 */
+    sdHuffDecodeWidthSelection = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 2-3 */
+    sdHuffDecodeHeightSelection = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 1 */
+    if (subInputStream.readBit() == 1) {
+      useRefinementAggregation = true;
+    }
+
+    /* Bit 0 */
+    if (subInputStream.readBit() == 1) {
+      isHuffmanEncoded = true;
+    }
+  }
+
+  private void setAtPixels() throws IOException {
+    if (!isHuffmanEncoded) {
+      if (sdTemplate == 0) {
+        readAtPixels(4);
+      } else {
+        readAtPixels(1);
+      }
+    }
+  }
+
+  private void setRefinementAtPixels() throws IOException {
+    if (useRefinementAggregation && sdrTemplate == 0) {
+      readRefinementAtPixels(2);
+    }
+  }
+
+  private void readAtPixels(final int amountOfPixels) throws IOException {
+    sdATX = new short[amountOfPixels];
+    sdATY = new short[amountOfPixels];
+
+    for (int i = 0; i < amountOfPixels; i++) {
+      sdATX[i] = subInputStream.readByte();
+      sdATY[i] = subInputStream.readByte();
+    }
+  }
+
+  private void readRefinementAtPixels(final int amountOfAtPixels) throws IOException {
+    sdrATX = new short[amountOfAtPixels];
+    sdrATY = new short[amountOfAtPixels];
+
+    for (int i = 0; i < amountOfAtPixels; i++) {
+      sdrATX[i] = subInputStream.readByte();
+      sdrATY[i] = subInputStream.readByte();
+    }
+  }
+
+  private void readAmountOfExportedSymbols() throws IOException {
+    amountOfExportSymbolss = (int) subInputStream.readBits(32); // & 0xffffffff;
+  }
+
+  private void readAmountOfNewSymbols() throws IOException {
+    amountOfNewSymbolss = (int) subInputStream.readBits(32); // & 0xffffffff;
+  }
+
+  private void setInSyms() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    if (segmentHeader.getRtSegments() != null) {
+      retrieveImportSymbols();
+    } else {
+      importSymbols = new ArrayList<Bitmap>();
+    }
+  }
+
+  private void setRetainedCodingContexts(final SymbolDictionary sd) {
+    this.arithmeticDecoder = sd.arithmeticDecoder;
+    this.isHuffmanEncoded = sd.isHuffmanEncoded;
+    this.useRefinementAggregation = sd.useRefinementAggregation;
+    this.sdTemplate = sd.sdTemplate;
+    this.sdrTemplate = sd.sdrTemplate;
+    this.sdATX = sd.sdATX;
+    this.sdATY = sd.sdATY;
+    this.sdrATX = sd.sdrATX;
+    this.sdrATY = sd.sdrATY;
+    this.cx = sd.cx;
+  }
+
+  private void checkInput() throws InvalidHeaderValueException {
+    if (sdHuffDecodeHeightSelection == 2) {
+      log.info("sdHuffDecodeHeightSelection = " + sdHuffDecodeHeightSelection + " (value not permitted)");
+    }
+
+    if (sdHuffDecodeWidthSelection == 2) {
+      log.info("sdHuffDecodeWidthSelection = " + sdHuffDecodeWidthSelection + " (value not permitted)");
+    }
+
+    if (isHuffmanEncoded) {
+      if (sdTemplate != 0) {
+        log.info("sdTemplate = " + sdTemplate + " (should be 0)");
+        sdTemplate = 0;
+      }
+      if (!useRefinementAggregation) {
+        if (isCodingContextRetained) {
+          log.info("isCodingContextRetained = " + isCodingContextRetained + " (should be 0)");
+          isCodingContextRetained = false;
+        }
+
+        if (isCodingContextUsed) {
+          log.info("isCodingContextUsed = " + isCodingContextUsed + " (should be 0)");
+          isCodingContextUsed = false;
+        }
+      }
+
+    } else {
+      if (sdHuffBMSizeSelection != 0) {
+        log.info("sdHuffBMSizeSelection should be 0");
+        sdHuffBMSizeSelection = 0;
+      }
+      if (sdHuffDecodeWidthSelection != 0) {
+        log.info("sdHuffDecodeWidthSelection should be 0");
+        sdHuffDecodeWidthSelection = 0;
+      }
+      if (sdHuffDecodeHeightSelection != 0) {
+        log.info("sdHuffDecodeHeightSelection should be 0");
+        sdHuffDecodeHeightSelection = 0;
+      }
+    }
+
+    if (!useRefinementAggregation) {
+      if (sdrTemplate != 0) {
+        log.info("sdrTemplate = " + sdrTemplate + " (should be 0)");
+        sdrTemplate = 0;
+      }
+    }
+
+    if (!isHuffmanEncoded || !useRefinementAggregation) {
+      if (sdHuffAggInstanceSelection != 0) {
+        log.info("sdHuffAggInstanceSelection = " + sdHuffAggInstanceSelection + " (should be 0)");
+        sdHuffAggInstanceSelection = 0;
+      }
+    }
+  }
+
+  /**
+   * 6.5.5 Decoding the symbol dictionary
+   * 
+   * @return List of decoded symbol bitmaps as an <code>ArrayList</code>
+   */
+  public ArrayList<Bitmap> getDictionary() throws IOException, IntegerMaxValueException, InvalidHeaderValueException {
+    long timestamp = System.currentTimeMillis();
+    if (null == exportSymbols) {
+
+      if (useRefinementAggregation)
+        sbSymCodeLen = getSbSymCodeLen();
+
+      if (!isHuffmanEncoded) {
+        setCodingStatistics();
+      }
+
+      /* 6.5.5 1) */
+      newSymbols = new Bitmap[amountOfNewSymbolss];
+
+      /* 6.5.5 2) */
+      int[] newSymbolsWidths = null;
+      if (isHuffmanEncoded && !useRefinementAggregation) {
+        newSymbolsWidths = new int[amountOfNewSymbolss];
+      }
+
+      setSymbolsArray();
+
+      /* 6.5.5 3) */
+      int heightClassHeight = 0;
+      amountOfDecodedSymbols = 0;
+
+      /* 6.5.5 4 a) */
+      while (amountOfDecodedSymbols != amountOfNewSymbolss) {
+
+        /* 6.5.5 4 b) */
+        heightClassHeight += decodeHeightClassDeltaHeight();
+        int symbolWidth = 0;
+        int totalWidth = 0;
+        final int heightClassFirstSymbolIndex = amountOfDecodedSymbols;
+
+        /* 6.5.5 4 c) */
+
+        // Repeat until OOB - OOB sends a break;
+        while (true) {
+          /* 4 c) i) */
+          final long differenceWidth = decodeDifferenceWidth();
+
+          // If result is OOB, then all the symbols in this height
+          // class has been decoded; proceed to step 4 d)
+          if (differenceWidth == Long.MAX_VALUE) {
+            break;
+          }
+
+          symbolWidth += differenceWidth;
+          totalWidth += symbolWidth;
+
+          /* 4 c) ii) */
+          if (!isHuffmanEncoded || useRefinementAggregation) {
+            if (!useRefinementAggregation) {
+              // 6.5.8.1 - Direct coded
+              decodeDirectlyThroughGenericRegion(symbolWidth, heightClassHeight);
+            } else {
+              // 6.5.8.2 - Refinement/Aggregate-coded
+              decodeAggregate(symbolWidth, heightClassHeight);
+            }
+          } else if (isHuffmanEncoded && !useRefinementAggregation) {
+            /* 4 c) iii) */
+            newSymbolsWidths[amountOfDecodedSymbols] = symbolWidth;
+          }
+          amountOfDecodedSymbols++;
+        }
+
+        /* 6.5.5 4 d) */
+        if (isHuffmanEncoded && !useRefinementAggregation) {
+          /* 6.5.9 */
+          final long bmSize;
+          if (sdHuffBMSizeSelection == 0) {
+            bmSize = StandardTables.getTable(1).decode(subInputStream);
+          } else {
+            bmSize = huffDecodeBmSize();
+          }
+
+          subInputStream.skipBits();
+
+          final Bitmap heightClassCollectiveBitmap = decodeHeightClassCollectiveBitmap(bmSize, heightClassHeight,
+              totalWidth);
+
+          subInputStream.skipBits();
+          decodeHeightClassBitmap(heightClassCollectiveBitmap, heightClassFirstSymbolIndex, heightClassHeight,
+              newSymbolsWidths);
+        }
+      }
+
+      /* 5) */
+      /* 6.5.10 1) - 5) */
+
+      final int[] exFlags = getToExportFlags();
+
+      /* 6.5.10 6) - 8) */
+      setExportedSymbols(exFlags);
+    }
+
+    if (JBIG2ImageReader.PERFORMANCE_TEST)
+      log.info("SYMBOL DECODING: " + (System.currentTimeMillis() - timestamp) + " ms");
+
+    // DictionaryViewer.viewSymbols(sdExSyms);
+
+    return exportSymbols;
+  }
+
+  private void setCodingStatistics() throws IOException {
+    if (cxIADT == null) {
+      cxIADT = new CX(512, 1);
+    }
+
+    if (cxIADH == null) {
+      cxIADH = new CX(512, 1);
+    }
+
+    if (cxIADW == null) {
+      cxIADW = new CX(512, 1);
+    }
+
+    if (cxIAAI == null) {
+      cxIAAI = new CX(512, 1);
+    }
+
+    if (cxIAEX == null) {
+      cxIAEX = new CX(512, 1);
+    }
+
+    if (useRefinementAggregation && cxIAID == null) {
+      cxIAID = new CX(1 << sbSymCodeLen, 1);
+      cxIARDX = new CX(512, 1);
+      cxIARDY = new CX(512, 1);
+    }
+
+    if (cx == null) {
+      cx = new CX(65536, 1);
+    }
+
+    if (arithmeticDecoder == null) {
+      arithmeticDecoder = new ArithmeticDecoder(subInputStream);
+    }
+
+    if (iDecoder == null) {
+      iDecoder = new ArithmeticIntegerDecoder(arithmeticDecoder);
+    }
+
+  }
+
+  private final void decodeHeightClassBitmap(final Bitmap heightClassCollectiveBitmap,
+      final int heightClassFirstSymbol, final int heightClassHeight, final int[] newSymbolsWidths)
+      throws IntegerMaxValueException, InvalidHeaderValueException, IOException {
+
+    for (int i = heightClassFirstSymbol; i < amountOfDecodedSymbols; i++) {
+      int startColumn = 0;
+
+      for (int j = heightClassFirstSymbol; j <= i - 1; j++) {
+        startColumn += newSymbolsWidths[j];
+      }
+
+      final Rectangle roi = new Rectangle(startColumn, 0, newSymbolsWidths[i], heightClassHeight);
+      final Bitmap symbolBitmap = Bitmaps.extract(roi, heightClassCollectiveBitmap);
+      newSymbols[i] = symbolBitmap;
+      sbSymbols.add(symbolBitmap);
+    }
+  }
+
+  private final void decodeAggregate(final int symbolWidth, final int heightClassHeight) throws IOException,
+      InvalidHeaderValueException, IntegerMaxValueException {
+    // 6.5.8.2 1)
+    // 6.5.8.2.1 - Number of symbol instances in aggregation
+    final long amountOfRefinementAggregationInstances;
+    if (isHuffmanEncoded) {
+      log.info("Refinement or aggregate-coded symbols may couse problems with huffman decoding!");
+      amountOfRefinementAggregationInstances = huffDecodeRefAggNInst();
+    } else {
+      amountOfRefinementAggregationInstances = iDecoder.decode(cxIAAI);
+    }
+
+    if (amountOfRefinementAggregationInstances > 1) {
+      // 6.5.8.2 2)
+      decodeThroughTextRegion(symbolWidth, heightClassHeight, amountOfRefinementAggregationInstances);
+    } else if (amountOfRefinementAggregationInstances == 1) {
+      // 6.5.8.2 3) refers to 6.5.8.2.2
+      decodeRefinedSymbol(symbolWidth, heightClassHeight);
+    }
+  }
+
+  private final long huffDecodeRefAggNInst() throws IOException, InvalidHeaderValueException {
+    if (sdHuffAggInstanceSelection == 0) {
+      return StandardTables.getTable(1).decode(subInputStream);
+    } else if (sdHuffAggInstanceSelection == 1) {
+      if (aggInstTable == null) {
+        int aggregationInstanceNumber = 0;
+
+        if (sdHuffDecodeHeightSelection == 3) {
+          aggregationInstanceNumber++;
+        }
+        if (sdHuffDecodeWidthSelection == 3) {
+          aggregationInstanceNumber++;
+        }
+        if (sdHuffBMSizeSelection == 3) {
+          aggregationInstanceNumber++;
+        }
+
+        aggInstTable = getUserTable(aggregationInstanceNumber);
+      }
+      return aggInstTable.decode(subInputStream);
+    }
+    return 0;
+  }
+
+  private final void decodeThroughTextRegion(final int symbolWidth, final int heightClassHeight,
+      final long amountOfRefinementAggregationInstances) throws IOException, IntegerMaxValueException,
+      InvalidHeaderValueException {
+    if (textRegion == null) {
+      textRegion = new TextRegion(subInputStream, null);
+
+      textRegion.setContexts(cx, // default context
+          new CX(512, 1), // IADT
+          new CX(512, 1), // IAFS
+          new CX(512, 1), // IADS
+          new CX(512, 1), // IAIT
+          cxIAID, // IAID
+          new CX(512, 1), // IARDW
+          new CX(512, 1), // IARDH
+          new CX(512, 1), // IARDX
+          new CX(512, 1) // IARDY
+      );
+    }
+
+    // 6.5.8.2.4 Concatenating the array used as parameter later.
+    setSymbolsArray();
+
+    // 6.5.8.2 2) Parameters set according to Table 17, page 36
+    textRegion.setParameters(arithmeticDecoder, iDecoder, isHuffmanEncoded, true, symbolWidth, heightClassHeight,
+        amountOfRefinementAggregationInstances, 1, (amountOfImportedSymbolss + amountOfDecodedSymbols), (short) 0,
+        (short) 0, (short) 0, (short) 1, (short) 0, (short) 0, (short) 0, (short) 0, (short) 0, (short) 0, (short) 0,
+        (short) 0, (short) 0, sdrTemplate, sdrATX, sdrATY, sbSymbols, sbSymCodeLen);
+
+    addSymbol(textRegion);
+  }
+
+  private final void decodeRefinedSymbol(final int symbolWidth, final int heightClassHeight) throws IOException,
+      InvalidHeaderValueException, IntegerMaxValueException {
+
+    final int id;
+    final int rdx;
+    final int rdy;
+    // long symInRefSize = 0;
+    if (isHuffmanEncoded) {
+      /* 2) - 4) */
+      id = (int) subInputStream.readBits(sbSymCodeLen);
+      rdx = (int) StandardTables.getTable(15).decode(subInputStream);
+      rdy = (int) StandardTables.getTable(15).decode(subInputStream);
+
+      /* 5) a) */
+      /* symInRefSize = */StandardTables.getTable(1).decode(subInputStream);
+
+      /* 5) b) - Skip over remaining bits */
+      subInputStream.skipBits();
+    } else {
+      /* 2) - 4) */
+      id = iDecoder.decodeIAID(cxIAID, sbSymCodeLen);
+      rdx = (int) iDecoder.decode(cxIARDX);
+      rdy = (int) iDecoder.decode(cxIARDY);
+    }
+
+    /* 6) */
+    setSymbolsArray();
+    final Bitmap ibo = sbSymbols.get(id);
+    decodeNewSymbols(symbolWidth, heightClassHeight, ibo, rdx, rdy);
+
+    /* 7) */
+    if (isHuffmanEncoded) {
+      subInputStream.skipBits();
+      // Make sure that the processed bytes are equal to the value read in step 5 a)
+    }
+  }
+
+  private final void decodeNewSymbols(final int symWidth, final int hcHeight, final Bitmap ibo, final int rdx,
+      final int rdy) throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    if (genericRefinementRegion == null) {
+      genericRefinementRegion = new GenericRefinementRegion(subInputStream);
+
+      if (arithmeticDecoder == null) {
+        arithmeticDecoder = new ArithmeticDecoder(subInputStream);
+      }
+
+      if (cx == null) {
+        cx = new CX(65536, 1);
+      }
+    }
+
+    // Parameters as shown in Table 18, page 36
+    genericRefinementRegion.setParameters(cx, arithmeticDecoder, sdrTemplate, symWidth, hcHeight, ibo, rdx, rdy, false,
+        sdrATX, sdrATY);
+
+    addSymbol(genericRefinementRegion);
+  }
+
+  private final void decodeDirectlyThroughGenericRegion(final int symWidth, final int hcHeight) throws IOException,
+      IntegerMaxValueException, InvalidHeaderValueException {
+    if (genericRegion == null) {
+      genericRegion = new GenericRegion(subInputStream);
+    }
+
+    // Parameters set according to Table 16, page 35
+    genericRegion.setParameters(false, sdTemplate, false, false, sdATX, sdATY, symWidth, hcHeight, cx,
+        arithmeticDecoder);
+
+    addSymbol(genericRegion);
+  }
+
+  private final void addSymbol(final Region region) throws IntegerMaxValueException, InvalidHeaderValueException,
+      IOException {
+    final Bitmap symbol = region.getRegionBitmap();
+    newSymbols[amountOfDecodedSymbols] = symbol;
+    sbSymbols.add(symbol);
+  }
+
+  private final long decodeDifferenceWidth() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      switch (sdHuffDecodeWidthSelection){
+        case 0 :
+          return StandardTables.getTable(2).decode(subInputStream);
+        case 1 :
+          return StandardTables.getTable(3).decode(subInputStream);
+        case 3 :
+          if (dwTable == null) {
+            int dwNr = 0;
+
+            if (sdHuffDecodeHeightSelection == 3) {
+              dwNr++;
+            }
+            dwTable = getUserTable(dwNr);
+          }
+
+          return dwTable.decode(subInputStream);
+      }
+    } else {
+      return iDecoder.decode(cxIADW);
+    }
+    return 0;
+  }
+
+  private final long decodeHeightClassDeltaHeight() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      return decodeHeightClassDeltaHeightWithHuffman();
+    } else {
+      return iDecoder.decode(cxIADH);
+    }
+  }
+
+  /**
+   * 6.5.6 if isHuffmanEncoded
+   * 
+   * @return long - Result of decoding HCDH
+   * @throws IOException
+   * @throws InvalidHeaderValueException
+   */
+  private final long decodeHeightClassDeltaHeightWithHuffman() throws IOException, InvalidHeaderValueException {
+    switch (sdHuffDecodeHeightSelection){
+      case 0 :
+        return StandardTables.getTable(4).decode(subInputStream);
+      case 1 :
+        return StandardTables.getTable(5).decode(subInputStream);
+      case 3 :
+        if (dhTable == null) {
+          dhTable = getUserTable(0);
+        }
+        return dhTable.decode(subInputStream);
+    }
+
+    return 0;
+  }
+
+  private final Bitmap decodeHeightClassCollectiveBitmap(final long bmSize, final int heightClassHeight,
+      final int totalWidth) throws IOException {
+    if (bmSize == 0) {
+      final Bitmap heightClassCollectiveBitmap = new Bitmap(totalWidth, heightClassHeight);
+
+      for (int i = 0; i < heightClassCollectiveBitmap.getByteArray().length; i++) {
+        heightClassCollectiveBitmap.setByte(i, subInputStream.readByte());
+      }
+
+      return heightClassCollectiveBitmap;
+    } else {
+      if (genericRegion == null) {
+        genericRegion = new GenericRegion(subInputStream);
+      }
+
+      genericRegion.setParameters(true, subInputStream.getStreamPosition(), bmSize, heightClassHeight, totalWidth);
+
+      return genericRegion.getRegionBitmap();
+    }
+  }
+
+  private void setExportedSymbols(int[] toExportFlags) {
+    exportSymbols = new ArrayList<Bitmap>(amountOfExportSymbolss);
+
+    for (int i = 0; i < amountOfImportedSymbolss + amountOfNewSymbolss; i++) {
+
+      if (toExportFlags[i] == 1) {
+        if (i < amountOfImportedSymbolss) {
+          exportSymbols.add(importSymbols.get(i));
+        } else {
+          exportSymbols.add(newSymbols[i - amountOfImportedSymbolss]);
+        }
+      }
+    }
+  }
+
+  private int[] getToExportFlags() throws IOException, InvalidHeaderValueException {
+    int currentExportFlag = 0;
+    long exRunLength = 0;
+    final int[] exportFlags = new int[amountOfImportedSymbolss + amountOfNewSymbolss];
+
+    for (int exportIndex = 0; exportIndex < amountOfImportedSymbolss + amountOfNewSymbolss; exportIndex += exRunLength) {
+
+      if (isHuffmanEncoded) {
+        exRunLength = StandardTables.getTable(1).decode(subInputStream);
+      } else {
+        exRunLength = iDecoder.decode(cxIAEX);
+      }
+
+      if (exRunLength != 0) {
+        for (int index = exportIndex; index < exportIndex + exRunLength; index++) {
+          exportFlags[index] = currentExportFlag;
+        }
+      }
+
+      currentExportFlag = (currentExportFlag == 0) ? 1 : 0;
+    }
+
+    return exportFlags;
+  }
+
+  private final long huffDecodeBmSize() throws IOException, InvalidHeaderValueException {
+    if (bmSizeTable == null) {
+      int bmNr = 0;
+
+      if (sdHuffDecodeHeightSelection == 3) {
+        bmNr++;
+      }
+
+      if (sdHuffDecodeWidthSelection == 3) {
+        bmNr++;
+      }
+
+      bmSizeTable = getUserTable(bmNr);
+    }
+    return bmSizeTable.decode(subInputStream);
+  }
+
+  /**
+   * 6.5.8.2.3 - Setting SBSYMCODES and SBSYMCODELEN
+   * 
+   * @return Result of computing SBSYMCODELEN
+   * @throws IOException
+   */
+  private int getSbSymCodeLen() throws IOException {
+    if (isHuffmanEncoded) {
+      return Math.max((int) (Math.ceil(Math.log(amountOfImportedSymbolss + amountOfNewSymbolss) / Math.log(2))), 1);
+    } else {
+      return (int) (Math.ceil(Math.log(amountOfImportedSymbolss + amountOfNewSymbolss) / Math.log(2)));
+    }
+  }
+
+  /**
+   * 6.5.8.2.4 - Setting SBSYMS
+   * 
+   * @throws IOException
+   * @throws InvalidHeaderValueException
+   * @throws IntegerMaxValueException
+   */
+  private final void setSymbolsArray() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    if (importSymbols == null) {
+      retrieveImportSymbols();
+    }
+
+    if (sbSymbols == null) {
+      sbSymbols = new ArrayList<Bitmap>();
+      sbSymbols.addAll(importSymbols);
+    }
+  }
+
+  /**
+   * Concatenates symbols from all referred-to segments.
+   * 
+   * @throws IOException
+   * @throws InvalidHeaderValueException
+   * @throws IntegerMaxValueException
+   */
+  private void retrieveImportSymbols() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    importSymbols = new ArrayList<Bitmap>();
+    for (final SegmentHeader referredToSegmentHeader : segmentHeader.getRtSegments()) {
+      if (referredToSegmentHeader.getSegmentType() == 0) {
+        final SymbolDictionary sd = (SymbolDictionary) referredToSegmentHeader.getSegmentData();
+        importSymbols.addAll(sd.getDictionary());
+        amountOfImportedSymbolss += sd.amountOfExportSymbolss;
+      }
+    }
+  }
+
+  private HuffmanTable getUserTable(final int tablePosition) throws InvalidHeaderValueException, IOException {
+    int tableCounter = 0;
+
+    for (final SegmentHeader referredToSegmentHeader : segmentHeader.getRtSegments()) {
+      if (referredToSegmentHeader.getSegmentType() == 53) {
+        if (tableCounter == tablePosition) {
+          final Table t = (Table) referredToSegmentHeader.getSegmentData();
+          return new EncodedTable(t);
+        } else {
+          tableCounter++;
+        }
+      }
+    }
+    return null;
+  }
+
+  public void init(final SegmentHeader header, final SubInputStream sis) throws InvalidHeaderValueException,
+      IntegerMaxValueException, IOException {
+    this.subInputStream = sis;
+    this.segmentHeader = header;
+    parseHeader();
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/Table.java b/src/main/java/org/apache/pdfbox/jbig2/segments/Table.java
new file mode 100644
index 0000000..43924e2
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/Table.java
@@ -0,0 +1,97 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.jbig2.SegmentData;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+
+/**
+ * This class represents a "Table" segment. It handles custom tables, see Annex B.
+ */
+public class Table implements SegmentData {
+
+  private SubInputStream subInputStream;
+
+  /** Code table flags, B.2.1, page 87 */
+  private int htOutOfBand;
+  private int htPS;
+  private int htRS;
+
+  /** Code table lowest value, B.2.2, page 87 */
+  private int htLow;
+
+  /** Code table highest value, B.2.3, page 87 */
+  private int htHigh;
+
+  private void parseHeader() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    int bit;
+
+    /* Bit 7 */
+    if ((bit = subInputStream.readBit()) == 1) {
+      throw new InvalidHeaderValueException("B.2.1 Code table flags: Bit 7 must be zero, but was " + bit);
+    }
+
+    /* Bit 4-6 */
+    htRS = (int) ((subInputStream.readBits(3) + 1) & 0xf);
+
+    /* Bit 1-3 */
+    htPS = (int) ((subInputStream.readBits(3) + 1) & 0xf);
+
+    /* Bit 0 */
+    htOutOfBand = (int) subInputStream.readBit();
+
+    htLow = (int) subInputStream.readBits(32); // & 0xffffffff);
+    htHigh = (int) subInputStream.readBits(32); // & 0xffffffff);
+  }
+
+  public void init(SegmentHeader header, SubInputStream sis) throws InvalidHeaderValueException, IOException,
+      IntegerMaxValueException {
+    subInputStream = sis;
+
+    parseHeader();
+  }
+
+  public int getHtOOB() {
+    return htOutOfBand;
+  }
+
+  public int getHtPS() {
+    return htPS;
+  }
+
+  public int getHtRS() {
+    return htRS;
+  }
+
+  public int getHtLow() {
+    return htLow;
+  }
+
+  public int getHtHigh() {
+    return htHigh;
+  }
+
+  public SubInputStream getSubInputStream() {
+    return subInputStream;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/segments/TextRegion.java b/src/main/java/org/apache/pdfbox/jbig2/segments/TextRegion.java
new file mode 100644
index 0000000..797729c
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/segments/TextRegion.java
@@ -0,0 +1,973 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2ImageReader;
+import org.apache.pdfbox.jbig2.Region;
+import org.apache.pdfbox.jbig2.SegmentHeader;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticIntegerDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.CX;
+import org.apache.pdfbox.jbig2.decoder.huffman.EncodedTable;
+import org.apache.pdfbox.jbig2.decoder.huffman.FixedSizeTable;
+import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable;
+import org.apache.pdfbox.jbig2.decoder.huffman.StandardTables;
+import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable.Code;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.apache.pdfbox.jbig2.util.log.Logger;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+
+/**
+ * This class represented the segment type "Text region", 7.4.3, page 56.
+ */
+public class TextRegion implements Region {
+
+  private final Logger log = LoggerFactory.getLogger(TextRegion.class);
+
+  private SubInputStream subInputStream;
+
+  /** Region segment information field, 7.4.1 */
+  private RegionSegmentInformation regionInfo;
+
+  /** Text region segment flags, 7.4.3.1.1 */
+  private short sbrTemplate;
+  private short sbdsOffset; /* 6.4.8 */
+  private short defaultPixel;
+  private CombinationOperator combinationOperator;
+  private short isTransposed;
+  private short referenceCorner;
+  private short logSBStrips;
+  private boolean useRefinement;
+  private boolean isHuffmanEncoded;
+
+  /** Text region segment Huffman flags, 7.4.3.1.2 */
+  private short sbHuffRSize;
+  private short sbHuffRDY;
+  private short sbHuffRDX;
+  private short sbHuffRDHeight;
+  private short sbHuffRDWidth;
+  private short sbHuffDT;
+  private short sbHuffDS;
+  private short sbHuffFS;
+
+  /** Text region refinement AT flags, 7.4.3.1.3 */
+  private short sbrATX[];
+  private short sbrATY[];
+
+  /** Number of symbol instances, 7.4.3.1.4 */
+  private long amountOfSymbolInstances;
+
+  /** Further parameters */
+  private long currentS;
+  private int sbStrips;
+  private int amountOfSymbols;
+
+  private Bitmap regionBitmap;
+  private ArrayList<Bitmap> symbols = new ArrayList<Bitmap>();
+
+  private ArithmeticDecoder arithmeticDecoder;
+  private ArithmeticIntegerDecoder integerDecoder;
+  private GenericRefinementRegion genericRefinementRegion;
+
+  private CX cxIADT;
+  private CX cxIAFS;
+  private CX cxIADS;
+  private CX cxIAIT;
+  private CX cxIARI;
+  private CX cxIARDW;
+  private CX cxIARDH;
+  private CX cxIAID;
+  private CX cxIARDX;
+  private CX cxIARDY;
+  private CX cx;
+
+  /** codeTable including a code to each symbol used in that region */
+  private int symbolCodeLength;
+  private FixedSizeTable symbolCodeTable;
+  private SegmentHeader segmentHeader;
+
+  /** User-supplied tables * */
+  private HuffmanTable fsTable;
+  private HuffmanTable dsTable;
+  private HuffmanTable table;
+  private HuffmanTable rdwTable;
+  private HuffmanTable rdhTable;
+  private HuffmanTable rdxTable;
+  private HuffmanTable rdyTable;
+  private HuffmanTable rSizeTable;
+
+  public TextRegion() {
+  }
+
+  public TextRegion(SubInputStream subInputStream, SegmentHeader segmentHeader) {
+    this.subInputStream = subInputStream;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+    this.segmentHeader = segmentHeader;
+  }
+
+  private void parseHeader() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+
+    regionInfo.parseHeader();
+
+    readRegionFlags();
+
+    if (isHuffmanEncoded) {
+      readHuffmanFlags();
+    }
+
+    readUseRefinement();
+
+    readAmountOfSymbolInstances();
+
+    /* 7.4.3.1.7 */
+    getSymbols();
+
+    computeSymbolCodeLength();
+
+    this.checkInput();
+  }
+
+  private void readRegionFlags() throws IOException {
+    /* Bit 15 */
+    sbrTemplate = (short) subInputStream.readBit();
+
+    /* Bit 10-14 */
+    sbdsOffset = (short) (subInputStream.readBits(5));
+    if (sbdsOffset > 0x0f) {
+      sbdsOffset -= 0x20;
+    }
+
+    /* Bit 9 */
+    defaultPixel = (short) subInputStream.readBit();
+
+    /* Bit 7-8 */
+    combinationOperator = CombinationOperator.translateOperatorCodeToEnum((short) (subInputStream.readBits(2) & 0x3));
+
+    /* Bit 6 */
+    isTransposed = (short) subInputStream.readBit();
+
+    /* Bit 4-5 */
+    referenceCorner = (short) (subInputStream.readBits(2) & 0x3);
+
+    /* Bit 2-3 */
+    logSBStrips = (short) (subInputStream.readBits(2) & 0x3);
+    sbStrips = (1 << logSBStrips);
+
+    /* Bit 1 */
+    if (subInputStream.readBit() == 1) {
+      useRefinement = true;
+    }
+
+    /* Bit 0 */
+    if (subInputStream.readBit() == 1) {
+      isHuffmanEncoded = true;
+    }
+  }
+
+  private void readHuffmanFlags() throws IOException {
+    /* Bit 15 */
+    subInputStream.readBit(); // Dirty read...
+
+    /* Bit 14 */
+    sbHuffRSize = (short) subInputStream.readBit();
+
+    /* Bit 12-13 */
+    sbHuffRDY = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 10-11 */
+    sbHuffRDX = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 8-9 */
+    sbHuffRDHeight = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 6-7 */
+    sbHuffRDWidth = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 4-5 */
+    sbHuffDT = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 2-3 */
+    sbHuffDS = (short) (subInputStream.readBits(2) & 0xf);
+
+    /* Bit 0-1 */
+    sbHuffFS = (short) (subInputStream.readBits(2) & 0xf);
+  }
+
+  private void readUseRefinement() throws IOException {
+    if (useRefinement && sbrTemplate == 0) {
+      sbrATX = new short[2];
+      sbrATY = new short[2];
+
+      /* Byte 0 */
+      sbrATX[0] = subInputStream.readByte();
+
+      /* Byte 1 */
+      sbrATY[0] = subInputStream.readByte();
+
+      /* Byte 2 */
+      sbrATX[1] = subInputStream.readByte();
+
+      /* Byte 3 */
+      sbrATY[1] = subInputStream.readByte();
+    }
+  }
+
+  private void readAmountOfSymbolInstances() throws IOException {
+    amountOfSymbolInstances = subInputStream.readBits(32) & 0xffffffff;
+  }
+
+  private void getSymbols() throws IOException, IntegerMaxValueException, InvalidHeaderValueException {
+    if (segmentHeader.getRtSegments() != null) {
+      initSymbols();
+    }
+  }
+
+  private void computeSymbolCodeLength() throws IOException {
+    if (isHuffmanEncoded) {
+      symbolIDCodeLengths();
+    } else {
+      symbolCodeLength = (int) Math.ceil((Math.log(amountOfSymbols) / Math.log(2)));
+    }
+  }
+
+  private void checkInput() throws InvalidHeaderValueException {
+
+    if (!useRefinement) {
+      if (sbrTemplate != 0) {
+        log.info("sbrTemplate should be 0");
+        sbrTemplate = 0;
+      }
+    }
+
+    if (sbHuffFS == 2 || sbHuffRDWidth == 2 || sbHuffRDHeight == 2 || sbHuffRDX == 2 || sbHuffRDY == 2) {
+      throw new InvalidHeaderValueException("Huffman flag value of text region segment is not permitted");
+    }
+
+    if (!useRefinement) {
+      if (sbHuffRSize != 0) {
+        log.info("sbHuffRSize should be 0");
+        sbHuffRSize = 0;
+      }
+      if (sbHuffRDY != 0) {
+        log.info("sbHuffRDY should be 0");
+        sbHuffRDY = 0;
+      }
+      if (sbHuffRDX != 0) {
+        log.info("sbHuffRDX should be 0");
+        sbHuffRDX = 0;
+      }
+      if (sbHuffRDWidth != 0) {
+        log.info("sbHuffRDWidth should be 0");
+        sbHuffRDWidth = 0;
+      }
+      if (sbHuffRDHeight != 0) {
+        log.info("sbHuffRDHeight should be 0");
+        sbHuffRDHeight = 0;
+      }
+    }
+  }
+
+  public Bitmap getRegionBitmap() throws IOException, IntegerMaxValueException, InvalidHeaderValueException {
+
+    if (!isHuffmanEncoded) {
+      setCodingStatistics();
+    }
+
+    createRegionBitmap();
+    decodeSymbolInstances();
+
+    /* 4) */
+    return regionBitmap;
+  }
+
+  private void setCodingStatistics() throws IOException {
+    if (cxIADT == null)
+      cxIADT = new CX(512, 1);
+
+    if (cxIAFS == null)
+      cxIAFS = new CX(512, 1);
+
+    if (cxIADS == null)
+      cxIADS = new CX(512, 1);
+
+    if (cxIAIT == null)
+      cxIAIT = new CX(512, 1);
+
+    if (cxIARI == null)
+      cxIARI = new CX(512, 1);
+
+    if (cxIARDW == null)
+      cxIARDW = new CX(512, 1);
+
+    if (cxIARDH == null)
+      cxIARDH = new CX(512, 1);
+
+    if (cxIAID == null)
+      cxIAID = new CX(1 << symbolCodeLength, 1);
+
+    if (cxIARDX == null)
+      cxIARDX = new CX(512, 1);
+
+    if (cxIARDY == null)
+      cxIARDY = new CX(512, 1);
+
+    if (arithmeticDecoder == null)
+      arithmeticDecoder = new ArithmeticDecoder(subInputStream);
+
+    if (integerDecoder == null)
+      integerDecoder = new ArithmeticIntegerDecoder(arithmeticDecoder);
+  }
+
+  private void createRegionBitmap() {
+
+    /* 6.4.5 */
+    final int width = regionInfo.getBitmapWidth();
+    final int height = regionInfo.getBitmapHeight();
+    regionBitmap = new Bitmap(width, height);
+
+    /* 1) */
+    if (defaultPixel != 0) {
+      Arrays.fill(regionBitmap.getByteArray(), (byte) 0xff);
+    }
+  }
+
+  private final long decodeStripT() throws IOException, InvalidHeaderValueException {
+
+    long stripT = 0;
+    /* 2) */
+    if (isHuffmanEncoded) {
+      /* 6.4.6 */
+      if (sbHuffDT == 3) {
+        // TODO test user-specified table
+        if (table == null) {
+          int dtNr = 0;
+
+          if (sbHuffFS == 3) {
+            dtNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            dtNr++;
+          }
+
+          table = getUserTable(dtNr);
+        }
+        stripT = table.decode(subInputStream);
+      } else {
+        stripT = StandardTables.getTable(11 + sbHuffDT).decode(subInputStream);
+      }
+    } else {
+      stripT = integerDecoder.decode(cxIADT);
+    }
+
+    return stripT * -(sbStrips);
+  }
+
+  private void decodeSymbolInstances() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+
+    long stripT = decodeStripT();
+
+    /* Last two sentences of 6.4.5 2) */
+    long firstS = 0;
+    int instanceCounter = 0;
+
+    /* 6.4.5 3 a) */
+    while (instanceCounter < amountOfSymbolInstances) {
+
+      final long dT = decodeDT();
+      stripT += dT;
+      long dfS = 0;
+
+      /* 3 c) symbol instances in the strip */
+      boolean first = true;
+      currentS = 0;
+
+      // do until OOB
+      for (;;) {
+        /* 3 c) i) - first symbol instance in the strip */
+        if (first) {
+          /* 6.4.7 */
+          dfS = decodeDfS();
+          firstS += dfS;
+          currentS = firstS;
+          first = false;
+          /* 3 c) ii) - the remaining symbol instances in the strip */
+        } else {
+          /* 6.4.8 */
+          final long idS = decodeIdS();
+          /*
+           * If result is OOB, then all the symbol instances in this strip have been decoded;
+           * proceed to step 3 d) respectively 3 b)
+           */
+          if (idS == Long.MAX_VALUE)
+            break;
+
+          currentS += (idS + sbdsOffset);
+        }
+
+        /* 3 c) iii) */
+        final long currentT = decodeCurrentT();
+        final long t = stripT + currentT;
+
+        /* 3 c) iv) */
+        final long id = decodeID();
+
+        /* 3 c) v) */
+        final long r = decodeRI();
+        /* 6.4.11 */
+        final Bitmap ib = decodeIb(r, id);
+
+        /* vi) */
+        blit(ib, t);
+
+        instanceCounter++;
+      }
+    }
+  }
+
+  private final long decodeDT() throws IOException {
+    /* 3) b) */
+    /* 6.4.6 */
+    long dT;
+    if (isHuffmanEncoded) {
+      if (sbHuffDT == 3) {
+        dT = table.decode(subInputStream);
+      } else {
+        dT = StandardTables.getTable(11 + sbHuffDT).decode(subInputStream);
+      }
+    } else {
+      dT = integerDecoder.decode(cxIADT);
+    }
+
+    return (dT * sbStrips);
+  }
+
+  private final long decodeDfS() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffFS == 3) {
+        if (fsTable == null) {
+          fsTable = getUserTable(0);
+        }
+        return fsTable.decode(subInputStream);
+      } else {
+        return StandardTables.getTable(6 + sbHuffFS).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIAFS);
+    }
+  }
+
+  private final long decodeIdS() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffDS == 3) {
+        // TODO test user-specified table
+        if (dsTable == null) {
+          int dsNr = 0;
+          if (sbHuffFS == 3) {
+            dsNr++;
+          }
+
+          dsTable = getUserTable(dsNr);
+        }
+        return dsTable.decode(subInputStream);
+
+      } else {
+        return StandardTables.getTable(8 + sbHuffDS).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIADS);
+    }
+  }
+
+  private final long decodeCurrentT() throws IOException {
+    if (sbStrips != 1) {
+      if (isHuffmanEncoded) {
+        return subInputStream.readBits(logSBStrips);
+      } else {
+        return integerDecoder.decode(cxIAIT);
+      }
+    }
+
+    return 0;
+  }
+
+  private final long decodeID() throws IOException {
+    if (isHuffmanEncoded) {
+      if (symbolCodeTable == null) {
+        return subInputStream.readBits(symbolCodeLength);
+      }
+
+      return symbolCodeTable.decode(subInputStream);
+    } else {
+      return integerDecoder.decodeIAID(cxIAID, symbolCodeLength);
+    }
+  }
+
+  private final long decodeRI() throws IOException {
+    if (useRefinement) {
+      if (isHuffmanEncoded) {
+        return subInputStream.readBit();
+      } else {
+        return integerDecoder.decode(cxIARI);
+      }
+    }
+    return 0;
+  }
+
+  private final Bitmap decodeIb(long r, long id) throws IOException, InvalidHeaderValueException,
+      IntegerMaxValueException {
+    Bitmap ib;
+
+    if (r == 0) {
+      ib = symbols.get((int) id);
+    } else {
+      /* 1) - 4) */
+      final long rdw = decodeRdw();
+      final long rdh = decodeRdh();
+      final long rdx = decodeRdx();
+      final long rdy = decodeRdy();
+
+      /* 5) */
+      /* long symInRefSize = 0; */
+      if (isHuffmanEncoded) {
+        /* symInRefSize = */decodeSymInRefSize();
+        subInputStream.skipBits();
+      }
+
+      /* 6) */
+      final Bitmap ibo = symbols.get((int) id);
+      final int wo = ibo.getWidth();
+      final int ho = ibo.getHeight();
+
+      final int genericRegionReferenceDX = (int) ((rdw >> 1) + rdx);
+      final int genericRegionReferenceDY = (int) ((rdh >> 1) + rdy);
+
+      if (genericRefinementRegion == null) {
+        genericRefinementRegion = new GenericRefinementRegion(subInputStream);
+      }
+
+      genericRefinementRegion.setParameters(cx, arithmeticDecoder, sbrTemplate, (int) (wo + rdw), (int) (ho + rdh),
+          ibo, genericRegionReferenceDX, genericRegionReferenceDY, false, sbrATX, sbrATY);
+
+      ib = genericRefinementRegion.getRegionBitmap();
+
+      /* 7 */
+      if (isHuffmanEncoded) {
+        subInputStream.skipBits();
+      }
+    }
+    return ib;
+  }
+
+  private final long decodeRdw() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffRDWidth == 3) {
+        // TODO test user-specified table
+        if (rdwTable == null) {
+          int rdwNr = 0;
+          if (sbHuffFS == 3) {
+            rdwNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            rdwNr++;
+          }
+
+          if (sbHuffDT == 3) {
+            rdwNr++;
+          }
+
+          rdwTable = getUserTable(rdwNr);
+        }
+        return rdwTable.decode(subInputStream);
+
+      } else {
+        return StandardTables.getTable(14 + sbHuffRDWidth).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIARDW);
+    }
+  }
+
+  private final long decodeRdh() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffRDHeight == 3) {
+        if (rdhTable == null) {
+          int rdhNr = 0;
+
+          if (sbHuffFS == 3) {
+            rdhNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            rdhNr++;
+          }
+
+          if (sbHuffDT == 3) {
+            rdhNr++;
+          }
+
+          if (sbHuffRDWidth == 3) {
+            rdhNr++;
+          }
+
+          rdhTable = getUserTable(rdhNr);
+        }
+        return rdhTable.decode(subInputStream);
+      } else {
+        return StandardTables.getTable(14 + sbHuffRDHeight).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIARDH);
+    }
+  }
+
+  private final long decodeRdx() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffRDX == 3) {
+        if (rdxTable == null) {
+          int rdxNr = 0;
+          if (sbHuffFS == 3) {
+            rdxNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            rdxNr++;
+          }
+
+          if (sbHuffDT == 3) {
+            rdxNr++;
+          }
+
+          if (sbHuffRDWidth == 3) {
+            rdxNr++;
+          }
+
+          if (sbHuffRDHeight == 3) {
+            rdxNr++;
+          }
+
+          rdxTable = getUserTable(rdxNr);
+        }
+        return rdxTable.decode(subInputStream);
+      } else {
+        return StandardTables.getTable(14 + sbHuffRDX).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIARDX);
+    }
+  }
+
+  private final long decodeRdy() throws IOException, InvalidHeaderValueException {
+    if (isHuffmanEncoded) {
+      if (sbHuffRDY == 3) {
+        if (rdyTable == null) {
+          int rdyNr = 0;
+          if (sbHuffFS == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffDS == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffDT == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffRDWidth == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffRDHeight == 3) {
+            rdyNr++;
+          }
+
+          if (sbHuffRDX == 3) {
+            rdyNr++;
+          }
+
+          rdyTable = getUserTable(rdyNr);
+        }
+        return rdyTable.decode(subInputStream);
+      } else {
+        return StandardTables.getTable(14 + sbHuffRDY).decode(subInputStream);
+      }
+    } else {
+      return integerDecoder.decode(cxIARDY);
+    }
+  }
+
+  private final long decodeSymInRefSize() throws IOException, InvalidHeaderValueException {
+    if (sbHuffRSize == 0) {
+      return StandardTables.getTable(1).decode(subInputStream);
+    } else {
+      if (rSizeTable == null) {
+        int rSizeNr = 0;
+
+        if (sbHuffFS == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffDS == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffDT == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffRDWidth == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffRDHeight == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffRDX == 3) {
+          rSizeNr++;
+        }
+
+        if (sbHuffRDY == 3) {
+          rSizeNr++;
+        }
+
+        rSizeTable = getUserTable(rSizeNr);
+      }
+      return rSizeTable.decode(subInputStream);
+    }
+
+  }
+
+  private final void blit(Bitmap ib, long t) {
+    if (isTransposed == 0 && (referenceCorner == 2 || referenceCorner == 3)) {
+      currentS += ib.getWidth() - 1;
+    } else if (isTransposed == 1 && (referenceCorner == 0 || referenceCorner == 2)) {
+      currentS += ib.getHeight() - 1;
+    }
+
+    /* vii) */
+    long s = currentS;
+
+    /* viii) */
+    if (isTransposed == 1) {
+      final long swap = t;
+      t = s;
+      s = swap;
+    }
+
+    if (referenceCorner != 1) {
+      if (referenceCorner == 0) {
+        // BL
+        t -= ib.getHeight() - 1;
+      } else if (referenceCorner == 2) {
+        // BR
+        t -= ib.getHeight() - 1;
+        s -= ib.getWidth() - 1;
+      } else if (referenceCorner == 3) {
+        // TR
+        s -= ib.getWidth() - 1;
+      }
+    }
+
+    Bitmaps.blit(ib, regionBitmap, (int) s, (int) t, combinationOperator);
+
+    /* x) */
+    if (isTransposed == 0 && (referenceCorner == 0 || referenceCorner == 1)) {
+      currentS += ib.getWidth() - 1;
+    }
+
+    if (isTransposed == 1 && (referenceCorner == 1 || referenceCorner == 3)) {
+      currentS += ib.getHeight() - 1;
+    }
+
+  }
+
+  private void initSymbols() throws IOException, IntegerMaxValueException, InvalidHeaderValueException {
+    for (final SegmentHeader segment : segmentHeader.getRtSegments()) {
+      if (segment.getSegmentType() == 0) {
+        final SymbolDictionary sd = (SymbolDictionary) segment.getSegmentData();
+
+        sd.cxIAID = cxIAID;
+        symbols.addAll(sd.getDictionary());
+      }
+    }
+    amountOfSymbols = symbols.size();
+  }
+
+  private HuffmanTable getUserTable(final int tablePosition) throws InvalidHeaderValueException, IOException {
+    int tableCounter = 0;
+
+    for (final SegmentHeader referredToSegmentHeader : segmentHeader.getRtSegments()) {
+      if (referredToSegmentHeader.getSegmentType() == 53) {
+        if (tableCounter == tablePosition) {
+          final Table t = (Table) referredToSegmentHeader.getSegmentData();
+          return new EncodedTable(t);
+        } else {
+          tableCounter++;
+        }
+      }
+    }
+    return null;
+  }
+
+  private void symbolIDCodeLengths() throws IOException {
+    /* 1) - 2) */
+    final List<Code> runCodeTable = new ArrayList<Code>();
+
+    for (int i = 0; i < 35; i++) {
+      final int prefLen = (int) (subInputStream.readBits(4) & 0xf);
+      if (prefLen > 0) {
+        runCodeTable.add(new Code(prefLen, 0, i, false));
+      }
+    }
+
+    if (JBIG2ImageReader.DEBUG)
+      log.debug(HuffmanTable.codeTableToString(runCodeTable));
+
+    HuffmanTable ht = new FixedSizeTable(runCodeTable);
+
+    /* 3) - 5) */
+    long previousCodeLength = 0;
+
+    int counter = 0;
+    final List<Code> sbSymCodes = new ArrayList<Code>();
+    while (counter < amountOfSymbols) {
+      final long code = ht.decode(subInputStream);
+      if (code < 32) {
+        if (code > 0) {
+          sbSymCodes.add(new Code((int) code, 0, counter, false));
+        }
+
+        previousCodeLength = code;
+        counter++;
+      } else {
+
+        long runLength = 0;
+        long currCodeLength = 0;
+        if (code == 32) {
+          runLength = 3 + subInputStream.readBits(2);
+          if (counter > 0) {
+            currCodeLength = previousCodeLength;
+          }
+        } else if (code == 33) {
+          runLength = 3 + subInputStream.readBits(3);
+        } else if (code == 34) {
+          runLength = 11 + subInputStream.readBits(7);
+        }
+
+        for (int j = 0; j < runLength; j++) {
+          if (currCodeLength > 0) {
+            sbSymCodes.add(new Code((int) currCodeLength, 0, counter, false));
+          }
+          counter++;
+        }
+      }
+    }
+
+    /* 6) - Skip over remaining bits in the last Byte read */
+    subInputStream.skipBits();
+
+    /* 7) */
+    symbolCodeTable = new FixedSizeTable(sbSymCodes);
+
+  }
+
+  public void init(SegmentHeader header, SubInputStream sis) throws InvalidHeaderValueException,
+      IntegerMaxValueException, IOException {
+    this.segmentHeader = header;
+    this.subInputStream = sis;
+    this.regionInfo = new RegionSegmentInformation(subInputStream);
+    parseHeader();
+  }
+
+  protected void setContexts(CX cx, CX cxIADT, CX cxIAFS, CX cxIADS, CX cxIAIT, CX cxIAID, CX cxIARDW, CX cxIARDH,
+      CX cxIARDX, CX cxIARDY) {
+    this.cx = cx;
+
+    this.cxIADT = cxIADT;
+    this.cxIAFS = cxIAFS;
+    this.cxIADS = cxIADS;
+    this.cxIAIT = cxIAIT;
+
+    this.cxIAID = cxIAID;
+
+    this.cxIARDW = cxIARDW;
+    this.cxIARDH = cxIARDH;
+    this.cxIARDX = cxIARDX;
+    this.cxIARDY = cxIARDY;
+  }
+
+  protected void setParameters(ArithmeticDecoder arithmeticDecoder, ArithmeticIntegerDecoder iDecoder,
+      boolean isHuffmanEncoded, boolean sbRefine, int sbw, int sbh, long sbNumInstances, int sbStrips, int sbNumSyms,
+      short sbDefaultPixel, short sbCombinationOperator, short transposed, short refCorner, short sbdsOffset,
+      short sbHuffFS, short sbHuffDS, short sbHuffDT, short sbHuffRDWidth, short sbHuffRDHeight, short sbHuffRDX,
+      short sbHuffRDY, short sbHuffRSize, short sbrTemplate, short sbrATX[], short sbrATY[], ArrayList<Bitmap> sbSyms,
+      int sbSymCodeLen) {
+
+    this.arithmeticDecoder = arithmeticDecoder;
+
+    this.integerDecoder = iDecoder;
+
+    this.isHuffmanEncoded = isHuffmanEncoded;
+    this.useRefinement = sbRefine;
+
+    this.regionInfo.setBitmapWidth(sbw);
+    this.regionInfo.setBitmapHeight(sbh);
+
+    this.amountOfSymbolInstances = sbNumInstances;
+    this.sbStrips = sbStrips;
+    this.amountOfSymbols = sbNumSyms;
+    this.defaultPixel = sbDefaultPixel;
+    this.combinationOperator = CombinationOperator.translateOperatorCodeToEnum(sbCombinationOperator);
+    this.isTransposed = transposed;
+    this.referenceCorner = refCorner;
+    this.sbdsOffset = sbdsOffset;
+
+    this.sbHuffFS = sbHuffFS;
+    this.sbHuffDS = sbHuffDS;
+    this.sbHuffDT = sbHuffDT;
+    this.sbHuffRDWidth = sbHuffRDWidth;
+    this.sbHuffRDHeight = sbHuffRDHeight;
+    this.sbHuffRDX = sbHuffRDX;
+    this.sbHuffRDY = sbHuffRDY;
+    this.sbHuffRSize = sbHuffRSize;
+
+    this.sbrTemplate = sbrTemplate;
+    this.sbrATX = sbrATX;
+    this.sbrATY = sbrATY;
+
+    this.symbols = sbSyms;
+    this.symbolCodeLength = sbSymCodeLen;
+  }
+
+  public RegionSegmentInformation getRegionInfo() {
+    return regionInfo;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/CombinationOperator.java b/src/main/java/org/apache/pdfbox/jbig2/util/CombinationOperator.java
new file mode 100644
index 0000000..1d40717
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/CombinationOperator.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+/**
+ * This enumeration keeps the available logical operator defined in the JBIG2 ISO standard.
+ */
+public enum CombinationOperator {
+  OR, AND, XOR, XNOR, REPLACE;
+
+  public static CombinationOperator translateOperatorCodeToEnum(short combinationOperatorCode) {
+    switch (combinationOperatorCode){
+      case 0 :
+        return OR;
+      case 1 :
+        return AND;
+      case 2 :
+        return XOR;
+      case 3 :
+        return XNOR;
+      default :
+        return REPLACE;
+    }
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/DictionaryViewer.java b/src/main/java/org/apache/pdfbox/jbig2/util/DictionaryViewer.java
new file mode 100644
index 0000000..2659082
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/DictionaryViewer.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+import java.util.List;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.TestImage;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+
+/**
+ * This class is for debug purpose only. The {@code DictionaryViewer} is able to show a single
+ * bitmap or all symbol bitmaps.
+ */
+class DictionaryViewer {
+
+  public static void show(Bitmap b) {
+    new TestImage(Bitmaps.asBufferedImage(b));
+  }
+
+  public static void show(List<Bitmap> symbols) {
+    int width = 0;
+    int height = 0;
+
+    for (Bitmap b : symbols) {
+      width += b.getWidth();
+
+      if (b.getHeight() > height) {
+        height = b.getHeight();
+      }
+    }
+
+    Bitmap result = new Bitmap(width, height);
+
+    int xOffset = 0;
+
+    for (Bitmap b : symbols) {
+      Bitmaps.blit(b, result, xOffset, 0, CombinationOperator.REPLACE);
+      xOffset += b.getWidth();
+    }
+
+    new TestImage(Bitmaps.asBufferedImage(result));
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/ServiceLookup.java b/src/main/java/org/apache/pdfbox/jbig2/util/ServiceLookup.java
new file mode 100644
index 0000000..0c3f78e
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/ServiceLookup.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+public class ServiceLookup<B> {
+
+  public Iterator<B> getServices(Class<B> cls) {
+    return getServices(cls, null);
+  }
+
+  public Iterator<B> getServices(Class<B> cls, ClassLoader clsLoader) {
+    Iterator<B> services = ServiceLoader.load(cls).iterator();
+
+    if (!services.hasNext()) {
+      services = ServiceLoader.load(cls, cls.getClass().getClassLoader()).iterator();
+    }
+
+    if (!services.hasNext() && clsLoader != null) {
+      services = ServiceLoader.load(cls, clsLoader).iterator();
+    }
+
+    return services;
+  }
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/Utils.java b/src/main/java/org/apache/pdfbox/jbig2/util/Utils.java
new file mode 100644
index 0000000..ae2ae7a
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/Utils.java
@@ -0,0 +1,101 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+
+public class Utils {
+
+  /**
+   * Create a rectangle with the same area as the given input rectangle but with all of its edges
+   * snapped (rounded) to the integer grid. The resulting rectangle is guaranteed to cover
+   * <em>all</em> of the input rectangle's area, so that
+   * <code>enlargeToGrid(r).contains(r) == true</code> holds. This can be depicted as the edges
+   * being stretched in an outward direction.
+   * 
+   * @param r
+   * @return
+   */
+  public static Rectangle enlargeRectToGrid(Rectangle2D r) {
+    final int x0 = floor(r.getMinX());
+    final int y0 = floor(r.getMinY());
+    final int x1 = ceil(r.getMaxX());
+    final int y1 = ceil(r.getMaxY());
+    return new Rectangle(x0, y0, x1 - x0, y1 - y0);
+  }
+  
+  /**
+   * Return a new rectangle which covers the area of the given rectangle with an additional margin
+   * on the sides.
+   * 
+   * @param r
+   * @param marginX
+   */
+  public static Rectangle2D dilateRect(Rectangle2D r, double marginX, double marginY) {
+    return new Rectangle2D.Double(r.getX() - marginX, r.getY() - marginY, r.getWidth() + 2 * marginX, r.getHeight() + 2
+        * marginY);
+  }
+  
+  /**
+   * Clamp the value into the range [min..max].
+   * 
+   * @param value
+   * @param min
+   * @param max
+   * @return the clamped value
+   */
+  public static double clamp(double value, double min, double max) {
+    return Math.min(max, Math.max(value, min));
+  }
+  
+  private static final int BIG_ENOUGH_INT = 16 * 1024;
+  private static final double BIG_ENOUGH_FLOOR = BIG_ENOUGH_INT;
+  private static final double BIG_ENOUGH_ROUND = BIG_ENOUGH_INT + 0.5;
+  
+  /**
+   * A fast implementation of {@link Math#floor(double)}.
+   * 
+   * @param x the argument
+   * @return
+   */
+  public static int floor(double x) {
+    return (int) (x + BIG_ENOUGH_FLOOR) - BIG_ENOUGH_INT;
+  }
+
+  /**
+   * A fast implementation of {@link Math#round(double)}.
+   * 
+   * @param x the argument
+   * @return
+   */
+  public static int round(double x) {
+    return (int) (x + BIG_ENOUGH_ROUND) - BIG_ENOUGH_INT;
+  }
+
+  /**
+   * A fast implementation of {@link Math#ceil(double)}.
+   * 
+   * @param x the argument
+   * @return
+   */
+  public static int ceil(double x) {
+    return BIG_ENOUGH_INT - (int) (BIG_ENOUGH_FLOOR - x);
+  }
+  
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/Cache.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/Cache.java
new file mode 100644
index 0000000..41038cf
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/Cache.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+public interface Cache {
+
+/**
+ * 
+ * @param key
+ * @param value
+ * @param sizeEstimate
+ * 
+ * @return the old object, that was replaced if present. Otherwise {@code null}.
+ */
+  Object put(Object key, Object value, int sizeEstimate);
+
+  Object get(Object key);
+
+  /**
+   * Removes all mappings from a map (optional operation).
+   * 
+   * @throws UnsupportedOperationException if {@code clear()} is not supported by the map.
+   */
+  void clear();
+
+  /**
+   * 
+   * @param key
+   * @return the removed object, if present. Otherwise {@code null}.
+   */
+  Object remove(Object key);
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheBridge.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheBridge.java
new file mode 100644
index 0000000..19338b1
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheBridge.java
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+public interface CacheBridge {
+
+  Cache getCache();
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheFactory.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheFactory.java
new file mode 100644
index 0000000..718c04a
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/CacheFactory.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+import java.util.Iterator;
+
+import org.apache.pdfbox.jbig2.util.ServiceLookup;
+
+/**
+ * Retrieves a {@link Cache} via registered {@link CacheBridge} through
+ * <code>META-INF/services</code> lookup.
+ */
+public class CacheFactory {
+
+  private static CacheBridge cacheBridge;
+
+  private static ClassLoader clsLoader;
+
+  public static Cache getCache(ClassLoader clsLoader) {
+    if (null == cacheBridge) {
+      final ServiceLookup<CacheBridge> serviceLookup = new ServiceLookup<CacheBridge>();
+      final Iterator<CacheBridge> cacheBridgeServices = serviceLookup.getServices(CacheBridge.class, clsLoader);
+
+      if (!cacheBridgeServices.hasNext()) {
+        throw new IllegalStateException("No implementation of " + CacheBridge.class
+            + " was avaliable using META-INF/services lookup");
+      }
+      cacheBridge = cacheBridgeServices.next();
+    }
+    return cacheBridge.getCache();
+  }
+
+  public static Cache getCache() {
+    return getCache(clsLoader != null ? clsLoader : CacheBridge.class.getClassLoader());
+  }
+
+  public static void setClassLoader(ClassLoader clsLoader) {
+    CacheFactory.clsLoader = clsLoader;
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCache.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCache.java
new file mode 100644
index 0000000..35a1407
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCache.java
@@ -0,0 +1,50 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+
+public class SoftReferenceCache implements Cache {
+
+  private HashMap<Object, SoftReference<?>> cache = new HashMap<Object, SoftReference<?>>();
+
+  public Object put(Object key, Object value, int sizeEstimate) {
+    SoftReference<Object> softReference = new SoftReference<Object>(value);
+    SoftReference<?> oldValue = cache.put(key, softReference);
+    return getValueNullSafe(oldValue);
+  }
+
+  public Object get(Object key) {
+    SoftReference<?> softReference = cache.get(key);
+    return getValueNullSafe(softReference);
+  }
+
+  public void clear() {
+    cache.clear();
+  }
+
+  public Object remove(Object key) {
+    SoftReference<?> removedObj = cache.remove(key);
+    return getValueNullSafe(removedObj);
+  }
+
+  private Object getValueNullSafe(SoftReference<?> softReference) {
+    return softReference == null ? null : softReference.get();
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCacheBridge.java b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCacheBridge.java
new file mode 100644
index 0000000..59f039f
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/cache/SoftReferenceCacheBridge.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.cache;
+
+public class SoftReferenceCacheBridge implements CacheBridge {
+
+  private static final SoftReferenceCache cache = new SoftReferenceCache();
+
+  public Cache getCache() {
+    return cache;
+  }
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLogger.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLogger.java
new file mode 100644
index 0000000..70f3e0e
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLogger.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+import java.util.logging.Level;
+
+public class JDKLogger implements Logger {
+  final java.util.logging.Logger wrappedLogger;
+
+  public JDKLogger(java.util.logging.Logger logger) {
+    wrappedLogger = logger;
+  }
+
+  public void debug(String msg) {
+    wrappedLogger.log(Level.FINE, msg);
+  }
+
+  public void debug(String msg, Throwable t) {
+    wrappedLogger.log(Level.FINE, msg, t);
+  }
+
+  public void info(String msg) {
+    wrappedLogger.log(Level.INFO, msg);
+  }
+
+  public void info(String msg, Throwable t) {
+    wrappedLogger.log(Level.INFO, msg, t);
+  }
+
+  public void warn(String msg) {
+    wrappedLogger.log(Level.WARNING, msg);
+  }
+
+  public void warn(String msg, Throwable t) {
+    wrappedLogger.log(Level.WARNING, msg, t);
+  }
+
+  public void fatal(String msg) {
+    wrappedLogger.log(Level.SEVERE, msg);
+  }
+
+  public void fatal(String msg, Throwable t) {
+    wrappedLogger.log(Level.SEVERE, msg, t);
+  }
+
+  public void error(String msg) {
+    wrappedLogger.log(Level.SEVERE, msg);
+  }
+
+  public void error(String msg, Throwable t) {
+    wrappedLogger.log(Level.SEVERE, msg, t);
+  }
+
+  public boolean isDebugEnabled() {
+    return wrappedLogger.isLoggable(Level.FINE);
+  }
+
+  public boolean isInfoEnabled() {
+    return wrappedLogger.isLoggable(Level.INFO);
+  }
+
+  public boolean isWarnEnabled() {
+    return wrappedLogger.isLoggable(Level.WARNING);
+  }
+
+  public boolean isFatalEnabled() {
+    return wrappedLogger.isLoggable(Level.SEVERE);
+  }
+
+  public boolean isErrorEnabled() {
+    return wrappedLogger.isLoggable(Level.SEVERE);
+  }
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLoggerBridge.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLoggerBridge.java
new file mode 100644
index 0000000..bc82d9e
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/JDKLoggerBridge.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+public class JDKLoggerBridge implements LoggerBridge {
+
+  public Logger getLogger(Class<?> classToBeLogged) {
+    return new JDKLogger(java.util.logging.Logger.getLogger(classToBeLogged.getName()));
+  }
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/Logger.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/Logger.java
new file mode 100644
index 0000000..941ea61
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/Logger.java
@@ -0,0 +1,135 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+public interface Logger {
+
+
+  /**
+   * Log a message at the DEBUG level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void debug(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the DEBUG level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception.
+   * @param t the exception ({@link Throwable}) to log.
+   */
+  public void debug(String msg, Throwable t);
+
+  /**
+   * Log a message at the INFO level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void info(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the INFO level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception
+   * @param t the exception ({@link Throwable}) to log
+   */
+  public void info(String msg, Throwable t);
+
+  /**
+   * Log a message at the WARN level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void warn(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the WARN level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception
+   * @param t the exception ({@link Throwable}) to log
+   */
+  public void warn(String msg, Throwable t);
+
+  /**
+   * Log a message at the WARN level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void fatal(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the WARN level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception
+   * @param t the exception ({@link Throwable}) to log
+   */
+  public void fatal(String msg, Throwable t);
+
+  /**
+   * Log a message at the ERROR level.
+   * 
+   * @param msg the message string to be logged
+   */
+  public void error(String msg);
+
+  /**
+   * Log an exception ({@link Throwable}) at the ERROR level with an accompanying message.
+   * 
+   * @param msg the message accompanying the exception
+   * @param t the exception ({@link Throwable}) to log
+   */
+  public void error(String msg, Throwable t);
+
+  /**
+   * Is the logger instance enabled for the DEBUG level?
+   * 
+   * @return True if this Logger is enabled for the DEBUG level, false otherwise.
+   * 
+   */
+  public boolean isDebugEnabled();
+
+  /**
+   * Is the logger instance enabled for the INFO level?
+   * 
+   * @return True if this Logger is enabled for the INFO level, false otherwise.
+   */
+  public boolean isInfoEnabled();
+
+  /**
+   * Is the logger instance enabled for the WARN level?
+   * 
+   * @return True if this Logger is enabled for the WARN level, false otherwise.
+   */
+  public boolean isWarnEnabled();
+
+  /**
+   * Is the logger instance enabled for the FATAL level?
+   * 
+   * @return True if this Logger is enabled for the FATAL level, false otherwise.
+   */
+  public boolean isFatalEnabled();
+
+  /**
+   * Is the logger instance enabled for the ERROR level?
+   * 
+   * @return True if this Logger is enabled for the ERROR level, false otherwise.
+   */
+  public boolean isErrorEnabled();
+
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerBridge.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerBridge.java
new file mode 100644
index 0000000..6dd033c
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerBridge.java
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+public interface LoggerBridge {
+
+  Logger getLogger(Class<?> clazz);
+
+}
diff --git a/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerFactory.java b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerFactory.java
new file mode 100644
index 0000000..2eea946
--- /dev/null
+++ b/src/main/java/org/apache/pdfbox/jbig2/util/log/LoggerFactory.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util.log;
+
+import java.util.Iterator;
+
+import org.apache.pdfbox.jbig2.util.ServiceLookup;
+
+/**
+ * Retrieves a {@link Logger} via registered {@link LoggerBridge} through META-INF/services lookup.
+ */
+public class LoggerFactory {
+
+  private static LoggerBridge loggerBridge;
+
+  private static ClassLoader clsLoader;
+
+  public static Logger getLogger(Class<?> cls, ClassLoader clsLoader) {
+    if (null == loggerBridge) {
+      final ServiceLookup<LoggerBridge> serviceLookup = new ServiceLookup<LoggerBridge>();
+      final Iterator<LoggerBridge> loggerBridgeServices = serviceLookup.getServices(LoggerBridge.class, clsLoader);
+
+      if (!loggerBridgeServices.hasNext()) {
+        throw new IllegalStateException("No implementation of " + LoggerBridge.class
+            + " was avaliable using META-INF/services lookup");
+      }
+      loggerBridge = loggerBridgeServices.next();
+    }
+    return loggerBridge.getLogger(cls);
+  }
+
+  public static Logger getLogger(Class<?> cls) {
+    return getLogger(cls, clsLoader != null ? clsLoader : LoggerBridge.class.getClassLoader());
+  }
+
+  public static void setClassLoader(ClassLoader clsLoader) {
+    LoggerFactory.clsLoader = clsLoader;
+  }
+
+}
diff --git a/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100644
index 0000000..8f70796
--- /dev/null
+++ b/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.apache.pdfbox.jbig2.JBIG2ImageReaderSpi
\ No newline at end of file
diff --git a/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.cache.CacheBridge b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.cache.CacheBridge
new file mode 100644
index 0000000..fa9ad12
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.cache.CacheBridge
@@ -0,0 +1,18 @@
+#

+# Licensed to the Apache Software Foundation (ASF) under one or more

+# contributor license agreements.  See the NOTICE file distributed with

+# this work for additional information regarding copyright ownership.

+# The ASF licenses this file to You under the Apache License, Version 2.0

+# (the "License"); you may not use this file except in compliance with

+# the License.  You may obtain a copy of the License at

+#

+#      http://www.apache.org/licenses/LICENSE-2.0

+#

+# Unless required by applicable law or agreed to in writing, software

+# distributed under the License is distributed on an "AS IS" BASIS,

+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+# See the License for the specific language governing permissions and

+# limitations under the License.

+#

+

+org.apache.pdfbox.jbig2.util.cache.SoftReferenceCacheBridge
\ No newline at end of file
diff --git a/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.log.LoggerBridge b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.log.LoggerBridge
new file mode 100644
index 0000000..03e4159
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.pdfbox.jbig2.util.log.LoggerBridge
@@ -0,0 +1,18 @@
+#

+# Licensed to the Apache Software Foundation (ASF) under one or more

+# contributor license agreements.  See the NOTICE file distributed with

+# this work for additional information regarding copyright ownership.

+# The ASF licenses this file to You under the Apache License, Version 2.0

+# (the "License"); you may not use this file except in compliance with

+# the License.  You may obtain a copy of the License at

+#

+#      http://www.apache.org/licenses/LICENSE-2.0

+#

+# Unless required by applicable law or agreed to in writing, software

+# distributed under the License is distributed on an "AS IS" BASIS,

+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+# See the License for the specific language governing permissions and

+# limitations under the License.

+#

+

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

+ * Licensed to the Apache Software Foundation (ASF) under one or more

+ * contributor license agreements.  See the NOTICE file distributed with

+ * this work for additional information regarding copyright ownership.

+ * The ASF licenses this file to You under the Apache License, Version 2.0

+ * (the "License"); you may not use this file except in compliance with

+ * the License.  You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+package org.apache.pdfbox.jbig2;

+

+import static org.apache.pdfbox.jbig2.ChecksumCalculator.*;

+import static org.apache.pdfbox.jbig2.JBIG2DocumentFacade.*;

+

+import java.io.InputStream;

+

+import javax.imageio.ImageIO;

+import javax.imageio.stream.ImageInputStream;

+

+import org.apache.pdfbox.jbig2.Bitmap;

+import org.apache.pdfbox.jbig2.JBIG2Document;

+import org.apache.pdfbox.jbig2.JBIG2Page;

+import org.junit.Assert;

+import org.junit.Test;

+

+/**

+ * Collection of tests for <a href="https://github.com/levigo/jbig2-imageio/issues">Github

+ * issues</a>.

+ */

+public class GithubIssuesTest {

+

+  /**

+   * <a href="https://github.com/levigo/jbig2-imageio/issues/21">Github issue 21s</a>

+   */

+  @Test

+  public void issue21() throws Exception {

+    final byte[] md5Expected = new byte[]{

+        83, 74, -69, -60, -122, -99, 21, 126, -115, 13, 9, 107, -31, -109, 77, -119

+    };

+

+    final InputStream imageStream = getClass().getResourceAsStream("/com/levigo/jbig2/github/21.jb2");

+    final InputStream globalsStream = getClass().getResourceAsStream("/com/levigo/jbig2/github/21.glob");

+    final ImageInputStream globalsIIS = ImageIO.createImageInputStream(globalsStream);

+    final ImageInputStream imageIIS = ImageIO.createImageInputStream(imageStream);

+

+    byte[] md5Actual = null;

+    try {

+      final JBIG2Document doc = doc(imageIIS, globalsIIS);

+      final JBIG2Page page = doc.getPage(1);

+      final Bitmap bitmap = page.getBitmap();

+      md5Actual = md5(bitmap);

+    } finally {

+      Assert.assertArrayEquals(md5Expected, md5Actual);

+

+      globalsIIS.close();

+      globalsStream.close();

+      imageIIS.close();

+      imageStream.close();

+    }

+

+  }

+}

diff --git a/src/test/java/org/apache/pdfbox/jbig2/JBIG2DocumentFacade.java b/src/test/java/org/apache/pdfbox/jbig2/JBIG2DocumentFacade.java
new file mode 100644
index 0000000..e38c09d
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/JBIG2DocumentFacade.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.io.IOException;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2Document;
+import org.apache.pdfbox.jbig2.JBIG2Globals;
+import org.apache.pdfbox.jbig2.JBIG2Page;
+import org.apache.pdfbox.jbig2.err.JBIG2Exception;
+
+public class JBIG2DocumentFacade extends JBIG2Document {
+  
+  public static JBIG2Document doc(ImageInputStream doc, ImageInputStream globals) throws IOException {
+    final JBIG2Document globalsDoc = new JBIG2Document(globals);
+    return new JBIG2Document(doc, globalsDoc.getGlobalSegments());
+  }
+
+  public JBIG2DocumentFacade(ImageInputStream input) throws IOException {
+    super(input);
+  }
+
+  public JBIG2DocumentFacade(ImageInputStream input, JBIG2Globals globals) throws IOException {
+    super(input, globals);
+  }
+
+  public JBIG2Page getPage(int pageNumber) {
+    return super.getPage(pageNumber);
+  }
+
+  public Bitmap getPageBitmap(int pageNumber) throws JBIG2Exception, IOException {
+    return getPage(pageNumber).getBitmap();
+  }
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/JBIG2ImageReaderDemo.java b/src/test/java/org/apache/pdfbox/jbig2/JBIG2ImageReaderDemo.java
new file mode 100644
index 0000000..bb253cc
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/JBIG2ImageReaderDemo.java
@@ -0,0 +1,79 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+
+import javax.imageio.ImageReadParam;
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2Document;
+import org.apache.pdfbox.jbig2.JBIG2ImageReader;
+import org.apache.pdfbox.jbig2.JBIG2ImageReaderSpi;
+import org.apache.pdfbox.jbig2.TestImage;
+import org.apache.pdfbox.jbig2.err.JBIG2Exception;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.image.FilterType;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+
+public class JBIG2ImageReaderDemo {
+
+  private String filepath;
+  private int imageIndex;
+
+  public JBIG2ImageReaderDemo(String filepath, int imageIndex) {
+    this.filepath = filepath;
+    this.imageIndex = imageIndex;
+  }
+
+  public void show() throws IOException, JBIG2Exception {
+    InputStream inputStream = new FileInputStream(new File(filepath));
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream imageInputStream = disf.getInputStream(inputStream);
+
+    JBIG2ImageReader imageReader = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
+
+    imageReader.setInput(imageInputStream);
+    ImageReadParam param = new PreconfiguredImageReadParam(new Rectangle(100, 100, 500, 500));
+
+    long timeStamp = System.currentTimeMillis();
+
+    final JBIG2Document doc = new JBIG2Document(imageInputStream);
+    final Bitmap bitmap = doc.getPage(imageIndex).getBitmap();
+    final BufferedImage bufferedImage = Bitmaps.asBufferedImage(bitmap, param, FilterType.Lanczos);
+    long duration = System.currentTimeMillis() - timeStamp;
+    System.out.println(filepath + " decoding took " + duration + " ms");
+
+    new TestImage(bufferedImage);
+  }
+
+  public static void main(String[] args) throws InterruptedException, InvocationTargetException, IOException,
+      JBIG2Exception {
+    URL imageUrl = JBIG2ImageReaderDemo.class.getResource("/images/042_1.jb2");
+    new JBIG2ImageReaderDemo(imageUrl.getPath(), 1).show();
+  }
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/JBIG2ImageReaderTest.java b/src/test/java/org/apache/pdfbox/jbig2/JBIG2ImageReaderTest.java
new file mode 100644
index 0000000..9da7b08
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/JBIG2ImageReaderTest.java
@@ -0,0 +1,141 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+
+import junit.framework.Assert;
+
+import org.apache.pdfbox.jbig2.JBIG2ImageReader;
+import org.apache.pdfbox.jbig2.JBIG2ImageReaderSpi;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.junit.Test;
+
+public class JBIG2ImageReaderTest {
+
+  @Test
+  public void testGetDefaultReadParams() throws Exception {
+    ImageReader reader = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
+    ImageReadParam param = reader.getDefaultReadParam();
+    Assert.assertNotNull(param);
+
+    Assert.assertNull(param.getSourceRegion());
+    Assert.assertNull(param.getSourceRenderSize());
+
+    Assert.assertEquals(1, param.getSourceXSubsampling());
+    Assert.assertEquals(1, param.getSourceYSubsampling());
+    Assert.assertEquals(0, param.getSubsamplingXOffset());
+    Assert.assertEquals(0, param.getSubsamplingYOffset());
+  }
+
+  @Test
+  public void testRead() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    String filepath = "/images/042_1.jb2";
+    int imageIndex = 0;
+
+    InputStream inputStream = getClass().getResourceAsStream(filepath);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream imageInputStream = disf.getInputStream(inputStream);
+
+    JBIG2ImageReader imageReader = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
+    imageReader.setInput(imageInputStream);
+
+    // long timeStamp = System.currentTimeMillis();
+    BufferedImage bufferedImage = imageReader.read(imageIndex, imageReader.getDefaultReadParam());
+    // long duration = System.currentTimeMillis() - timeStamp;
+    // System.out.println(filepath + " decoding took " + duration + " ms");
+
+    Assert.assertNotNull(bufferedImage);
+  }
+
+  @Test
+  public void testReadRaster() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    String filepath = "/images/042_1.jb2";
+    int imageIndex = 0;
+
+    InputStream inputStream = getClass().getResourceAsStream(filepath);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream imageInputStream = disf.getInputStream(inputStream);
+
+    JBIG2ImageReader imageReader = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
+    imageReader.setInput(imageInputStream);
+    Raster raster = imageReader.readRaster(imageIndex, imageReader.getDefaultReadParam());
+
+    Assert.assertNotNull(raster);
+  }
+
+  @Test
+  public void testReadImageReadParamNull() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    String filepath = "/images/042_1.jb2";
+    int imageIndex = 0;
+
+    InputStream inputStream = getClass().getResourceAsStream(filepath);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream imageInputStream = disf.getInputStream(inputStream);
+    JBIG2ImageReader imageReader = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
+    imageReader.setInput(imageInputStream);
+    BufferedImage bufferedImage = imageReader.read(imageIndex, null);
+
+    Assert.assertNotNull(bufferedImage);
+  }
+
+  @Test
+  public void testReadRasterImageReadParamNull() throws IOException, InvalidHeaderValueException,
+      IntegerMaxValueException {
+    String filepath = "/images/042_1.jb2";
+    int imageIndex = 0;
+
+    InputStream inputStream = getClass().getResourceAsStream(filepath);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream imageInputStream = disf.getInputStream(inputStream);
+    JBIG2ImageReader imageReader = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
+    imageReader.setInput(imageInputStream);
+    Raster raster = imageReader.readRaster(imageIndex, null);
+
+    Assert.assertNotNull(raster);
+  }
+
+  @Test
+  public void testGetNumImages() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    String filepath = "/images/002.jb2";
+
+    InputStream inputStream = getClass().getResourceAsStream(filepath);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream imageInputStream = disf.getInputStream(inputStream);
+    JBIG2ImageReader imageReader = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
+    imageReader.setInput(imageInputStream);
+    int numImages = imageReader.getNumImages(true);
+    Assert.assertEquals(17, numImages);
+  }
+
+  @Test
+  public void testCanReadRaster() throws IOException {
+    JBIG2ImageReader imageReader = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
+    Assert.assertTrue(imageReader.canReadRaster());
+  }
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/JBIG2PageTest.java b/src/test/java/org/apache/pdfbox/jbig2/JBIG2PageTest.java
new file mode 100644
index 0000000..1fb6ae5
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/JBIG2PageTest.java
@@ -0,0 +1,121 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2Document;
+import org.apache.pdfbox.jbig2.JBIG2ReadParam;
+import org.apache.pdfbox.jbig2.TestImage;
+import org.apache.pdfbox.jbig2.err.JBIG2Exception;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.image.FilterType;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class JBIG2PageTest {
+
+  // TESTS WITH TESTOUTPUT
+  // Ignore in build process
+
+  @Ignore
+  @Test
+  public void composeDisplayTest() throws IOException, JBIG2Exception {
+
+    String filepath = "/images/amb_1.jb2";
+    int pageNumber = 1;
+
+    InputStream is = getClass().getResourceAsStream(filepath);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    JBIG2Document doc = new JBIG2Document(iis);
+
+    Bitmap pageBitmap = doc.getPage(pageNumber).getBitmap();
+    BufferedImage b = Bitmaps.asBufferedImage(pageBitmap, new JBIG2ReadParam(1, 1, 0, 0, new Rectangle(166, 333, 555,
+        444), null), FilterType.Gaussian);
+    new TestImage(b);
+  }
+
+  @Ignore
+  @Test
+  public void composeTestWithDurationCalc() throws IOException, JBIG2Exception {
+    int runs = 40;
+    long avg = 0;
+
+    String path = "/images/042_8.jb2";
+    int pageNumber = 1;
+
+    System.out.println("File: " + path);
+
+    InputStream is = getClass().getResourceAsStream(path);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+
+    for (int i = 0; i < runs; i++) {
+
+      long time = System.currentTimeMillis();
+      JBIG2Document doc = new JBIG2Document(iis);
+      Bitmap pageBitmap = doc.getPage(pageNumber).getBitmap();
+      Bitmaps.asBufferedImage(pageBitmap);
+      long duration = System.currentTimeMillis() - time;
+
+      System.out.println((i + 1) + ": " + duration + " ms");
+      avg += duration;
+    }
+    System.out.println("Average: " + avg / runs);
+  }
+
+  @Ignore
+  @Test
+  public void composeTestWithDurationCalcAggregate() throws IOException, JBIG2Exception {
+    int runs = 40;
+    long avg = 0;
+    String path = "/images/002.jb2";
+    int pages = 17;
+
+    System.out.println("File: " + path);
+
+    InputStream is = getClass().getResourceAsStream(path);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+
+    for (int j = 1; j <= pages; j++) {
+      avg = 0;
+
+      for (int i = 0; i < runs; i++) {
+        long time = System.currentTimeMillis();
+        JBIG2Document doc = new JBIG2Document(iis);
+        Bitmap pageBitmap = doc.getPage(j).getBitmap();
+        Bitmaps.asBufferedImage(pageBitmap);
+        long duration = System.currentTimeMillis() - time;
+        System.out.print((i + 1) + ": " + duration + " ms ");
+        avg += duration;
+      }
+      System.out.println();
+      System.out.println("Page " + j + " Average: " + avg / runs);
+    }
+  }
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/PreconfiguredImageReadParam.java b/src/test/java/org/apache/pdfbox/jbig2/PreconfiguredImageReadParam.java
new file mode 100644
index 0000000..b8f58c0
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/PreconfiguredImageReadParam.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+
+import javax.imageio.ImageReadParam;
+
+public class PreconfiguredImageReadParam extends ImageReadParam {
+  public PreconfiguredImageReadParam(Rectangle sourceRegion) {
+    this.sourceRegion = sourceRegion;
+  }
+
+  public PreconfiguredImageReadParam(Dimension sourceRenderSize) {
+    this.sourceRenderSize = sourceRenderSize;
+  }
+
+  public PreconfiguredImageReadParam(int sourceXSubsampling, int sourceYSubsampling, int subsamplingXOffset,
+      int subsamplingYOffset) {
+    this.sourceXSubsampling = sourceXSubsampling;
+    this.sourceYSubsampling = sourceYSubsampling;
+    this.subsamplingXOffset = subsamplingXOffset;
+    this.subsamplingYOffset = subsamplingYOffset;
+  }
+
+  public PreconfiguredImageReadParam(Rectangle sourceRegion, Dimension sourceRenderSize) {
+    this.sourceRegion = sourceRegion;
+    this.sourceRenderSize = sourceRenderSize;
+  }
+
+  public PreconfiguredImageReadParam(Rectangle sourceRegion, Dimension sourceRenderSize, int sourceXSubsampling,
+      int sourceYSubsampling, int subsamplingXOffset, int subsamplingYOffset) {
+    this.sourceRegion = sourceRegion;
+    this.sourceRenderSize = sourceRenderSize;
+    this.sourceXSubsampling = sourceXSubsampling;
+    this.sourceYSubsampling = sourceYSubsampling;
+    this.subsamplingXOffset = subsamplingXOffset;
+    this.subsamplingYOffset = subsamplingYOffset;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticDecoderTest.java b/src/test/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticDecoderTest.java
new file mode 100644
index 0000000..b29d03c
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticDecoderTest.java
@@ -0,0 +1,577 @@
+/**
+ * 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.arithmetic;
+
+import java.io.InputStream;
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.CX;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.junit.*;
+
+public class ArithmeticDecoderTest {
+
+  long[][] tracedata = { //
+      {
+          0L, 0x8000L, 0x42638000L, 0x3D9C0000L
+      }, {
+          0L, 0xAC02L, 0x84C70000L, 0x273A0000L
+      }, {
+          0L, 0xF002L, 0xA18C7600L, 0x4E758800L
+      }, {
+          0L, 0xD801L, 0x898B7600L, 0x4E758800L
+      }, {
+          0L, 0xC000L, 0x718A7600L, 0x4E758800L
+      }, {
+          0L, 0xA7FFL, 0x59897600L, 0x4E758800L
+      }, {
+          0L, 0x8FFEL, 0x41887600L, 0x4E758800L
+      }, {
+          0L, 0xEFFAL, 0x530EEC00L, 0x9CEB1000L
+      }, {
+          0L, 0xE539L, 0x484DEC00L, 0x9CEB1000L
+      }, {
+          0L, 0xDA78L, 0x3D8CEC00L, 0x9CEB1000L
+      }, {
+          0L, 0xCFB7L, 0x32CBEC00L, 0x9CEB1000L
+      }, {
+          0L, 0xC4F6L, 0x280AEC00L, 0x9CEB1000L
+      }, {
+          0L, 0xBA35L, 0x1D49EC00L, 0x9CEB1000L
+      }, {
+          0L, 0xAF74L, 0x1288EC00L, 0x9CEB1000L
+      }, {
+          1L, 0xA4B3L, 0x07C7EC00L, 0x9CEB1000L
+      }, {
+          0L, 0xAC10L, 0x7C7EC000L, 0x2F910000L
+      }, {
+          0L, 0x900FL, 0x607DC000L, 0x2F910000L
+      }, {
+          0L, 0xE81CL, 0x88F98000L, 0x5F220000L
+      }, {
+          0L, 0xD21BL, 0x72F88000L, 0x5F220000L
+      }, {
+          0L, 0xBC1AL, 0x5CF78000L, 0x5F220000L
+      }, {
+          0L, 0xA619L, 0x46F68000L, 0x5F220000L
+      }, {
+          0L, 0x9018L, 0x30F58000L, 0x5F220000L
+      }, {
+          0L, 0xF42EL, 0x35E90000L, 0xBE440000L
+      }, {
+          0L, 0xE32DL, 0x24E80000L, 0xBE440000L
+      }, {
+          0L, 0xD22CL, 0x13E70000L, 0xBE440000L
+      }, {
+          1L, 0xC12BL, 0x02E60000L, 0xBE440000L
+      }, {
+          0L, 0x8808L, 0x1737E000L, 0x70D01800L
+      }, {
+          1L, 0xE80EL, 0x066DC000L, 0xE1A03000L
+      }, {
+          0L, 0x9008L, 0x336E0000L, 0x5C998000L
+      }, {
+          0L, 0xF40EL, 0x3ADA0000L, 0xB9330000L
+      }, {
+          0L, 0xE00DL, 0x26D90000L, 0xB9330000L
+      }, {
+          1L, 0xCC0CL, 0x12D80000L, 0xB9330000L
+      }, {
+          0L, 0xA008L, 0x96C70800L, 0x0940F000L
+      }, {
+          0L, 0x8807L, 0x7EC60800L, 0x0940F000L
+      }, {
+          0L, 0xE00CL, 0xCD8A1000L, 0x1281E000L
+      }, {
+          0L, 0xCA0BL, 0xB7891000L, 0x1281E000L
+      }, {
+          0L, 0xB40AL, 0xA1881000L, 0x1281E000L
+      }, {
+          0L, 0x9E09L, 0x8B871000L, 0x1281E000L
+      }, {
+          0L, 0x8808L, 0x75861000L, 0x1281E000L
+      }, {
+          0L, 0xE40EL, 0xBF0A2000L, 0x2503C000L
+      }, {
+          0L, 0xD00DL, 0xAB092000L, 0x2503C000L
+      }, {
+          0L, 0xBC0CL, 0x97082000L, 0x2503C000L
+      }, {
+          0L, 0xA80BL, 0x83072000L, 0x2503C000L
+      }, {
+          0L, 0x940AL, 0x6F062000L, 0x2503C000L
+      }, {
+          0L, 0x8009L, 0x5B052000L, 0x2503C000L
+      }, {
+          0L, 0xD810L, 0x8E084000L, 0x4A078000L
+      }, {
+          0L, 0xC60FL, 0x7C074000L, 0x4A078000L
+      }, {
+          0L, 0xB40EL, 0x6A064000L, 0x4A078000L
+      }, {
+          0L, 0xA20DL, 0x58054000L, 0x4A078000L
+      }, {
+          0L, 0x900CL, 0x46044000L, 0x4A078000L
+      }, {
+          0L, 0xFC16L, 0x68068000L, 0x940F0000L
+      }, {
+          0L, 0xEB15L, 0x57058000L, 0x940F0000L
+      }, {
+          0L, 0xDA14L, 0x46048000L, 0x940F0000L
+      }, {
+          0L, 0xC913L, 0x35038000L, 0x940F0000L
+      }, {
+          0L, 0xB812L, 0x24028000L, 0x940F0000L
+      }, {
+          0L, 0xA711L, 0x13018000L, 0x940F0000L
+      }, {
+          1L, 0x9610L, 0x02008000L, 0x940F0000L
+      }, {
+          1L, 0x8808L, 0x10068400L, 0x78017800L
+      }, {
+          0L, 0xA008L, 0x80342000L, 0x1FD3C000L
+      }, {
+          0L, 0x8807L, 0x68332000L, 0x1FD3C000L
+      }, {
+          0L, 0xE00CL, 0xA0644000L, 0x3FA78000L
+      }, {
+          0L, 0xCA0BL, 0x8A634000L, 0x3FA78000L
+      }, {
+          0L, 0xB40AL, 0x74624000L, 0x3FA78000L
+      }, {
+          0L, 0x9E09L, 0x5E614000L, 0x3FA78000L
+      }, {
+          0L, 0x8808L, 0x48604000L, 0x3FA78000L
+      }, {
+          0L, 0xE40EL, 0x64BE8000L, 0x7F4F0000L
+      }, {
+          0L, 0xD00DL, 0x50BD8000L, 0x7F4F0000L
+      }, {
+          0L, 0xBC0CL, 0x3CBC8000L, 0x7F4F0000L
+      }, {
+          0L, 0xA80BL, 0x28BB8000L, 0x7F4F0000L
+      }, {
+          0L, 0x940AL, 0x14BA8000L, 0x7F4F0000L
+      }, {
+          1L, 0x8009L, 0x00B98000L, 0x7F4F0000L
+      }, {
+          1L, 0xA008L, 0x05CD0C00L, 0x9A3AF000L
+      }, {
+          0L, 0xC008L, 0x2E686000L, 0x919F8000L
+      }, {
+          1L, 0x9E07L, 0x0C676000L, 0x919F8000L
+      }, {
+          0L, 0x8804L, 0x319D8000L, 0x56660000L
+      }, {
+          1L, 0xC006L, 0x13390000L, 0xACCC0000L
+      }, {
+          0L, 0x9004L, 0x4CE41000L, 0x431FEC00L
+      }, {
+          0L, 0xC006L, 0x39C62000L, 0x863FD800L
+      }, {
+          1L, 0x9805L, 0x11C52000L, 0x863FD800L
+      }, {
+          0L, 0xA004L, 0x47148000L, 0x58EF6000L
+      }, {
+          1L, 0xD806L, 0x26270000L, 0xB1DEC000L
+      }, {
+          0L, 0xC004L, 0x989C0000L, 0x27670000L
+      }, {
+          0L, 0x8C03L, 0x649B0000L, 0x27670000L
+      }, {
+          0L, 0xB004L, 0x61340400L, 0x4ECFFA00L
+      }, {
+          0L, 0x8003L, 0x31330400L, 0x4ECFFA00L
+      }, {
+          1L, 0xA004L, 0x02640800L, 0x9D9FF400L
+      }, {
+          1L, 0xA004L, 0x09902000L, 0x9673D000L
+      }, {
+          1L, 0xD004L, 0x26408000L, 0xA9C34000L
+      }, {
+          0L, 0xE004L, 0x99020000L, 0x47010000L
+      }, {
+          0L, 0x9803L, 0x51010000L, 0x47010000L
+      }, {
+          1L, 0xA004L, 0x12004000L, 0x8E03BE00L
+      }, {
+          0L, 0xE004L, 0x48010000L, 0x9802F800L
+      }, {
+          1L, 0x9803L, 0x00000000L, 0x9802F800L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x9001F000L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA201E000L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x9001C000L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA2018000L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x90010000L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA201FE00L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x9001FC00L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA201F800L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x9001F000L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA201E000L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x9001C000L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA2018000L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x90010000L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA201FE00L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x9001FC00L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA201F800L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x9001F000L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA201E000L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x9001C000L
+      }, {
+          1L, 0xA202L, 0x00000000L, 0xA2018000L
+      }, {
+          0L, 0x9002L, 0x00000000L, 0x90010000L
+      }, {
+          1L, 0xA202L, 0x00008200L, 0xA2017C00L
+      }, {
+          0L, 0x9002L, 0x00010400L, 0x9000F800L
+      }, {
+          1L, 0xA202L, 0x00020800L, 0xA1FFF000L
+      }, {
+          0L, 0x9002L, 0x00041000L, 0x8FFDE000L
+      }, {
+          1L, 0xA202L, 0x00082000L, 0xA1F9C000L
+      }, {
+          0L, 0x9002L, 0x00104000L, 0x8FF18000L
+      }, {
+          1L, 0xA202L, 0x00208000L, 0xA1E10000L
+      }, {
+          0L, 0x9002L, 0x00410000L, 0x8FC00000L
+      }, {
+          1L, 0xA202L, 0x00821A00L, 0xA17FE400L
+      }, {
+          0L, 0x9002L, 0x01043400L, 0x8EFDC800L
+      }, {
+          1L, 0xA202L, 0x02086800L, 0x9FF99000L
+      }, {
+          0L, 0x9002L, 0x0410D000L, 0x8BF12000L
+      }, {
+          1L, 0xA202L, 0x0821A000L, 0x99E04000L
+      }, {
+          0L, 0x9002L, 0x10434000L, 0x7FBE8000L
+      }, {
+          1L, 0xA202L, 0x20868000L, 0x817B0000L
+      }, {
+          0L, 0x9002L, 0x410D0000L, 0x4EF40000L
+      }, {
+          0L, 0xA202L, 0x821B7600L, 0x1FE68800L
+      }, {
+          0L, 0xB402L, 0x7434EC00L, 0x3FCD1000L
+      }, {
+          0L, 0xF802L, 0x7867D800L, 0x7F9A2000L
+      }, {
+          0L, 0xC401L, 0x4466D800L, 0x7F9A2000L
+      }, {
+          1L, 0x9000L, 0x1065D800L, 0x7F9A2000L
+      }, {
+          0L, 0xD004L, 0x41976000L, 0x8E6C8000L
+      }, {
+          1L, 0x9803L, 0x09966000L, 0x8E6C8000L
+      }, {
+          1L, 0xE004L, 0x26598000L, 0xB9AA0000L
+      }, {
+          0L, 0x9002L, 0x4CB30000L, 0x434E0000L
+      }, {
+          0L, 0xA202L, 0x99670C00L, 0x089AF200L
+      }, {
+          0L, 0xB402L, 0xA2CC1800L, 0x1135E400L
+      }, {
+          0L, 0xF802L, 0xD5963000L, 0x226BC800L
+      }, {
+          0L, 0xC401L, 0xA1953000L, 0x226BC800L
+      }, {
+          0L, 0x9000L, 0x6D943000L, 0x226BC800L
+      }, {
+          0L, 0xB7FEL, 0x73266000L, 0x44D79000L
+      }, {
+          0L, 0x87FDL, 0x43256000L, 0x44D79000L
+      }, {
+          1L, 0xAFF8L, 0x2648C000L, 0x89AF2000L
+      }, {
+          0L, 0xA004L, 0x99230000L, 0x06E08000L
+      }, {
+          0L, 0xD806L, 0xCA440000L, 0x0DC10000L
+      }, {
+          0L, 0xA805L, 0x9A430000L, 0x0DC10000L
+      }, {
+          0L, 0xF008L, 0xD485E800L, 0x1B821600L
+      }, {
+          0L, 0xC807L, 0xAC84E800L, 0x1B821600L
+      }, {
+          0L, 0xA006L, 0x8483E800L, 0x1B821600L
+      }, {
+          0L, 0xF00AL, 0xB905D000L, 0x37042C00L
+      }, {
+          0L, 0xCC09L, 0x9504D000L, 0x37042C00L
+      }, {
+          0L, 0xA808L, 0x7103D000L, 0x37042C00L
+      }, {
+          0L, 0x8407L, 0x4D02D000L, 0x37042C00L
+      }, {
+          0L, 0xC00CL, 0x5203A000L, 0x6E085800L
+      }, {
+          0L, 0x9E0BL, 0x3002A000L, 0x6E085800L
+      }, {
+          0L, 0xF814L, 0x1C034000L, 0xDC10B000L
+      }, {
+          1L, 0xDC13L, 0x00024000L, 0xDC10B000L
+      }, {
+          1L, 0xE008L, 0x00120000L, 0xDFF58000L
+      }, {
+          1L, 0x9004L, 0x00486200L, 0x8FBB9C00L
+      }, {
+          1L, 0xC004L, 0x01218800L, 0xBEE27000L
+      }, {
+          1L, 0xD004L, 0x04862000L, 0xCB7DC000L
+      }, {
+          1L, 0xE004L, 0x12188000L, 0xCDEB0000L
+      }, {
+          0L, 0x9002L, 0x24310000L, 0x6BD00000L
+      }, {
+          0L, 0xA202L, 0x4862FE00L, 0x599F0000L
+      }, {
+          1L, 0xB402L, 0x00C3FC00L, 0xB33E0000L
+      }, {
+          1L, 0xE004L, 0x030FF000L, 0xDCF40000L
+      }, {
+          0L, 0x9002L, 0x061FE000L, 0x89E20000L
+      }, {
+          1L, 0xA202L, 0x0C3FC000L, 0x95C20000L
+      }, {
+          0L, 0x9002L, 0x187F8000L, 0x77820000L
+      }, {
+          1L, 0xA202L, 0x30FF0000L, 0x71020000L
+      }, {
+          1L, 0x9002L, 0x61FFFE00L, 0x2E020000L
+      }, {
+          1L, 0xFC04L, 0x43FBF800L, 0xB8080000L
+      }, {
+          1L, 0xA802L, 0x87F7F000L, 0x200A0000L
+      }, {
+          0L, 0xA402L, 0x63EDE000L, 0x40140000L
+      }, {
+          0L, 0x9C02L, 0x1BD9C000L, 0x80280000L
+      }, {
+          1L, 0xAC02L, 0x37B38000L, 0x744E0000L
+      }, {
+          1L, 0xA802L, 0x6F670000L, 0x389A0000L
+      }, {
+          1L, 0xA402L, 0x32CE2000L, 0x7133DC00L
+      }, {
+          1L, 0xAC02L, 0x659C4000L, 0x4665B800L
+      }, {
+          0L, 0xB002L, 0x23368000L, 0x8CCB7000L
+      }, {
+          1L, 0xA202L, 0x466D0000L, 0x5B94E000L
+      }, {
+          1L, 0xA802L, 0x8CDA0000L, 0x1B27C000L
+      }, {
+          1L, 0xAE02L, 0x77B20000L, 0x364F8000L
+      }, {
+          1L, 0xCC02L, 0x5F620000L, 0x6C9F0000L
+      }, {
+          0L, 0x9401L, 0x27610000L, 0x6C9F0000L
+      }, {
+          1L, 0xE004L, 0x9D87FC00L, 0x427C0000L
+      }, {
+          1L, 0x9803L, 0x5586FC00L, 0x427C0000L
+      }, {
+          0L, 0xA004L, 0x1B0BF800L, 0x84F80000L
+      }, {
+          1L, 0xE004L, 0x6C2FE000L, 0x73D40000L
+      }, {
+          0L, 0x9803L, 0x242EE000L, 0x73D40000L
+      }, {
+          1L, 0x9002L, 0x485DC000L, 0x47A40000L
+      }, {
+          1L, 0xA202L, 0x90BB8000L, 0x11460000L
+      }, {
+          1L, 0xB402L, 0x91750000L, 0x228C0000L
+      }, {
+          1L, 0xF802L, 0xB2E8DC00L, 0x45192000L
+      }, {
+          1L, 0xC401L, 0x7EE7DC00L, 0x45192000L
+      }, {
+          1L, 0x9000L, 0x4AE6DC00L, 0x45192000L
+      }, {
+          0L, 0xB7FEL, 0x2DCBB800L, 0x8A324000L
+      }, {
+          1L, 0xC004L, 0xB72EE000L, 0x08D50000L
+      }, {
+          1L, 0x8C03L, 0x832DE000L, 0x08D50000L
+      }, {
+          1L, 0xB004L, 0x9E59C000L, 0x11AA0000L
+      }, {
+          1L, 0x8003L, 0x6E58C000L, 0x11AA0000L
+      }, {
+          1L, 0xA004L, 0x7CAF8000L, 0x23540000L
+      }, {
+          1L, 0xF006L, 0xA95D0000L, 0x46A80000L
+      }, {
+          1L, 0xCC05L, 0x855C0000L, 0x46A80000L
+      }, {
+          1L, 0xA804L, 0x615B0000L, 0x46A80000L
+      }, {
+          1L, 0x8403L, 0x3D5A0000L, 0x46A80000L
+      }, {
+          1L, 0xC004L, 0x32B28E00L, 0x8D517000L
+      }, {
+          0L, 0x9E03L, 0x10B18E00L, 0x8D517000L
+      }, {
+          1L, 0x8804L, 0x42C63800L, 0x453DC000L
+      }, {
+          1L, 0xC006L, 0x358A7000L, 0x8A7B8000L
+      }, {
+          0L, 0x9C05L, 0x11897000L, 0x8A7B8000L
+      }, {
+          1L, 0x9004L, 0x4625C000L, 0x49DE0000L
+      }, {
+          1L, 0xC006L, 0x2C498000L, 0x93BC0000L
+      }, {
+          0L, 0x9805L, 0x04488000L, 0x93BC0000L
+      }, {
+          0L, 0xA004L, 0x11223400L, 0x8EE1CA00L
+      }, {
+          1L, 0xD004L, 0x4488D000L, 0x8B7B2800L
+      }, {
+          0L, 0x9803L, 0x0C87D000L, 0x8B7B2800L
+      }, {
+          0L, 0xE004L, 0x321F4000L, 0xADE4A000L
+      }, {
+          0L, 0x9002L, 0x643E8000L, 0x2BC34000L
+      }, {
+          0L, 0xFC04L, 0x4CF60000L, 0xAF0D0000L
+      }, {
+          0L, 0xA802L, 0x99EDB600L, 0x0E144800L
+      }, {
+          1L, 0xA402L, 0x87D96C00L, 0x1C289000L
+      }, {
+          0L, 0x9C02L, 0x63B0D800L, 0x38512000L
+      }, {
+          0L, 0x8C02L, 0x1B5FB000L, 0x70A24000L
+      }, {
+          1L, 0xAC02L, 0x36BF6000L, 0x75428000L
+      }, {
+          1L, 0xA802L, 0x6D7EC000L, 0x3A830000L
+      }, {
+          1L, 0xA402L, 0x2EFB8000L, 0x75060000L
+      }, {
+          1L, 0xAC02L, 0x5DF70000L, 0x4E0A0000L
+      }, {
+          0L, 0xB002L, 0x13ECD400L, 0x9C152A00L
+      }, {
+          1L, 0xA202L, 0x27D9A800L, 0x7A285400L
+      }, {
+          0L, 0xA802L, 0x4FB35000L, 0x584EA800L
+      }, {
+          0L, 0xA202L, 0x9F66A000L, 0x029B5000L
+      }, {
+          0L, 0x9C02L, 0x96CB4000L, 0x0536A000L
+      }, {
+          1L, 0x8C02L, 0x81948000L, 0x0A6D4000L
+      }, {
+          1L, 0xD804L, 0xAE4E0000L, 0x29B50000L
+      }, {
+          0L, 0x8203L, 0x584D0000L, 0x29B50000L
+      }, {
+          1L, 0xB008L, 0x09337C00L, 0xA6D48000L
+      }, {
+          0L, 0xAC02L, 0x1266F800L, 0x999B0000L
+      }, {
+          1L, 0xAC02L, 0x24CDF000L, 0x87340000L
+      }, {
+          0L, 0xAC02L, 0x499BE000L, 0x62660000L
+      }, {
+          0L, 0xAC02L, 0x9337C000L, 0x18CA0000L
+      }, {
+          0L, 0xAC02L, 0x7A6D8000L, 0x31940000L
+      }, {
+          1L, 0xB002L, 0x4CD90000L, 0x63280000L
+      }, {
+          1L, 0xA202L, 0x99B3FE00L, 0x084E0000L
+      }, {
+          1L, 0x9C02L, 0x8B65FC00L, 0x109C0000L
+      }, {
+          0L, 0x8C02L, 0x6AC9F800L, 0x21380000L
+      }, {
+          1L, 0xD804L, 0x5323E000L, 0x84E00000L
+      }, {
+          1L, 0xAC02L, 0xA647C000L, 0x05BA0000L
+      }, {
+          1L, 0xAC02L, 0xA08D8000L, 0x0B740000L
+      }, {
+          1L, 0xB002L, 0x99190000L, 0x16E80000L
+      }, {
+          1L, 0xBE02L, 0x9031FE00L, 0x2DD00000L
+      }, {
+          1L, 0xEC02L, 0x9061FC00L, 0x5BA00000L
+      },
+  };
+
+  @Test
+  public void decodeTest() throws Throwable {
+    InputStream is = getClass().getResourceAsStream("/images/arith/encoded testsequence");
+    DefaultInputStreamFactory factory = new DefaultInputStreamFactory();
+    ImageInputStream iis = factory.getInputStream(is);
+
+    ArithmeticDecoder decoder = new ArithmeticDecoder(iis);
+
+    CX cx = new CX(1, 0);
+    for (int i = 0; i < 257; i++) {
+      decoder.decode(cx);
+    }
+
+  }
+
+  @Test
+  public void decodeTestWithTracadataComparison() throws Throwable {
+    InputStream is = getClass().getResourceAsStream("/images/arith/encoded testsequence");
+    DefaultInputStreamFactory factory = new DefaultInputStreamFactory();
+    ImageInputStream iis = factory.getInputStream(is);
+
+    ArithmeticDecoder decoder = new ArithmeticDecoder(iis);
+    CX cx = new CX(1, 0);
+
+    for (int i = 0; i < 255; i++) {
+      Assert.assertEquals(tracedata[i][0], decoder.decode(cx));
+      Assert.assertEquals(tracedata[i + 1][1], (long) decoder.getA());
+      Assert.assertEquals(tracedata[i + 1][2], decoder.getC());
+
+    }
+  }
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticIntegerDecoderTest.java b/src/test/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticIntegerDecoderTest.java
new file mode 100644
index 0000000..a17b8c6
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/decoder/arithmetic/ArithmeticIntegerDecoderTest.java
@@ -0,0 +1,46 @@
+/**
+ * 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.arithmetic;
+
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+
+import junit.framework.Assert;
+
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticDecoder;
+import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticIntegerDecoder;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.junit.Test;
+
+public class ArithmeticIntegerDecoderTest {
+
+  @Test
+  public void decodeTest() throws Throwable {
+    InputStream is = getClass().getResourceAsStream("/images/arith/encoded testsequence");
+    DefaultInputStreamFactory isFactory = new DefaultInputStreamFactory();
+    ImageInputStream iis = isFactory.getInputStream(is);
+
+    ArithmeticDecoder ad = new ArithmeticDecoder(iis);
+    ArithmeticIntegerDecoder aid = new ArithmeticIntegerDecoder(ad);
+
+    long result = aid.decode(null);
+
+    Assert.assertEquals(1, result);
+  }
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRDecompressorTest.java b/src/test/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRDecompressorTest.java
new file mode 100644
index 0000000..9b3baba
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/decoder/mmr/MMRDecompressorTest.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.decoder.mmr;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.decoder.mmr.MMRDecompressor;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.junit.Test;
+
+public class MMRDecompressorTest {
+
+  @Test
+  public void mmrDecodingTest() throws IOException, InvalidHeaderValueException {
+    final byte[] expected = new byte[]{
+        0, 0, 2, 34, 38, 102, -17, -1, 2, 102, 102, //
+        -18, -18, -17, -1, -1, 0, 2, 102, 102, 127, //
+        -1, -1, -1, 0, 0, 0, 4, 68, 102, 102, 127
+    };
+
+    final InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    final DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    final ImageInputStream iis = disf.getInputStream(is);
+
+    // Sixth Segment (number 5)
+    final SubInputStream sis = new SubInputStream(iis, 252, 38);
+
+    final MMRDecompressor mmrd = new MMRDecompressor(16 * 4, 4, sis);
+
+    final Bitmap b = mmrd.uncompress();
+    final byte[] actual = b.getByteArray();
+
+    assertArrayEquals(expected, actual);
+
+    // new TestImage(b.getByteArray(), (int) b.getWidth(), (int) b.getHeight(), b.getRowStride());
+  }
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/image/BitmapsBlitTest.java b/src/test/java/org/apache/pdfbox/jbig2/image/BitmapsBlitTest.java
new file mode 100644
index 0000000..eb3f095
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/image/BitmapsBlitTest.java
@@ -0,0 +1,78 @@
+/**
+ * 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 static org.junit.Assert.assertArrayEquals;
+
+import java.awt.Rectangle;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2DocumentFacade;
+import org.apache.pdfbox.jbig2.err.JBIG2Exception;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.junit.Test;
+
+public class BitmapsBlitTest {
+
+  @Test
+  public void testCompleteBitmapTransfer() throws IOException, JBIG2Exception {
+    final InputStream inputStream = getClass().getResourceAsStream("/images/042_1.jb2");
+    final DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    final ImageInputStream iis = disf.getInputStream(inputStream);
+
+    final JBIG2DocumentFacade doc = new JBIG2DocumentFacade(iis);
+
+    final Bitmap src = doc.getPageBitmap(1);
+    final Bitmap dst = new Bitmap(src.getWidth(), src.getHeight());
+    Bitmaps.blit(src, dst, 0, 0, CombinationOperator.REPLACE);
+
+    final byte[] srcData = src.getByteArray();
+    final byte[] dstData = dst.getByteArray();
+
+    assertArrayEquals(srcData, dstData);
+  }
+
+  @Test
+  public void test() throws IOException, JBIG2Exception {
+    final InputStream inputStream = getClass().getResourceAsStream("/images/042_1.jb2");
+    final DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    final ImageInputStream iis = disf.getInputStream(inputStream);
+
+    final JBIG2DocumentFacade doc = new JBIG2DocumentFacade(iis);
+
+    final Bitmap dst = doc.getPageBitmap(1);
+
+    final Rectangle roi = new Rectangle(100, 100, 100, 100);
+    final Bitmap src = new Bitmap(roi.width, roi.height);
+    Bitmaps.blit(src, dst, roi.x, roi.y, CombinationOperator.REPLACE);
+
+    final Bitmap dstRegionBitmap = Bitmaps.extract(roi, dst);
+
+    final byte[] srcData = src.getByteArray();
+    final byte[] dstRegionData = dstRegionBitmap.getByteArray();
+
+    assertArrayEquals(srcData, dstRegionData);
+  }
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/image/BitmapsByteCombinationTest.java b/src/test/java/org/apache/pdfbox/jbig2/image/BitmapsByteCombinationTest.java
new file mode 100644
index 0000000..b1baed5
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/image/BitmapsByteCombinationTest.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 static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class BitmapsByteCombinationTest {
+
+  private static final byte value1 = 0xA;
+  private static final byte value2 = 0xD;
+
+  private final int expected;
+  private final CombinationOperator operator;
+
+  @Parameters
+  public static Collection<Object[]> data() {
+    return Arrays.asList(new Object[][]{
+        {
+            0xF, CombinationOperator.OR
+        }, {
+            0x8, CombinationOperator.AND
+        }, {
+            0x7, CombinationOperator.XOR
+        }, {
+            -8, CombinationOperator.XNOR
+        }, {
+            value2, CombinationOperator.REPLACE
+        }
+    });
+  }
+
+  public BitmapsByteCombinationTest(final int expected, final CombinationOperator operator) {
+    this.expected = expected;
+    this.operator = operator;
+  }
+
+
+  @Test
+  public void test() {
+    assertEquals(expected, Bitmaps.combineBytes(value1, value2, operator));
+  }
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/image/BitmapsChecksumTest.java b/src/test/java/org/apache/pdfbox/jbig2/image/BitmapsChecksumTest.java
new file mode 100644
index 0000000..8c8ccf8
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/image/BitmapsChecksumTest.java
@@ -0,0 +1,177 @@
+/**
+ * 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 static org.junit.Assert.assertArrayEquals;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.image.DataBufferByte;
+import java.awt.image.WritableRaster;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import javax.imageio.ImageReadParam;
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.Bitmap;
+import org.apache.pdfbox.jbig2.JBIG2DocumentFacade;
+import org.apache.pdfbox.jbig2.JBIG2ImageReaderDemo;
+import org.apache.pdfbox.jbig2.PreconfiguredImageReadParam;
+import org.apache.pdfbox.jbig2.err.JBIG2Exception;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.image.FilterType;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.apache.pdfbox.jbig2.io.InputStreamFactory;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+
+@RunWith(Parameterized.class)
+public class BitmapsChecksumTest {
+
+  private String resourcePath;
+  private ImageReadParam param;
+  private FilterType filterType;
+  private String checksum;
+  private int pageNumber;
+
+  @Parameters
+  public static Collection<Object[]> data() {
+    return Arrays.asList(new Object[][]{
+        {
+            "/images/042_1.jb2", 1, new PreconfiguredImageReadParam(new Dimension(500, 500)), FilterType.Bessel,
+            "101-6467-126-3534108-8927-58-26-37248672"
+        },
+        {
+            "/images/042_1.jb2", 1, new PreconfiguredImageReadParam(new Dimension(500, 800)), FilterType.Box,
+            "-748135-126-6412111-11925-1038826-95-32-6-104"
+        },
+        {
+            "/images/042_1.jb2", 1, new PreconfiguredImageReadParam(new Dimension(4000, 5500)), FilterType.Box,
+            "-646510160-466410970-77-1031184396-8-23-18"
+        },
+        {
+            "/images/042_1.jb2", 1, new PreconfiguredImageReadParam(new Dimension(600, 300)), FilterType.Bessel,
+            "-69-11478-721003586-100-72-85-1559101-118-24-94"
+        },
+        {
+            "/images/042_1.jb2", 1, new PreconfiguredImageReadParam(2, 2, 0, 0), FilterType.Bessel,
+            "-4979-94-68-125645751-2111712617-59-295"
+        },
+        {
+            "/images/042_1.jb2", 1, new PreconfiguredImageReadParam(2, 2, 0, 0), FilterType.Lanczos,
+            "-4979-94-68-125645751-2111712617-59-295"
+        },
+        {
+            "/images/042_1.jb2", 1, new PreconfiguredImageReadParam(3, 3, 1, 1), FilterType.Lanczos,
+            "84-1069410599-9575-7934-1279-80-85127-18-128"
+        },
+        {
+            "/images/042_1.jb2", 1, new PreconfiguredImageReadParam(new Rectangle(100, 100, 500, 500)),
+            FilterType.Lanczos, "1245-23-127954634-1232173-109-5739-303-48"
+        },
+        {
+            "/images/042_1.jb2", 1, new PreconfiguredImageReadParam(new Rectangle(500, 500, 2000, 2000)),
+            FilterType.Lanczos, "-60-45-117-90-6596-11556-47-30-112-741138412082"
+        },
+        {
+            "/images/042_1.jb2", 1,
+            new PreconfiguredImageReadParam(new Rectangle(500, 500, 2000, 2000), new Dimension(678, 931)),
+            FilterType.Lanczos, "-17-95-5543-12062-625054-94-88-31-4-120-1971"
+        },
+        {
+            "/images/042_1.jb2", 1,
+            new PreconfiguredImageReadParam(new Rectangle(500, 500, 2000, 2000), new Dimension(678, 931), 3, 3, 1, 1),
+            FilterType.Lanczos, "-109-60118-41999255-94113-5019-2818-10-39-71"
+        }
+    });
+  }
+
+  public BitmapsChecksumTest(String resourcePath, int pageNumber, ImageReadParam param, FilterType filterType,
+      String checksum) {
+    this.resourcePath = resourcePath;
+    this.pageNumber = pageNumber;
+    this.param = param;
+    this.filterType = filterType;
+    this.checksum = checksum;
+  }
+
+  @Test
+  public void test() throws IOException, JBIG2Exception, NoSuchAlgorithmException {
+    final InputStream inputStream = JBIG2ImageReaderDemo.class.getResourceAsStream(resourcePath);
+    final InputStreamFactory disf = new DefaultInputStreamFactory();
+    final ImageInputStream iis = disf.getInputStream(inputStream);
+
+    final JBIG2DocumentFacade doc = new JBIG2DocumentFacade(iis);
+    final Bitmap b = doc.getPageBitmap(pageNumber);
+    final WritableRaster raster = Bitmaps.asRaster(b, param, filterType);
+
+    final DataBufferByte dataBufferByte = (DataBufferByte) raster.getDataBuffer();
+    final byte[] bytes = dataBufferByte.getData();
+
+    final MessageDigest md = MessageDigest.getInstance("MD5");
+
+    final byte[] digest = md.digest(bytes);
+    final StringBuilder sb = new StringBuilder();
+    for (byte toAppend : digest) {
+      sb.append(toAppend);
+    }
+
+    assertArrayEquals(checksum.getBytes(), sb.toString().getBytes());
+  }
+
+  static class RasterChecksumCalculator {
+    public static void main(String[] args) throws IOException, JBIG2Exception, NoSuchAlgorithmException {
+      final String resourcePath = "/images/042_1.jb2";
+
+      final int pageNumber = 1;
+
+      final URL imageUrl = JBIG2ImageReaderDemo.class.getResource(resourcePath);
+
+      final InputStream inputStream = new FileInputStream(new File(imageUrl.getPath()));
+      final InputStreamFactory disf = new DefaultInputStreamFactory();
+      final ImageInputStream iis = disf.getInputStream(inputStream);
+
+      final JBIG2DocumentFacade doc = new JBIG2DocumentFacade(iis);
+      final Bitmap b = doc.getPageBitmap(pageNumber);
+
+      final ImageReadParam param = new PreconfiguredImageReadParam(new Rectangle(100, 100, 500, 500));
+
+      final WritableRaster raster = Bitmaps.asRaster(b, param, FilterType.Lanczos);
+      final DataBufferByte dataBufferByte = (DataBufferByte) raster.getDataBuffer();
+      final byte[] bytes = dataBufferByte.getData();
+
+      final MessageDigest md = MessageDigest.getInstance("MD5");
+
+      final byte[] digest = md.digest(bytes);
+      for (byte d : digest) {
+        System.out.print(d);
+      }
+    }
+  }
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/segments/GenericRegionTest.java b/src/test/java/org/apache/pdfbox/jbig2/segments/GenericRegionTest.java
new file mode 100644
index 0000000..8692c6c
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/segments/GenericRegionTest.java
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+
+import junit.framework.Assert;
+
+import org.apache.pdfbox.jbig2.TestImage;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.image.Bitmaps;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.segments.GenericRegion;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class GenericRegionTest {
+
+  @Test
+  public void parseHeaderTest() throws IOException, InvalidHeaderValueException {
+    InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+
+    // Twelfth Segment (number 11)
+    SubInputStream sis = new SubInputStream(iis, 523, 35);
+    GenericRegion gr = new GenericRegion();
+    gr.init(null, sis);
+
+    Assert.assertEquals(54, gr.getRegionInfo().getBitmapWidth());
+    Assert.assertEquals(44, gr.getRegionInfo().getBitmapHeight());
+    Assert.assertEquals(4, gr.getRegionInfo().getXLocation());
+    Assert.assertEquals(11, gr.getRegionInfo().getYLocation());
+    Assert.assertEquals(CombinationOperator.OR, gr.getRegionInfo().getCombinationOperator());
+
+    Assert.assertFalse(gr.useExtTemplates());
+    Assert.assertFalse(gr.isMMREncoded());
+    Assert.assertEquals(0, gr.getGbTemplate());
+    Assert.assertTrue(gr.isTPGDon());
+
+    short[] gbAtX = gr.getGbAtX();
+    short[] gbAtY = gr.getGbAtY();
+    Assert.assertEquals(3, gbAtX[0]);
+    Assert.assertEquals(-1, gbAtY[0]);
+    Assert.assertEquals(-3, gbAtX[1]);
+    Assert.assertEquals(-1, gbAtY[1]);
+    Assert.assertEquals(2, gbAtX[2]);
+    Assert.assertEquals(-2, gbAtY[2]);
+    Assert.assertEquals(-2, gbAtX[3]);
+    Assert.assertEquals(-2, gbAtY[3]);
+  }
+
+  // TESTS WITH TESTOUTPUT
+  // Ignore in build process
+
+  @Ignore
+  @Test
+  public void decodeTemplate0Test() throws Throwable {
+    InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    // Twelfth Segment (number 11)
+    SubInputStream sis = new SubInputStream(iis, 523, 35);
+    GenericRegion gr = new GenericRegion();
+
+    gr.init(null, sis);
+    new TestImage(Bitmaps.asBufferedImage(gr.getRegionBitmap()));
+  }
+
+  @Ignore
+  @Test
+  public void decodeWithArithmetichCoding() throws Throwable {
+
+    InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    // Twelfth Segment (number 11)
+    SubInputStream sis = new SubInputStream(iis, 523, 35);
+    GenericRegion gr = new GenericRegion(sis);
+
+    gr.init(null, sis);
+    new TestImage(Bitmaps.asBufferedImage(gr.getRegionBitmap()));
+  }
+
+  @Ignore
+  @Test
+  public void decodeWithMMR() throws Throwable {
+    InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    // Fifth Segment (number 4)
+    SubInputStream sis = new SubInputStream(iis, 190, 59);
+    GenericRegion gr = new GenericRegion(sis);
+    gr.init(null, sis);
+    new TestImage(Bitmaps.asBufferedImage(gr.getRegionBitmap()));
+  }
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/segments/HalftoneRegionTest.java b/src/test/java/org/apache/pdfbox/jbig2/segments/HalftoneRegionTest.java
new file mode 100644
index 0000000..64eae08
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/segments/HalftoneRegionTest.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+
+import junit.framework.Assert;
+
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.segments.HalftoneRegion;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.junit.Test;
+
+public class HalftoneRegionTest {
+
+  @Test
+  public void parseHeaderTest() throws IOException, InvalidHeaderValueException {
+    InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    // Seventh Segment (number 6)
+    SubInputStream sis = new SubInputStream(iis, 302, 87);
+    HalftoneRegion hr = new HalftoneRegion(sis);
+    hr.init(null, sis);
+
+    Assert.assertEquals(true, hr.isMMREncoded());
+    Assert.assertEquals(0, hr.getHTemplate());
+    Assert.assertEquals(false, hr.isHSkipEnabled());
+    Assert.assertEquals(CombinationOperator.OR, hr.getCombinationOperator());
+    Assert.assertEquals(0, hr.getHDefaultPixel());
+
+    Assert.assertEquals(8, hr.getHGridWidth());
+    Assert.assertEquals(9, hr.getHGridHeight());
+    Assert.assertEquals(0, hr.getHGridX());
+    Assert.assertEquals(0, hr.getHGridY());
+    Assert.assertEquals(1024, hr.getHRegionX());
+    Assert.assertEquals(0, hr.getHRegionY());
+
+  }
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/segments/PageInformationTest.java b/src/test/java/org/apache/pdfbox/jbig2/segments/PageInformationTest.java
new file mode 100644
index 0000000..2ef2cc2
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/segments/PageInformationTest.java
@@ -0,0 +1,91 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+import junit.framework.Assert;
+
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.*;
+import org.apache.pdfbox.jbig2.segments.PageInformation;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class PageInformationTest {
+
+	@Test
+	public void parseHeaderCompleteTest() throws IOException,
+			InvalidHeaderValueException {
+		InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+		DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+		ImageInputStream iis = disf.getInputStream(is);
+		// Second Segment (number 1)
+		SubInputStream sis = new SubInputStream(iis, 59, 19);
+		PageInformation pi = new PageInformation();
+		pi.init(null, sis);
+		Assert.assertEquals(64, pi.getBitmapWidth());
+		Assert.assertEquals(56, pi.getBitmapHeight());
+		Assert.assertEquals(0, pi.getResolutionX());
+		Assert.assertEquals(0, pi.getResolutionY());
+		Assert.assertEquals(true, pi.isLossless());
+		Assert.assertEquals(false, pi.mightContainRefinements());
+		Assert.assertEquals(0, pi.getDefaultPixelValue());
+		Assert.assertEquals(CombinationOperator.OR, pi.getCombinationOperator());
+		Assert.assertEquals(false, pi.isAuxiliaryBufferRequired());
+		Assert.assertEquals(false, pi.isCombinationOperatorOverrideAllowed());
+		Assert.assertEquals(false, pi.isStriped());
+		Assert.assertEquals(0, pi.getMaxStripeSize());
+	}
+
+	@Ignore
+	@Test
+	public void parseHeaderXOROperatorTest() throws IOException,
+			InvalidHeaderValueException {
+		InputStream is = getClass().getResourceAsStream(
+				"/sampledata_pageinformation_with_xor-opartor.jb2");
+		DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+		ImageInputStream iis = disf.getInputStream(is);
+		// Second Segment (number 1)
+		SubInputStream sis = new SubInputStream(iis, 59, 19);
+		PageInformation pi = new PageInformation();
+		pi.init(null, sis);
+		// XOR (2) als Operator erwartet
+		Assert.assertEquals(2, pi.getCombinationOperator());
+	}
+
+	@Ignore
+	@Test
+	public void parseHeaderANDOperatorTest() throws IOException,
+			InvalidHeaderValueException {
+		InputStream is = getClass().getResourceAsStream(
+				"/sampledata_pageinformation_with_and-opartor.jb2");
+		DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+		ImageInputStream iis = disf.getInputStream(is);
+		// Second Segment (number 1)
+		SubInputStream sis = new SubInputStream(iis, 59, 19);
+		PageInformation pi = new PageInformation();
+		pi.init(null, sis);
+		Assert.assertEquals(true, pi.isLossless());
+		// AND (1) als Operator erwartet
+		Assert.assertEquals(1, pi.getCombinationOperator());
+	}
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/segments/PatternDictionaryTest.java b/src/test/java/org/apache/pdfbox/jbig2/segments/PatternDictionaryTest.java
new file mode 100644
index 0000000..0f32f0a
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/segments/PatternDictionaryTest.java
@@ -0,0 +1,96 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.imageio.stream.ImageInputStream;
+
+import junit.framework.Assert;
+
+import org.apache.pdfbox.jbig2.*;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.*;
+import org.apache.pdfbox.jbig2.segments.*;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class PatternDictionaryTest {
+  @Test
+  public void parseHeaderTest() throws IOException, InvalidHeaderValueException {
+    InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    // Sixth Segment (number 5)
+    SubInputStream sis = new SubInputStream(iis, 245, 45);
+    PatternDictionary pd = new PatternDictionary();
+    pd.init(null, sis);
+    Assert.assertEquals(true, pd.isMMREncoded());
+    Assert.assertEquals(0, pd.getHdTemplate());
+    Assert.assertEquals(4, pd.getHdpWidth());
+    Assert.assertEquals(4, pd.getHdpHeight());
+    Assert.assertEquals(15, pd.getGrayMax());
+  }
+
+  // TESTS WITH TESTOUTPUT
+  // Ignore in build process
+
+  @Ignore
+  @Test
+  public void decodeTestWithOutput() throws Throwable {
+    InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    // Sixth Segment (number 5)
+    SubInputStream sis = new SubInputStream(iis, 245, 45);
+
+    PatternDictionary pd = new PatternDictionary();
+    pd.init(null, sis);
+
+    ArrayList<Bitmap> b = pd.getDictionary();
+
+    int i = 5;
+    // for (int i = 0; i < 8; i++) {
+    new TestImage(b.get(i).getByteArray(), (int) b.get(i).getWidth(), (int) b.get(i).getHeight(),
+        b.get(i).getRowStride());
+    // }
+  }
+
+  @Ignore
+  @Test
+  public void decodeTestWithOutput2() throws Throwable {
+    InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    // Twelfth Segment (number 12)
+    SubInputStream sis = new SubInputStream(iis, 569, 28);
+
+    PatternDictionary pd = new PatternDictionary();
+    pd.init(null, sis);
+
+    ArrayList<Bitmap> b = pd.getDictionary();
+
+    int i = 2;
+    // for (int i = 0; i < 8; i++) {
+    new TestImage(b.get(i).getByteArray(), (int) b.get(i).getWidth(), (int) b.get(i).getHeight(),
+        b.get(i).getRowStride());
+    // }
+  }
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/segments/RegionSegmentInformationTest.java b/src/test/java/org/apache/pdfbox/jbig2/segments/RegionSegmentInformationTest.java
new file mode 100644
index 0000000..84b555a
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/segments/RegionSegmentInformationTest.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.segments;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+
+import junit.framework.Assert;
+
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.apache.pdfbox.jbig2.io.SubInputStream;
+import org.apache.pdfbox.jbig2.segments.RegionSegmentInformation;
+import org.apache.pdfbox.jbig2.util.CombinationOperator;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class RegionSegmentInformationTest {
+
+  @Test
+  public void parseHeaderTest() throws IOException {
+    InputStream is = getClass().getResourceAsStream("/images/sampledata.jb2");
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    SubInputStream sis = new SubInputStream(iis, 130, 49);
+    RegionSegmentInformation rsi = new RegionSegmentInformation(sis);
+    rsi.parseHeader();
+    Assert.assertEquals(37, rsi.getBitmapWidth());
+    Assert.assertEquals(8, rsi.getBitmapHeight());
+    Assert.assertEquals(4, rsi.getXLocation());
+    Assert.assertEquals(1, rsi.getYLocation());
+    Assert.assertEquals(CombinationOperator.OR, rsi.getCombinationOperator());
+  }
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/segments/SymbolDictionaryTest.java b/src/test/java/org/apache/pdfbox/jbig2/segments/SymbolDictionaryTest.java
new file mode 100644
index 0000000..8babe4b
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/segments/SymbolDictionaryTest.java
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import org.junit.Ignore;
+
+@Ignore
+public class SymbolDictionaryTest {
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/pdfbox/jbig2/segments/TextRegionTest.java b/src/test/java/org/apache/pdfbox/jbig2/segments/TextRegionTest.java
new file mode 100644
index 0000000..da6f961
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/segments/TextRegionTest.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.segments;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.ImageInputStream;
+
+import org.apache.pdfbox.jbig2.JBIG2ImageReader;
+import org.apache.pdfbox.jbig2.JBIG2ImageReaderSpi;
+import org.apache.pdfbox.jbig2.TestImage;
+import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
+import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
+import org.apache.pdfbox.jbig2.io.DefaultInputStreamFactory;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class TextRegionTest {
+
+  // TESTS WITH TESTOUTPUT
+  // Ignore for in build process
+
+  @Ignore
+  @Test
+  public void textRegionWith() throws IOException, InvalidHeaderValueException, IntegerMaxValueException {
+    String filepath = "/images/042_11.jb2";
+    int pageNumber = 1;
+
+    InputStream is = getClass().getResourceAsStream(filepath);
+    DefaultInputStreamFactory disf = new DefaultInputStreamFactory();
+    ImageInputStream iis = disf.getInputStream(is);
+    JBIG2ImageReader jb2 = new JBIG2ImageReader(new JBIG2ImageReaderSpi());
+    jb2.setInput(iis);
+    BufferedImage b = jb2.read(pageNumber);
+    new TestImage(b);
+  }
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/util/CacheFactoryTest.java b/src/test/java/org/apache/pdfbox/jbig2/util/CacheFactoryTest.java
new file mode 100644
index 0000000..0f15338
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/util/CacheFactoryTest.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.pdfbox.jbig2.util.cache.CacheBridge;
+import org.apache.pdfbox.jbig2.util.cache.CacheFactory;
+import org.junit.Test;
+
+public class CacheFactoryTest {
+
+  @Test
+  public void testWithDefaultClassLoader() {
+    CacheFactory.setClassLoader(CacheBridge.class.getClassLoader());
+    assertNotNull(CacheFactory.getCache());
+  }
+
+  @Test
+  public void testWithContextClassLoader() {
+    CacheFactory.setClassLoader(Thread.currentThread().getContextClassLoader());
+    assertNotNull(CacheFactory.getCache());
+  }
+
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/util/LoggerFactoryTest.java b/src/test/java/org/apache/pdfbox/jbig2/util/LoggerFactoryTest.java
new file mode 100644
index 0000000..0fc89b6
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/util/LoggerFactoryTest.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.pdfbox.jbig2.util.log.LoggerBridge;
+import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
+import org.junit.Test;
+
+public class LoggerFactoryTest {
+
+  @Test
+  public void testWithDefaultClassLoader() {
+    LoggerFactory.setClassLoader(LoggerBridge.class.getClassLoader());
+    assertNotNull(LoggerFactory.getLogger(LoggerFactoryTest.class));
+  }
+  
+  @Test
+  public void testWithContextClassLoader() {
+    LoggerFactory.setClassLoader(Thread.currentThread().getContextClassLoader());
+    assertNotNull(LoggerFactory.getLogger(LoggerFactoryTest.class));
+  }
+  
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/util/ServiceLookupTest.java b/src/test/java/org/apache/pdfbox/jbig2/util/ServiceLookupTest.java
new file mode 100644
index 0000000..72c86ef
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/util/ServiceLookupTest.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+
+import org.apache.pdfbox.jbig2.util.ServiceLookup;
+import org.junit.Test;
+
+public class ServiceLookupTest {
+
+  @Test
+  public void withDefaultClassLoader() {
+    runTest(null);
+  }
+
+  @Test
+  public void withContextClassLoader() {
+    runTest(Thread.currentThread().getContextClassLoader());
+  }
+
+  @Test
+  public void withClassLoaderFromClass() {
+    runTest(TestService.class.getClassLoader());
+  }
+  
+  private void runTest(ClassLoader clsLoader) {
+    ServiceLookup<TestService> serviceLookup = new ServiceLookup<TestService>();
+
+    Iterator<TestService> services = clsLoader != null
+        ? serviceLookup.getServices(TestService.class, clsLoader)
+        : serviceLookup.getServices(TestService.class);
+
+    assertTrue(services.hasNext());
+    assertEquals(TestServiceImpl.class, services.next().getClass());
+  }
+
+}
diff --git a/src/test/java/org/apache/pdfbox/jbig2/util/TestService.java b/src/test/java/org/apache/pdfbox/jbig2/util/TestService.java
new file mode 100644
index 0000000..1bb8b13
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/util/TestService.java
@@ -0,0 +1,22 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+public interface TestService {
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/pdfbox/jbig2/util/TestServiceImpl.java b/src/test/java/org/apache/pdfbox/jbig2/util/TestServiceImpl.java
new file mode 100644
index 0000000..da1f608
--- /dev/null
+++ b/src/test/java/org/apache/pdfbox/jbig2/util/TestServiceImpl.java
@@ -0,0 +1,22 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.jbig2.util;
+
+public class TestServiceImpl implements TestService {
+
+}
\ No newline at end of file
diff --git a/src/test/resources/META-INF/services/org.apache.pdfbox.jbig2.util.TestService b/src/test/resources/META-INF/services/org.apache.pdfbox.jbig2.util.TestService
new file mode 100644
index 0000000..c916bc9
--- /dev/null
+++ b/src/test/resources/META-INF/services/org.apache.pdfbox.jbig2.util.TestService
@@ -0,0 +1,18 @@
+#

+# Licensed to the Apache Software Foundation (ASF) under one or more

+# contributor license agreements.  See the NOTICE file distributed with

+# this work for additional information regarding copyright ownership.

+# The ASF licenses this file to You under the Apache License, Version 2.0

+# (the "License"); you may not use this file except in compliance with

+# the License.  You may obtain a copy of the License at

+#

+#      http://www.apache.org/licenses/LICENSE-2.0

+#

+# Unless required by applicable law or agreed to in writing, software

+# distributed under the License is distributed on an "AS IS" BASIS,

+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+# See the License for the specific language governing permissions and

+# limitations under the License.

+#

+

+org.apache.pdfbox.jbig2.util.TestServiceImpl
\ No newline at end of file
diff --git a/src/test/resources/com/levigo/jbig2/github/21.glob b/src/test/resources/com/levigo/jbig2/github/21.glob
new file mode 100644
index 0000000..2261edf
--- /dev/null
+++ b/src/test/resources/com/levigo/jbig2/github/21.glob
Binary files differ
diff --git a/src/test/resources/com/levigo/jbig2/github/21.jb2 b/src/test/resources/com/levigo/jbig2/github/21.jb2
new file mode 100644
index 0000000..25f8f05
--- /dev/null
+++ b/src/test/resources/com/levigo/jbig2/github/21.jb2
Binary files differ
diff --git a/src/test/resources/images/001.jb2 b/src/test/resources/images/001.jb2
new file mode 100644
index 0000000..aad904f
--- /dev/null
+++ b/src/test/resources/images/001.jb2
Binary files differ
diff --git a/src/test/resources/images/002.jb2 b/src/test/resources/images/002.jb2
new file mode 100644
index 0000000..0b703aa
--- /dev/null
+++ b/src/test/resources/images/002.jb2
Binary files differ
diff --git a/src/test/resources/images/003.jb2 b/src/test/resources/images/003.jb2
new file mode 100644
index 0000000..f7c91a0
--- /dev/null
+++ b/src/test/resources/images/003.jb2
Binary files differ
diff --git a/src/test/resources/images/005.jb2 b/src/test/resources/images/005.jb2
new file mode 100644
index 0000000..dff9059
--- /dev/null
+++ b/src/test/resources/images/005.jb2
Binary files differ
diff --git a/src/test/resources/images/006.jb2 b/src/test/resources/images/006.jb2
new file mode 100644
index 0000000..b132735
--- /dev/null
+++ b/src/test/resources/images/006.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110001.jb2 b/src/test/resources/images/20123110001.jb2
new file mode 100644
index 0000000..553bd63
--- /dev/null
+++ b/src/test/resources/images/20123110001.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110002.jb2 b/src/test/resources/images/20123110002.jb2
new file mode 100644
index 0000000..c83efc5
--- /dev/null
+++ b/src/test/resources/images/20123110002.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110003.jb2 b/src/test/resources/images/20123110003.jb2
new file mode 100644
index 0000000..7d9f8bc
--- /dev/null
+++ b/src/test/resources/images/20123110003.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110004.jb2 b/src/test/resources/images/20123110004.jb2
new file mode 100644
index 0000000..37dfe83
--- /dev/null
+++ b/src/test/resources/images/20123110004.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110005.jb2 b/src/test/resources/images/20123110005.jb2
new file mode 100644
index 0000000..f4ef987
--- /dev/null
+++ b/src/test/resources/images/20123110005.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110006.jb2 b/src/test/resources/images/20123110006.jb2
new file mode 100644
index 0000000..0354d8e
--- /dev/null
+++ b/src/test/resources/images/20123110006.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110007.jb2 b/src/test/resources/images/20123110007.jb2
new file mode 100644
index 0000000..31bb6e6
--- /dev/null
+++ b/src/test/resources/images/20123110007.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110008.jb2 b/src/test/resources/images/20123110008.jb2
new file mode 100644
index 0000000..6362f76
--- /dev/null
+++ b/src/test/resources/images/20123110008.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110009.jb2 b/src/test/resources/images/20123110009.jb2
new file mode 100644
index 0000000..a5eb420
--- /dev/null
+++ b/src/test/resources/images/20123110009.jb2
Binary files differ
diff --git a/src/test/resources/images/20123110010.jb2 b/src/test/resources/images/20123110010.jb2
new file mode 100644
index 0000000..a26c605
--- /dev/null
+++ b/src/test/resources/images/20123110010.jb2
Binary files differ
diff --git a/src/test/resources/images/arith/decoded testsequence b/src/test/resources/images/arith/decoded testsequence
new file mode 100644
index 0000000..45c4827
--- /dev/null
+++ b/src/test/resources/images/arith/decoded testsequence
Binary files differ
diff --git a/src/test/resources/images/arith/encoded testsequence b/src/test/resources/images/arith/encoded testsequence
new file mode 100644
index 0000000..8ccf8b4
--- /dev/null
+++ b/src/test/resources/images/arith/encoded testsequence
Binary files differ