This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch TIKA-4862-pdfbox-image-quality in repository https://gitbox.apache.org/repos/asf/tika.git
commit 7290a9ec0644bcfbc5d4dc0aa52383453a9f9820 Author: tallison <[email protected]> AuthorDate: Mon Aug 31 08:26:33 2026 -0400 TIKA-4862 - add compression/image quality setting to PDFBoxRenderer --- CHANGES.txt | 9 +++ .../java/org/apache/tika/parser/pdf/OcrConfig.java | 7 +- .../tika/renderer/pdf/pdfbox/PDFBoxRenderer.java | 22 ++++++- .../renderer/pdf/pdfbox/PDFBoxRendererTest.java | 75 ++++++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ce09fe0392..0e826b7d79 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,14 @@ Release 4.1.0 - unreleased + * PDF page rendering writes compressed PNGs: PDFBoxRenderer gained an + imageQuality setting (new default 0.5) and OcrConfig.imageQuality's + default moved from 1.0 to 0.5. ImageIO's PNG "quality" is an inverted + effort knob - 1.0 wrote uncompressed files (~3.9 MB per 200-dpi gray + page) and the renderer's previous unset value spent ~10x the encode + time of 0.5 for a few percent smaller output; 0.5 is both fast and + small, and PNG stays lossless. Set ocr.imageQuality to restore old + behavior (TIKA-4862). + * embedded-limits maxDepth counts embedding levels again instead of the parsers a parse passes through; with AutoDetectParser over DefaultParser every value above 1 used to stop one level early (TIKA-4857). diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/OcrConfig.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/OcrConfig.java index 45454b7275..0b54b7b5a2 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/OcrConfig.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/OcrConfig.java @@ -121,7 +121,12 @@ public class OcrConfig implements Serializable { private int dpi = 300; private ImageType imageType = ImageType.GRAY; private ImageFormat imageFormat = ImageFormat.PNG; - private float imageQuality = 1.0f; + /** + * Compression quality handed to ImageIO when writing rendered pages. For PNG this is + * an inverted effort knob, not fidelity: 1.0 writes an uncompressed file, 0.0 spends + * ~10x the time of 0.5 for a few percent smaller output. PNG is always lossless. + */ + private float imageQuality = 0.5f; /** * Maximum total pixels (width × height) allowed for a rendered diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRenderer.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRenderer.java index 4307342458..3e26a13dda 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRenderer.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRenderer.java @@ -90,6 +90,13 @@ public class PDFBoxRenderer implements PDDocumentRenderer { private ImageType defaultImageType = ImageType.GRAY; private String defaultImageFormatName = "png"; + /** + * ImageIO's "compression quality"; for PNG it is an inverted effort knob: + * 1.0 writes an uncompressed file, 0.0 spends ~10x the time of the default for a + * few percent smaller output. 0.5 is both fast and small. + */ + private float defaultImageQuality = 0.5f; + @Override public RenderResults render(TikaInputStream tis, Metadata metadata, ParseContext parseContext, @@ -185,7 +192,8 @@ public class PDFBoxRenderer implements PDDocumentRenderer { metadata.set(PDFBOX_RENDERING_TIME_MS, renderingElapsed); start = System.currentTimeMillis(); try (OutputStream os = Files.newOutputStream(tmpFile)) { - ImageIOUtil.writeImage(image, getImageFormatName(parseContext), os, getDPI(parseContext)); + ImageIOUtil.writeImage(image, getImageFormatName(parseContext), os, getDPI(parseContext), + getImageQuality(parseContext)); } long elapsedWrite = System.currentTimeMillis() - start; metadata.set(PDFBOX_IMAGE_WRITING_TIME_MS, elapsedWrite); @@ -216,6 +224,18 @@ public class PDFBoxRenderer implements PDDocumentRenderer { this.defaultImageFormatName = imageFormatName; } + public void setImageQuality(float imageQuality) { + this.defaultImageQuality = imageQuality; + } + + protected float getImageQuality(ParseContext parseContext) { + PDFParserConfig pdfParserConfig = parseContext.get(PDFParserConfig.class); + if (pdfParserConfig == null) { + return defaultImageQuality; + } + return pdfParserConfig.getOcr().getImageQuality(); + } + protected int getDPI(ParseContext parseContext) { PDFParserConfig pdfParserConfig = parseContext.get(PDFParserConfig.class); if (pdfParserConfig == null) { diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRendererTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRendererTest.java new file mode 100644 index 0000000000..0bcf1dc396 --- /dev/null +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRendererTest.java @@ -0,0 +1,75 @@ +/* + * 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.tika.renderer.pdf.pdfbox; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.InputStream; + +import org.junit.jupiter.api.Test; + +import org.apache.tika.io.TikaInputStream; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.parser.ParseContext; +import org.apache.tika.parser.pdf.PDFParserConfig; +import org.apache.tika.renderer.PageBasedRenderResults; +import org.apache.tika.renderer.PageRangeRequest; +import org.apache.tika.renderer.RenderResult; + +public class PDFBoxRendererTest { + + private long renderedPngBytes(ParseContext context) throws Exception { + PDFBoxRenderer renderer = new PDFBoxRenderer(); + try (InputStream is = getClass().getResourceAsStream("/test-documents/testPDF.pdf"); + TikaInputStream tis = TikaInputStream.get(is)) { + assertNotNull(is); + PageBasedRenderResults results = (PageBasedRenderResults) renderer.render( + tis, new Metadata(), context, new PageRangeRequest(1, 1)); + RenderResult r = results.getResults().get(0); + assertEquals(RenderResult.STATUS.SUCCESS, r.getStatus()); + try (TikaInputStream img = r.getInputStream()) { + byte[] b = img.readAllBytes(); + assertEquals((byte) 0x89, b[0]); + assertEquals((byte) 'P', b[1]); + results.close(); + return b.length; + } + } + } + + @Test + public void testPngCompressedByDefault() throws Exception { + // letter page, 300 dpi gray: raw raster ~8.5 MB; compressed must be far smaller + long bytes = renderedPngBytes(new ParseContext()); + assertTrue(bytes < 2_000_000, "default render should be a compressed PNG, got " + bytes); + } + + @Test + public void testImageQualityConfigurable() throws Exception { + // ImageIO's PNG "quality" 1.0 = uncompressed; proves the config reaches the writer + PDFParserConfig config = new PDFParserConfig(); + config.getOcr().setImageQuality(1.0f); + ParseContext context = new ParseContext(); + context.set(PDFParserConfig.class, config); + long uncompressed = renderedPngBytes(context); + long compressed = renderedPngBytes(new ParseContext()); + assertTrue(uncompressed > compressed * 4, + "quality 1.0 should be far larger: " + uncompressed + " vs " + compressed); + } +}
