This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new b7e329a9ce TIKA-4862 - add compression/image quality setting to
PDFBoxRenderer (#3103)
b7e329a9ce is described below
commit b7e329a9cebf8fe885be7a4b30cd0d150952d73f
Author: Tim Allison <[email protected]>
AuthorDate: Mon Aug 31 09:46:21 2026 -0400
TIKA-4862 - add compression/image quality setting to PDFBoxRenderer (#3103)
---
CHANGES.txt | 7 +-
.../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, 105 insertions(+), 6 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index cfc2c66ea6..595ca638b8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,10 +1,9 @@
Release 4.1.0 - unreleased
+ * Allow image compression settings in PDFBox-based renderer (TIKA-4862).
+
* The tika-server full and tika-grpc Docker images set OMP_THREAD_LIMIT=1:
- the distro tesseract links OpenMP and runs up to 4 threads per OCR
- process, which oversubscribes the CPU under forked parse workers;
- single-threaded tesseract with process-level parallelism is the
- tesseract project's guidance for servers (TIKA-XXXX).
+ to avoid oversubscribing the CPU under forked parse workers (TIKA-4863).
* embedded-limits maxDepth counts embedding levels again instead of the
parsers a parse passes through; with AutoDetectParser over DefaultParser
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);
+ }
+}