This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/pdfbox-jbig2.git
commit 65744b46de70d7b83414c69b5ace1fba70e795f0 Author: Maruan Sahyoun <[email protected]> AuthorDate: Tue May 12 12:43:10 2026 +0200 PDFBOX-6199: Template 1 TPGR decoding and replace sliding window with readable pixel-by-pixel implementation --- pom.xml | 112 ++++++++++++++----- .../GenericRefinementRegionDecodingProcedure.java | 122 +++++++++++++++++++-- .../org/apache/pdfbox/jbig2/SerenityTests.java | 8 +- 3 files changed, 203 insertions(+), 39 deletions(-) diff --git a/pom.xml b/pom.xml index d035e6f..bc2e316 100644 --- a/pom.xml +++ b/pom.xml @@ -222,12 +222,24 @@ </scm> <dependencies> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.13.2</version> - <scope>test</scope> - </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.13.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.openjdk.jmh</groupId> + <artifactId>jmh-core</artifactId> + <version>1.37</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.openjdk.jmh</groupId> + <artifactId>jmh-generator-annprocess</artifactId> + <version>1.37</version> + <scope>test</scope> + </dependency> </dependencies> <!-- enable the usage of external testfiles with -Dskip-external-testfiles=false --> @@ -236,24 +248,24 @@ </properties> <build> - <plugins> - <plugin> - <artifactId>maven-enforcer-plugin</artifactId> - <executions> - <execution> - <goals> - <goal>enforce</goal> - </goals> - <configuration> - <rules combine.children="append"> - <requireMavenVersion> - <version>3.7.0</version> - </requireMavenVersion> - </rules> - </configuration> - </execution> - </executions> - </plugin> + <plugins> + <plugin> + <artifactId>maven-enforcer-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>enforce</goal> + </goals> + <configuration> + <rules combine.children="append"> + <requireMavenVersion> + <version>3.7.0</version> + </requireMavenVersion> + </rules> + </configuration> + </execution> + </executions> + </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> @@ -266,10 +278,10 @@ <artifactId>maven-release-plugin</artifactId> <configuration> <autoVersionSubmodules>true</autoVersionSubmodules> - <!-- Keep changes in the local repo, push will be done afterwards --> + <!-- Keep changes in the local repo, push will be done afterwards --> <pushChanges>false</pushChanges> <localCheckout>true</localCheckout> - <!-- Use a better name for the tag --> + <!-- Use a better name for the tag --> <tagNameFormat>${project.artifactId}-${project.version}</tagNameFormat> </configuration> <dependencies> @@ -420,6 +432,54 @@ </plugins> </build> </profile> + <profile> + <id>benchmark</id> + <build> + <plugins> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.8</source> + <target>1.8</target> + <debug>false</debug> + <optimize>true</optimize> + <annotationProcessorPaths> + <path> + <groupId>org.openjdk.jmh</groupId> + <artifactId>jmh-generator-annprocess</artifactId> + <version>1.37</version> + </path> + </annotationProcessorPaths> + </configuration> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>3.1.0</version> + <executions> + <execution> + <id>run-benchmarks</id> + <phase>test</phase> + <goals> + <goal>java</goal> + </goals> + <configuration> + <mainClass>org.openjdk.jmh.Main</mainClass> + <classpathScope>test</classpathScope> + <arguments> + <argument>.*Benchmark.*</argument> + <argument>-rf</argument> + <argument>json</argument> + <argument>-rff</argument> + <argument>${project.build.directory}/benchmark-results.json</argument> + </arguments> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> <profile> <id>apache-release</id> <build> diff --git a/src/main/java/org/apache/pdfbox/jbig2/decoder/GenericRefinementRegionDecodingProcedure.java b/src/main/java/org/apache/pdfbox/jbig2/decoder/GenericRefinementRegionDecodingProcedure.java index 1394c14..0201ec9 100644 --- a/src/main/java/org/apache/pdfbox/jbig2/decoder/GenericRefinementRegionDecodingProcedure.java +++ b/src/main/java/org/apache/pdfbox/jbig2/decoder/GenericRefinementRegionDecodingProcedure.java @@ -55,7 +55,7 @@ public class GenericRefinementRegionDecodingProcedure // ------------------------------------------------------------------------- private static final int SLTP_CONTEXT_TEMPLATE0 = 0x100; // §6.3.5.6, Figure 14 - private static final int SLTP_CONTEXT_TEMPLATE1 = 0x080; // §6.3.5.6, Figure 15 + private static final int SLTP_CONTEXT_TEMPLATE1 = 0x008; // §6.3.5.6, Figure 15 /** * Encapsulates the template-specific operations: context bit formation @@ -255,17 +255,27 @@ public class GenericRefinementRegionDecodingProcedure isLineTypicalPredicted ^= decodeSLTP(); } - if (isLineTypicalPredicted == 0) + if (templateID == 1) { - /* 6.3.5.6 - 3 c) */ - decodeOptimized(y, width, regionBitmap.getRowStride(), - referenceBitmap.getRowStride(), paddedWidth, deltaRefStride, yOffset); + if (isLineTypicalPredicted == 0) + { + decodeLineExplicitT1(y, width); + } + else + { + decodeLineTPGRT1(y, width); + } } else { - /* 6.3.5.6 - 3 d) */ - decodeTypicalPredictedLine(y, width, regionBitmap.getRowStride(), - referenceBitmap.getRowStride(), paddedWidth, deltaRefStride); + // existing template 0 paths unchanged + if (isLineTypicalPredicted == 0) { + decodeOptimized(y, width, regionBitmap.getRowStride(), + referenceBitmap.getRowStride(), paddedWidth, deltaRefStride, yOffset); + } else { + decodeTypicalPredictedLine(y, width, regionBitmap.getRowStride(), + referenceBitmap.getRowStride(), paddedWidth, deltaRefStride); + } } } @@ -273,6 +283,102 @@ public class GenericRefinementRegionDecodingProcedure return regionBitmap; } + // ------------------------------------------------------------------------- + // Pixel accessors — §6.3.5.2 out-of-bounds rule: all outside pixels = 0 + // ------------------------------------------------------------------------- + + private int getPixelSafe(final Bitmap bitmap, final int x, final int y) + { + if (x < 0 || y < 0) + return 0; + if (x >= bitmap.getWidth() || y >= bitmap.getHeight()) + return 0; + return bitmap.getPixel(x, y); + } + + private int getReferenceBit(final int x, final int y) + { + return getPixelSafe(referenceBitmap, x - referenceDX, y - referenceDY); + } + + private int getRegionBit(final int x, final int y) + { + return getPixelSafe(regionBitmap, x, y); + } + + // ------------------------------------------------------------------------- + // Template 1 context formation — §6.3.5.6, Figure 13 + // Pixels gathered in reading order, GRREG before GRREFERENCE: + // GRREG: (x-2,y-1) (x-1,y-1) (x,y-1) (x-1,y) → bits 9-6 + // GRREFERENCE:(x,y-1) (x-1,y) (x,y) (x+1,y) (x,y+1) (x+1,y+1) → bits 5-0 + // Spec example: only ⊗=(x,y)=1 → CX="GR0000001000"=0x08 → bit 3 ✓ + // ------------------------------------------------------------------------- + + private int buildContextT1(final int x, final int y) + { + return (getRegionBit(x - 1, y - 1) << 9) + | (getRegionBit(x, y - 1) << 8) + | (getRegionBit(x + 1, y - 1) << 7) + | (getRegionBit(x - 1, y ) << 6) + | (getReferenceBit(x , y - 1) << 5) + | (getReferenceBit(x - 1, y ) << 4) + | (getReferenceBit(x , y ) << 3) + | (getReferenceBit(x + 1, y ) << 2) + | (getReferenceBit(x, y + 1) << 1) + | (getReferenceBit(x + 1, y + 1)); + } + + // ------------------------------------------------------------------------- + // Template 1 — explicit decode (LTP=0 path, §6.3.5.6 step 3c) + // ------------------------------------------------------------------------- + + private void decodeLineExplicitT1(final int y, final int width) throws IOException + { + for (int x = 0; x < width; x++) + { + cx.setIndex(buildContextT1(x, y)); + regionBitmap.setPixel(x, y, (byte) arithDecoder.decode(cx)); + } + } + + // ------------------------------------------------------------------------- + // Template 1 — typical prediction decode (LTP=1 path, §6.3.5.6 step 3d) + // TPGRPIX=1 when the 3×3 reference neighbourhood is uniform (§6.3.5.6 3d-i) + // ------------------------------------------------------------------------- + + private void decodeLineTPGRT1(final int y, final int width) throws IOException + { + for (int x = 0; x < width; x++) + { + final int center = getReferenceBit(x, y); + boolean uniform = true; + outer: + for (int dy = -1; dy <= 1; dy++) + { + for (int dx = -1; dx <= 1; dx++) + { + if (getReferenceBit(x + dx, y + dy) != center) + { + uniform = false; + break outer; + } + } + } + + final int bit; + if (uniform) + { + bit = center; + } + else + { + cx.setIndex(buildContextT1(x, y)); + bit = arithDecoder.decode(cx); + } + regionBitmap.setPixel(x, y, (byte) bit); + } + } + // ------------------------------------------------------------------------- // Private decoding helpers (§6.3.5.6 sub-steps) // ------------------------------------------------------------------------- diff --git a/src/test/java/org/apache/pdfbox/jbig2/SerenityTests.java b/src/test/java/org/apache/pdfbox/jbig2/SerenityTests.java index 4a709b9..ccded2a 100644 --- a/src/test/java/org/apache/pdfbox/jbig2/SerenityTests.java +++ b/src/test/java/org/apache/pdfbox/jbig2/SerenityTests.java @@ -17,6 +17,7 @@ package org.apache.pdfbox.jbig2; +import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; @@ -25,6 +26,8 @@ import java.io.InputStream; import javax.imageio.ImageIO; import javax.imageio.stream.ImageInputStream; import org.apache.pdfbox.jbig2.err.JBIG2Exception; +import org.apache.pdfbox.jbig2.image.Bitmaps; + import static org.junit.Assert.assertEquals; import org.junit.Test; @@ -59,11 +62,6 @@ public class SerenityTests for (File file : files) { String name = file.getName(); - // Files that are not properly decoded yet - if (name.equals("bitmap-refine-template1-tpgron.jbig2")) - { - continue; - } imageIIS = ImageIO.createImageInputStream(file); doc = new JBIG2Document(imageIIS); page = doc.getPage(1);
