Skip to content

Commit b7e329a

Browse files
authored
TIKA-4862 - add compression/image quality setting to PDFBoxRenderer (#3103)
1 parent f865899 commit b7e329a

4 files changed

Lines changed: 105 additions & 6 deletions

File tree

CHANGES.txt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
Release 4.1.0 - unreleased
22

3+
* Allow image compression settings in PDFBox-based renderer (TIKA-4862).
4+
35
* The tika-server full and tika-grpc Docker images set OMP_THREAD_LIMIT=1:
4-
the distro tesseract links OpenMP and runs up to 4 threads per OCR
5-
process, which oversubscribes the CPU under forked parse workers;
6-
single-threaded tesseract with process-level parallelism is the
7-
tesseract project's guidance for servers (TIKA-XXXX).
6+
to avoid oversubscribing the CPU under forked parse workers (TIKA-4863).
87

98
* embedded-limits maxDepth counts embedding levels again instead of the
109
parsers a parse passes through; with AutoDetectParser over DefaultParser

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/OcrConfig.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,12 @@ public String toString() {
121121
private int dpi = 300;
122122
private ImageType imageType = ImageType.GRAY;
123123
private ImageFormat imageFormat = ImageFormat.PNG;
124-
private float imageQuality = 1.0f;
124+
/**
125+
* Compression quality handed to ImageIO when writing rendered pages. For PNG this is
126+
* an inverted effort knob, not fidelity: 1.0 writes an uncompressed file, 0.0 spends
127+
* ~10x the time of 0.5 for a few percent smaller output. PNG is always lossless.
128+
*/
129+
private float imageQuality = 0.5f;
125130

126131
/**
127132
* Maximum total pixels (width × height) allowed for a rendered

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/renderer/pdf/pdfbox/PDFBoxRenderer.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ public Set<MediaType> getSupportedTypes(ParseContext context) {
9090
private ImageType defaultImageType = ImageType.GRAY;
9191
private String defaultImageFormatName = "png";
9292

93+
/**
94+
* ImageIO's "compression quality"; for PNG it is an inverted effort knob:
95+
* 1.0 writes an uncompressed file, 0.0 spends ~10x the time of the default for a
96+
* few percent smaller output. 0.5 is both fast and small.
97+
*/
98+
private float defaultImageQuality = 0.5f;
99+
93100

94101
@Override
95102
public RenderResults render(TikaInputStream tis, Metadata metadata, ParseContext parseContext,
@@ -185,7 +192,8 @@ protected RenderResult renderPage(PDFRenderer renderer, int id, int pageNumber,
185192
metadata.set(PDFBOX_RENDERING_TIME_MS, renderingElapsed);
186193
start = System.currentTimeMillis();
187194
try (OutputStream os = Files.newOutputStream(tmpFile)) {
188-
ImageIOUtil.writeImage(image, getImageFormatName(parseContext), os, getDPI(parseContext));
195+
ImageIOUtil.writeImage(image, getImageFormatName(parseContext), os, getDPI(parseContext),
196+
getImageQuality(parseContext));
189197
}
190198
long elapsedWrite = System.currentTimeMillis() - start;
191199
metadata.set(PDFBOX_IMAGE_WRITING_TIME_MS, elapsedWrite);
@@ -216,6 +224,18 @@ public void setImageFormatName(String imageFormatName) {
216224
this.defaultImageFormatName = imageFormatName;
217225
}
218226

227+
public void setImageQuality(float imageQuality) {
228+
this.defaultImageQuality = imageQuality;
229+
}
230+
231+
protected float getImageQuality(ParseContext parseContext) {
232+
PDFParserConfig pdfParserConfig = parseContext.get(PDFParserConfig.class);
233+
if (pdfParserConfig == null) {
234+
return defaultImageQuality;
235+
}
236+
return pdfParserConfig.getOcr().getImageQuality();
237+
}
238+
219239
protected int getDPI(ParseContext parseContext) {
220240
PDFParserConfig pdfParserConfig = parseContext.get(PDFParserConfig.class);
221241
if (pdfParserConfig == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.tika.renderer.pdf.pdfbox;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertNotNull;
21+
import static org.junit.jupiter.api.Assertions.assertTrue;
22+
23+
import java.io.InputStream;
24+
25+
import org.junit.jupiter.api.Test;
26+
27+
import org.apache.tika.io.TikaInputStream;
28+
import org.apache.tika.metadata.Metadata;
29+
import org.apache.tika.parser.ParseContext;
30+
import org.apache.tika.parser.pdf.PDFParserConfig;
31+
import org.apache.tika.renderer.PageBasedRenderResults;
32+
import org.apache.tika.renderer.PageRangeRequest;
33+
import org.apache.tika.renderer.RenderResult;
34+
35+
public class PDFBoxRendererTest {
36+
37+
private long renderedPngBytes(ParseContext context) throws Exception {
38+
PDFBoxRenderer renderer = new PDFBoxRenderer();
39+
try (InputStream is = getClass().getResourceAsStream("/test-documents/testPDF.pdf");
40+
TikaInputStream tis = TikaInputStream.get(is)) {
41+
assertNotNull(is);
42+
PageBasedRenderResults results = (PageBasedRenderResults) renderer.render(
43+
tis, new Metadata(), context, new PageRangeRequest(1, 1));
44+
RenderResult r = results.getResults().get(0);
45+
assertEquals(RenderResult.STATUS.SUCCESS, r.getStatus());
46+
try (TikaInputStream img = r.getInputStream()) {
47+
byte[] b = img.readAllBytes();
48+
assertEquals((byte) 0x89, b[0]);
49+
assertEquals((byte) 'P', b[1]);
50+
results.close();
51+
return b.length;
52+
}
53+
}
54+
}
55+
56+
@Test
57+
public void testPngCompressedByDefault() throws Exception {
58+
// letter page, 300 dpi gray: raw raster ~8.5 MB; compressed must be far smaller
59+
long bytes = renderedPngBytes(new ParseContext());
60+
assertTrue(bytes < 2_000_000, "default render should be a compressed PNG, got " + bytes);
61+
}
62+
63+
@Test
64+
public void testImageQualityConfigurable() throws Exception {
65+
// ImageIO's PNG "quality" 1.0 = uncompressed; proves the config reaches the writer
66+
PDFParserConfig config = new PDFParserConfig();
67+
config.getOcr().setImageQuality(1.0f);
68+
ParseContext context = new ParseContext();
69+
context.set(PDFParserConfig.class, config);
70+
long uncompressed = renderedPngBytes(context);
71+
long compressed = renderedPngBytes(new ParseContext());
72+
assertTrue(uncompressed > compressed * 4,
73+
"quality 1.0 should be far larger: " + uncompressed + " vs " + compressed);
74+
}
75+
}

0 commit comments

Comments
 (0)