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 0f1fd02de2 TIKA-4902: skip ICC curve and lookup-table tags by default 
in image parsers (#3192)
0f1fd02de2 is described below

commit 0f1fd02de281584dfda48cb7224508a486e43685
Author: Tim Allison <[email protected]>
AuthorDate: Fri Sep 18 11:15:16 2026 -0400

    TIKA-4902: skip ICC curve and lookup-table tags by default in image parsers 
(#3192)
---
 CHANGES.txt                                        |  5 ++
 docs/modules/ROOT/nav.adoc                         |  1 +
 docs/modules/ROOT/pages/configuration/index.adoc   |  2 +
 .../pages/configuration/parsers/image-parsers.adoc | 54 ++++++++++++++++
 .../org/apache/tika/parser/image/BPGParser.java    | 19 +++++-
 .../org/apache/tika/parser/image/HeifParser.java   | 20 +++++-
 .../tika/parser/image/ImageMetadataConfig.java     | 58 ++++++++++++++++++
 .../tika/parser/image/ImageMetadataExtractor.java  | 67 ++++++++++++++++++--
 .../org/apache/tika/parser/image/JpegParser.java   | 20 +++++-
 .../apache/tika/parser/image/RawTiffParser.java    | 10 ++-
 .../org/apache/tika/parser/image/TiffParser.java   | 24 +++++++-
 .../org/apache/tika/parser/image/WebPParser.java   | 20 +++++-
 .../parser/image/ImageMetadataExtractorTest.java   | 53 ++++++++++++++++
 .../apache/tika/parser/image/JpegParserTest.java   | 71 ++++++++++++++++++++++
 .../configs/tika-config-icc-curves-on.json         | 10 +++
 15 files changed, 423 insertions(+), 11 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 60f2f9866a..82516f4584 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,10 @@
 Release 4.1.0 - unreleased
 
+   * ICC tone-curve and lookup-table tags (rTRC and friends) are no longer
+     written to icc:* by default; "jpeg-parser": {"includeIccCurvesAndLuts":
+     true} (likewise tiff-, heif-, bpg-, web-p- and raw-tiff-parser) restores
+     them (TIKA-4902).
+
    * Digesting an embedded document no longer makes its source hand out the
      bytes twice. (TIKA-4901).
 
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 66286e90eb..8f77dade5c 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -57,6 +57,7 @@
 *** xref:pipes/plugins/json.adoc[JSON]
 * xref:configuration/index.adoc[Configuration]
 ** xref:configuration/parsers/pdf-parser.adoc[PDF Parser]
+** xref:configuration/parsers/image-parsers.adoc[Image Parsers]
 ** xref:configuration/parsers/tesseract-ocr-parser.adoc[Tesseract OCR]
 ** xref:configuration/parsers/vlm-parsers.adoc[VLM Parsers (Claude, Gemini, 
OpenAI)]
 ** xref:configuration/parsers/external-parser.adoc[External Parser (ffmpeg, 
exiftool, etc.)]
diff --git a/docs/modules/ROOT/pages/configuration/index.adoc 
b/docs/modules/ROOT/pages/configuration/index.adoc
index 25db6243b6..541432897f 100644
--- a/docs/modules/ROOT/pages/configuration/index.adoc
+++ b/docs/modules/ROOT/pages/configuration/index.adoc
@@ -294,6 +294,8 @@ Parser configuration:
 
 * xref:configuration/pages.adoc[Pages] — rendering, OCR policy and page 
images, for every parser that renders
 * xref:configuration/parsers/pdf-parser.adoc[PDFParser] — PDF parsing options
+* xref:configuration/parsers/image-parsers.adoc[Image Parsers] — ICC metadata 
options for JPEG,
+  TIFF, HEIF, BPG and WebP
 * xref:configuration/parsers/tesseract-ocr-parser.adoc[TesseractOCRParser] — 
OCR for image-based
   text extraction
 * xref:configuration/parsers/tess4j-parser.adoc[Tess4J OCR Parser] — 
in-process OCR via tess4j JNA
diff --git a/docs/modules/ROOT/pages/configuration/parsers/image-parsers.adoc 
b/docs/modules/ROOT/pages/configuration/parsers/image-parsers.adoc
new file mode 100644
index 0000000000..dac1de871d
--- /dev/null
+++ b/docs/modules/ROOT/pages/configuration/parsers/image-parsers.adoc
@@ -0,0 +1,54 @@
+//
+// 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.
+//
+
+= Image Parsers Configuration
+
+Options shared by the parsers that read EXIF, IPTC and ICC metadata through 
metadata-extractor:
+`jpeg-parser`, `tiff-parser`, `raw-tiff-parser`, `heif-parser`, `bpg-parser` 
and `web-p-parser`.
+Each parser takes the options under its own name; `raw-tiff-parser` takes them 
alongside its
+preview options.
+
+[cols="1,1,3"]
+|===
+| Option | Default | Description
+
+| `includeIccCurvesAndLuts`
+| `false`
+| Write ICC profile tags whose data is a tone curve or lookup table (`curv`, 
`para`, `mft1`,
+  `mft2`, `mAB`, `mBA`; in practice `icc:Red TRC` and friends) to the 
metadata. Off, an
+  sRGB-profiled JPEG loses about 30 KB of numbers per file that nothing 
queries, and the cost of
+  formatting them. Header and text tags (`icc:Profile Description`, `icc:Color 
space`,
+  `icc:Device manufacturer`, ...) are always written.
+|===
+
+[source,json]
+----
+{
+  "parsers": [
+    "default-parser",
+    {
+      "jpeg-parser": {
+        "includeIccCurvesAndLuts": true
+      }
+    }
+  ]
+}
+----
+
+The same option can be set per request on the `ParseContext`, keyed by
+`org.apache.tika.parser.image.ImageMetadataConfig`, which then applies to 
every one of these
+parsers.
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/BPGParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/BPGParser.java
index a7439f4256..664a288901 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/BPGParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/BPGParser.java
@@ -28,6 +28,8 @@ import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
 import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ConfigDeserializer;
+import org.apache.tika.config.JsonConfig;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.exception.TikaMemoryLimitException;
 import org.apache.tika.io.EndianUtils;
@@ -58,6 +60,20 @@ public class BPGParser extends AbstractImageParser {
             new HashSet<>(
                     Arrays.asList(MediaType.image("x-bpg"), 
MediaType.image("bpg"))));
 
+    private final ImageMetadataConfig defaultConfig;
+
+    public BPGParser() {
+        this(new ImageMetadataConfig());
+    }
+
+    public BPGParser(ImageMetadataConfig config) {
+        this.defaultConfig = config;
+    }
+
+    public BPGParser(JsonConfig jsonConfig) {
+        this(ConfigDeserializer.buildConfig(jsonConfig, 
ImageMetadataConfig.class));
+    }
+
     public Set<MediaType> getSupportedTypes(ParseContext context) {
         return SUPPORTED_TYPES;
     }
@@ -149,7 +165,8 @@ public class BPGParser extends AbstractImageParser {
         // Extension Data
         if (hasExtensions) {
             long extensionsDataSeen = 0;
-            ImageMetadataExtractor metadataExtractor = new 
ImageMetadataExtractor(metadata);
+            ImageMetadataExtractor metadataExtractor = new 
ImageMetadataExtractor(metadata,
+                    ImageMetadataConfig.resolve(parseContext, "bpg-parser", 
defaultConfig));
 
             while (extensionsDataSeen < extensionDataLength) {
                 int extensionType = (int) EndianUtils.readUE7(stream);
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/HeifParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/HeifParser.java
index e6a1940346..757c1927c6 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/HeifParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/HeifParser.java
@@ -29,6 +29,8 @@ import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
 import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ConfigDeserializer;
+import org.apache.tika.config.JsonConfig;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TemporaryResources;
 import org.apache.tika.io.TikaInputStream;
@@ -45,6 +47,20 @@ public class HeifParser extends AbstractImageParser {
                     MediaType.image("heic"), MediaType.image("heic-sequence"),
                     MediaType.image("avif"))));
 
+    private final ImageMetadataConfig defaultConfig;
+
+    public HeifParser() {
+        this(new ImageMetadataConfig());
+    }
+
+    public HeifParser(ImageMetadataConfig config) {
+        this.defaultConfig = config;
+    }
+
+    public HeifParser(JsonConfig jsonConfig) {
+        this(ConfigDeserializer.buildConfig(jsonConfig, 
ImageMetadataConfig.class));
+    }
+
     @Override
     public Set<MediaType> getSupportedTypes(ParseContext context) {
         return SUPPORTED_TYPES;
@@ -66,7 +82,9 @@ public class HeifParser extends AbstractImageParser {
                 ImageXmp.scanAndExtract(tis, metadata, parseContext);
             }
             try (InputStream heif = Files.newInputStream(file.toPath())) {
-                new ImageMetadataExtractor(metadata).parseHeif(heif);
+                new ImageMetadataExtractor(metadata,
+                        ImageMetadataConfig.resolve(parseContext, 
"heif-parser", defaultConfig))
+                        .parseHeif(heif);
             }
         } finally {
             tmp.dispose();
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataConfig.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataConfig.java
new file mode 100644
index 0000000000..456a097293
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataConfig.java
@@ -0,0 +1,58 @@
+/*
+ * 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.parser.image;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.tika.config.ParseContextConfig;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.parser.ParseContext;
+
+/**
+ * Options for the metadata-extractor pass shared by the JPEG, TIFF, HEIF, BPG 
and WebP
+ * parsers. Each parser reads it from its own component name in the config, or 
from a
+ * class-keyed instance in the {@link ParseContext}.
+ */
+public class ImageMetadataConfig implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private boolean includeIccCurvesAndLuts = false;
+
+    /**
+     * Whether ICC profile tags whose data is a tone curve or lookup table 
(curv, para, mft1,
+     * mft2, mAB, mBA) are written to {@code icc:*}. Off by default: the 
values are long
+     * numeric dumps that nobody queries, and formatting them dominates the 
cost of reading an
+     * ICC-profiled image. Header and text tags (profile description, color 
space, device
+     * class, ...) are always kept.
+     */
+    public boolean isIncludeIccCurvesAndLuts() {
+        return includeIccCurvesAndLuts;
+    }
+
+    public void setIncludeIccCurvesAndLuts(boolean includeIccCurvesAndLuts) {
+        this.includeIccCurvesAndLuts = includeIccCurvesAndLuts;
+    }
+
+    static ImageMetadataConfig resolve(ParseContext context, String configKey,
+                                       ImageMetadataConfig defaultConfig)
+            throws TikaException, IOException {
+        return ParseContextConfig.getConfig(context, configKey, 
ImageMetadataConfig.class,
+                defaultConfig);
+    }
+}
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
index 41f2b4bca3..6fa3299e3d 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
@@ -106,10 +106,18 @@ public class ImageMetadataExtractor {
     private DirectoryHandler[] handlers;
 
     /**
-     * @param metadata to extract to, using default directory handlers
+     * @param metadata to extract to, using default directory handlers and 
options
      */
     public ImageMetadataExtractor(Metadata metadata) {
-        this(metadata, new CopyUnknownFieldsHandler(), new 
TiffPageNumberHandler(),
+        this(metadata, new ImageMetadataConfig());
+    }
+
+    /**
+     * @param metadata to extract to, using default directory handlers
+     * @param config   options for the default handlers
+     */
+    public ImageMetadataExtractor(Metadata metadata, ImageMetadataConfig 
config) {
+        this(metadata, new CopyUnknownFieldsHandler(config), new 
TiffPageNumberHandler(),
                 new JpegCommentHandler(), new ExifHandler(), new 
DimensionsHandler(),
                 new GeotagHandler(), new IptcHandler());
     }
@@ -347,6 +355,24 @@ public class ImageMetadataExtractor {
      * This leads to more predictable behavior than {@link 
CopyAllFieldsHandler}.
      */
     static class CopyUnknownFieldsHandler implements DirectoryHandler {
+        // ICC tag-table entries carry a 4-byte type signature; these types 
are curves and LUTs
+        private static final int ICC_TYPE_CURV = 0x63757276;
+        private static final int ICC_TYPE_PARA = 0x70617261;
+        private static final int ICC_TYPE_MFT1 = 0x6D667431;
+        private static final int ICC_TYPE_MFT2 = 0x6D667432;
+        private static final int ICC_TYPE_MAB = 0x6D414220;
+        private static final int ICC_TYPE_MBA = 0x6D424120;
+
+        private final boolean includeIccCurvesAndLuts;
+
+        CopyUnknownFieldsHandler() {
+            this(new ImageMetadataConfig());
+        }
+
+        CopyUnknownFieldsHandler(ImageMetadataConfig config) {
+            this.includeIccCurvesAndLuts = config.isIncludeIccCurvesAndLuts();
+        }
+
         public boolean supports(Class<? extends Directory> directoryType) {
             return true;
         }
@@ -355,8 +381,17 @@ public class ImageMetadataExtractor {
             if (directory.getTags() != null) {
                 for (Tag tag : directory.getTags()) {
                     String name = tag.getTagName();
-                    if (!MetadataFields.isMetadataField(name) && 
tag.getDescription() != null) {
-                        String value = tag.getDescription().trim();
+                    if (MetadataFields.isMetadataField(name)) {
+                        continue;
+                    }
+                    // decided before getDescription(): formatting a curve is 
the whole cost
+                    if (!includeIccCurvesAndLuts && directory instanceof 
IccDirectory &&
+                            isIccCurveOrLut(directory, tag.getTagType())) {
+                        continue;
+                    }
+                    String description = tag.getDescription();
+                    if (description != null) {
+                        String value = description.trim();
                         if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
                             value = Boolean.TRUE.toString();
                         } else if 
(Boolean.FALSE.toString().equalsIgnoreCase(value)) {
@@ -373,6 +408,30 @@ public class ImageMetadataExtractor {
                 }
             }
         }
+
+        private static boolean isIccCurveOrLut(Directory directory, int 
tagType) {
+            // header fields sit below the printable-ASCII range of tag-table 
signatures
+            if (tagType <= 0x20202020 || tagType >= 0x7a7a7a7a) {
+                return false;
+            }
+            byte[] data = directory.getByteArray(tagType);
+            if (data == null || data.length < 4) {
+                return false;
+            }
+            int type = ((data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) |
+                    ((data[2] & 0xff) << 8) | (data[3] & 0xff);
+            switch (type) {
+                case ICC_TYPE_CURV:
+                case ICC_TYPE_PARA:
+                case ICC_TYPE_MFT1:
+                case ICC_TYPE_MFT2:
+                case ICC_TYPE_MAB:
+                case ICC_TYPE_MBA:
+                    return true;
+                default:
+                    return false;
+            }
+        }
     }
 
 
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/JpegParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/JpegParser.java
index bf49883b5a..151fa1e287 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/JpegParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/JpegParser.java
@@ -25,6 +25,8 @@ import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
 import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ConfigDeserializer;
+import org.apache.tika.config.JsonConfig;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.CacheMemoryBudget;
 import org.apache.tika.io.TemporaryResources;
@@ -44,6 +46,20 @@ public class JpegParser extends AbstractImageParser {
     private static final Set<MediaType> SUPPORTED_TYPES =
             Collections.singleton(MediaType.image("jpeg"));
 
+    private final ImageMetadataConfig defaultConfig;
+
+    public JpegParser() {
+        this(new ImageMetadataConfig());
+    }
+
+    public JpegParser(ImageMetadataConfig config) {
+        this.defaultConfig = config;
+    }
+
+    public JpegParser(JsonConfig jsonConfig) {
+        this(ConfigDeserializer.buildConfig(jsonConfig, 
ImageMetadataConfig.class));
+    }
+
     public Set<MediaType> getSupportedTypes(ParseContext context) {
         return SUPPORTED_TYPES;
     }
@@ -60,7 +76,9 @@ public class JpegParser extends AbstractImageParser {
             // XMP first so it is canonical; the metadata-extractor handlers 
(IPTC/EXIF) fill gaps.
             ImageXmp.extractJpeg(tis, metadata, parseContext);
             tis.rewind();
-            new ImageMetadataExtractor(metadata).parseJpeg(tis);
+            new ImageMetadataExtractor(metadata,
+                    ImageMetadataConfig.resolve(parseContext, "jpeg-parser", 
defaultConfig))
+                    .parseJpeg(tis);
         } finally {
             tmp.dispose();
         }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
index 44ca2202f8..825e2cc2f0 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
@@ -18,7 +18,6 @@ package org.apache.tika.parser.image;
 
 import java.io.IOException;
 import java.io.RandomAccessFile;
-import java.io.Serializable;
 import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
 import java.util.ArrayDeque;
@@ -128,6 +127,7 @@ public class RawTiffParser extends TiffParser {
     }
 
     public RawTiffParser(RawTiffParserConfig config) {
+        super(config);
         this.defaultConfig = config;
     }
 
@@ -165,6 +165,12 @@ public class RawTiffParser extends TiffParser {
                 RawTiffParserConfig.class, defaultConfig);
     }
 
+    @Override
+    protected ImageMetadataConfig getImageMetadataConfig(ParseContext context)
+            throws TikaException, IOException {
+        return getConfig(context);
+    }
+
     private void extractPreviews(TikaInputStream tis, XHTMLContentHandler 
xhtml, Metadata metadata,
                                  ParseContext context, RawTiffParserConfig 
config)
             throws IOException, SAXException {
@@ -531,7 +537,7 @@ public class RawTiffParser extends TiffParser {
     /**
      * Configuration class for RawTiffParser.
      */
-    public static class RawTiffParserConfig implements Serializable {
+    public static class RawTiffParserConfig extends ImageMetadataConfig {
 
         private static final long serialVersionUID = 1990316744955315312L;
 
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/TiffParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/TiffParser.java
index 4a5cad5f48..056f36dcae 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/TiffParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/TiffParser.java
@@ -25,6 +25,8 @@ import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
 import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ConfigDeserializer;
+import org.apache.tika.config.JsonConfig;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.CacheMemoryBudget;
 import org.apache.tika.io.TemporaryResources;
@@ -44,6 +46,20 @@ public class TiffParser extends AbstractImageParser {
     private static final Set<MediaType> SUPPORTED_TYPES =
             Collections.singleton(MediaType.image("tiff"));
 
+    private final ImageMetadataConfig defaultConfig;
+
+    public TiffParser() {
+        this(new ImageMetadataConfig());
+    }
+
+    public TiffParser(ImageMetadataConfig config) {
+        this.defaultConfig = config;
+    }
+
+    public TiffParser(JsonConfig jsonConfig) {
+        this(ConfigDeserializer.buildConfig(jsonConfig, 
ImageMetadataConfig.class));
+    }
+
     public Set<MediaType> getSupportedTypes(ParseContext context) {
         return SUPPORTED_TYPES;
     }
@@ -59,9 +75,15 @@ public class TiffParser extends AbstractImageParser {
             // XMP first so it is canonical; metadata-extractor (IPTC/EXIF) 
fills gaps.
             ImageXmp.scanAndExtract(tis, metadata, parseContext);
             tis.rewind();
-            new ImageMetadataExtractor(metadata).parseTiff(tis);
+            new ImageMetadataExtractor(metadata, 
getImageMetadataConfig(parseContext))
+                    .parseTiff(tis);
         } finally {
             tmp.dispose();
         }
     }
+
+    protected ImageMetadataConfig getImageMetadataConfig(ParseContext context)
+            throws TikaException, IOException {
+        return ImageMetadataConfig.resolve(context, "tiff-parser", 
defaultConfig);
+    }
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/WebPParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/WebPParser.java
index 459aa07def..e4890576fa 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/WebPParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/WebPParser.java
@@ -24,6 +24,8 @@ import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
 import org.apache.tika.annotation.TikaComponent;
+import org.apache.tika.config.ConfigDeserializer;
+import org.apache.tika.config.JsonConfig;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.CacheMemoryBudget;
 import org.apache.tika.io.TikaInputStream;
@@ -44,6 +46,20 @@ public class WebPParser implements Parser {
     private static final Set<MediaType> SUPPORTED_TYPES =
             Collections.singleton(MediaType.image("webp"));
 
+    private final ImageMetadataConfig defaultConfig;
+
+    public WebPParser() {
+        this(new ImageMetadataConfig());
+    }
+
+    public WebPParser(ImageMetadataConfig config) {
+        this.defaultConfig = config;
+    }
+
+    public WebPParser(JsonConfig jsonConfig) {
+        this(ConfigDeserializer.buildConfig(jsonConfig, 
ImageMetadataConfig.class));
+    }
+
     public Set<MediaType> getSupportedTypes(ParseContext context) {
         return SUPPORTED_TYPES;
     }
@@ -54,7 +70,9 @@ public class WebPParser implements Parser {
         tis.enableRewind(context.get(CacheMemoryBudget.class));
         ImageXmp.extractWebp(tis, metadata, context);
         tis.rewind();
-        new ImageMetadataExtractor(metadata).parseWebP(tis);
+        new ImageMetadataExtractor(metadata,
+                ImageMetadataConfig.resolve(context, "web-p-parser", 
defaultConfig))
+                .parseWebP(tis);
 
         XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata, 
context);
         xhtml.startDocument();
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
index 4db12e8b71..7bc751c9b5 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
@@ -21,6 +21,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.GregorianCalendar;
 import java.util.Iterator;
@@ -35,6 +38,7 @@ import com.drew.metadata.Tag;
 import com.drew.metadata.exif.ExifIFD0Directory;
 import com.drew.metadata.exif.ExifSubIFDDirectory;
 import com.drew.metadata.exif.GpsDirectory;
+import com.drew.metadata.icc.IccDirectory;
 import com.drew.metadata.jpeg.JpegCommentDirectory;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
@@ -159,4 +163,53 @@ public class ImageMetadataExtractorTest {
         assertNull(metadata.get(TikaCoreProperties.DESCRIPTION));
     }
 
+    @Test
+    public void testIccCurvesSkippedUnlessConfigured() throws 
MetadataException {
+        List<Integer> described = new ArrayList<>();
+        IccDirectory d = new IccDirectory() {
+            @Override
+            public String getDescription(int tagType) {
+                described.add(tagType);
+                return super.getDescription(tagType);
+            }
+        };
+        d.setString(IccDirectory.TAG_COLOR_SPACE, "RGB ");
+        d.setByteArray(IccDirectory.TAG_TAG_desc, iccDesc("sRGB"));
+        d.setByteArray(IccDirectory.TAG_TAG_rTRC, iccCurve(2));
+
+        Metadata metadata = new Metadata();
+        new ImageMetadataExtractor.CopyUnknownFieldsHandler().handle(d, 
metadata);
+        assertEquals("RGB", metadata.get(ImageMetadataExtractor.ICC_NS + 
"Color space"));
+        assertEquals("sRGB", metadata.get(ImageMetadataExtractor.ICC_NS + 
"Profile Description"));
+        assertNull(metadata.get(ImageMetadataExtractor.ICC_NS + "Red TRC"));
+        //the skip happens before the curve is formatted, not after
+        assertFalse(described.contains(IccDirectory.TAG_TAG_rTRC));
+
+        ImageMetadataConfig config = new ImageMetadataConfig();
+        config.setIncludeIccCurvesAndLuts(true);
+        metadata = new Metadata();
+        new ImageMetadataExtractor.CopyUnknownFieldsHandler(config).handle(d, 
metadata);
+        assertEquals("sRGB", metadata.get(ImageMetadataExtractor.ICC_NS + 
"Profile Description"));
+        assertEquals("0.0, 1.0", metadata.get(ImageMetadataExtractor.ICC_NS + 
"Red TRC"));
+    }
+
+    //ICC 'desc' tag: type, reserved, byte count including the NUL, ASCII, NUL
+    private static byte[] iccDesc(String text) {
+        byte[] ascii = text.getBytes(StandardCharsets.US_ASCII);
+        ByteBuffer b = ByteBuffer.allocate(12 + ascii.length + 1);
+        
b.put("desc".getBytes(StandardCharsets.US_ASCII)).putInt(0).putInt(ascii.length 
+ 1)
+                .put(ascii).put((byte) 0);
+        return b.array();
+    }
+
+    //ICC 'curv' tag: type, reserved, count, count uint16 samples spread over 
0..1
+    private static byte[] iccCurve(int count) {
+        ByteBuffer b = ByteBuffer.allocate(12 + 2 * count);
+        
b.put("curv".getBytes(StandardCharsets.US_ASCII)).putInt(0).putInt(count);
+        for (int i = 0; i < count; i++) {
+            b.putShort((short) (65535L * i / (count - 1)));
+        }
+        return b.array();
+    }
+
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/JpegParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/JpegParserTest.java
index a493205ce0..11d6d5a9f0 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/JpegParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/JpegParserTest.java
@@ -18,12 +18,20 @@ package org.apache.tika.parser.image;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.List;
 import java.util.TimeZone;
+import javax.imageio.ImageIO;
 
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
@@ -34,6 +42,7 @@ import org.junit.jupiter.api.parallel.Resources;
 import org.xml.sax.helpers.DefaultHandler;
 
 import org.apache.tika.TikaTest;
+import org.apache.tika.config.loader.TikaLoader;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Geographic;
 import org.apache.tika.metadata.HttpHeaders;
@@ -296,4 +305,66 @@ public class JpegParserTest extends TikaTest {
         assertEquals("xmp.did:49E997348D4911E1AB62EBF9B374B234", 
metadata.get(XMPMM.DOCUMENTID));
     }
 
+    @Test
+    public void testIccCurvesSkippedByDefault() throws Exception {
+        Metadata metadata = parseSyntheticIccJpeg(parser, new ParseContext());
+        assertEquals("RGB", metadata.get(ImageMetadataExtractor.ICC_NS + 
"Color space"));
+        assertNotNull(metadata.get(ImageMetadataExtractor.ICC_NS + "Profile 
Description"));
+        assertNull(metadata.get(ImageMetadataExtractor.ICC_NS + "Red TRC"));
+        assertNull(metadata.get(ImageMetadataExtractor.ICC_NS + "Blue TRC"));
+    }
+
+    @Test
+    public void testIccCurvesViaParseContext() throws Exception {
+        ImageMetadataConfig config = new ImageMetadataConfig();
+        config.setIncludeIccCurvesAndLuts(true);
+        ParseContext context = new ParseContext();
+        context.set(ImageMetadataConfig.class, config);
+        Metadata metadata = parseSyntheticIccJpeg(parser, context);
+        String redTrc = metadata.get(ImageMetadataExtractor.ICC_NS + "Red 
TRC");
+        assertNotNull(redTrc);
+        //the JDK sRGB profile has a 1024-point curve
+        assertEquals(1024, redTrc.split(", ").length);
+    }
+
+    @Test
+    public void testIccCurvesViaJsonConfig() throws Exception {
+        Parser loaded = TikaLoader
+                .load(getConfigPath(JpegParserTest.class, 
"tika-config-icc-curves-on.json"))
+                .loadParsers();
+        Metadata metadata = parseSyntheticIccJpeg(loaded, new ParseContext());
+        assertNotNull(metadata.get(ImageMetadataExtractor.ICC_NS + "Red TRC"));
+    }
+
+    private static Metadata parseSyntheticIccJpeg(Parser parser, ParseContext 
context)
+            throws Exception {
+        Metadata metadata = new Metadata();
+        metadata.set(HttpHeaders.CONTENT_TYPE, "image/jpeg");
+        try (TikaInputStream tis = TikaInputStream.get(jpegWithSrgbProfile())) 
{
+            parser.parse(tis, new DefaultHandler(), metadata, context);
+        }
+        return metadata;
+    }
+
+    //a 4x4 JPEG with the JDK's sRGB profile in an APP2 ICC_PROFILE segment 
after SOI
+    private static byte[] jpegWithSrgbProfile() throws IOException {
+        ByteArrayOutputStream plain = new ByteArrayOutputStream();
+        ImageIO.write(new BufferedImage(4, 4, BufferedImage.TYPE_INT_RGB), 
"jpeg", plain);
+        byte[] jpeg = plain.toByteArray();
+        byte[] profile = ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData();
+        byte[] marker = "ICC_PROFILE\0".getBytes(StandardCharsets.US_ASCII);
+        int segmentLength = 2 + marker.length + 2 + profile.length;
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write(jpeg, 0, 2);
+        out.write(0xFF);
+        out.write(0xE2);
+        out.write(segmentLength >> 8);
+        out.write(segmentLength & 0xFF);
+        out.write(marker);
+        out.write(1);
+        out.write(1);
+        out.write(profile);
+        out.write(jpeg, 2, jpeg.length - 2);
+        return out.toByteArray();
+    }
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/resources/configs/tika-config-icc-curves-on.json
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/resources/configs/tika-config-icc-curves-on.json
new file mode 100644
index 0000000000..f115ee5b62
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/resources/configs/tika-config-icc-curves-on.json
@@ -0,0 +1,10 @@
+{
+  "parsers": [
+    "default-parser",
+    {
+      "jpeg-parser": {
+        "includeIccCurvesAndLuts": true
+      }
+    }
+  ]
+}

Reply via email to