You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2026/08/31 12:27:02 UTC
(tika) 01/01: TIKA-4862 - add compression/image quality setting to PDFBoxRenderer
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 <ta...@apache.org>
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);
+ }
+}