Author: tilman
Date: Sun Sep 20 15:23:58 2026
New Revision: 1938392

Log:
PDFBOX-6260: adjust to PDF specification + test, by Valery Bokov and Claude Code

Added:
   
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/color/PDCalGrayTest.java
   (contents, props changed)
Modified:
   
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDCalGray.java

Modified: 
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDCalGray.java
==============================================================================
--- 
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDCalGray.java
   Sun Sep 20 12:45:04 2026        (r1938391)
+++ 
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDCalGray.java
   Sun Sep 20 15:23:58 2026        (r1938392)
@@ -83,25 +83,20 @@ public final class PDCalGray extends PDC
     @Override
     public float[] toRGB(float[] value)
     {
-        // see implementation of toRGB in PDCalRGB, and PDFBOX-2971
-        if (isWhitePoint())
+        float a = value[0];
+        float[] result = map1.get(a);
+        if (result != null)
         {
-            float a = value[0];
-            float[] result = map1.get(a);
-            if (result != null)
-            {
-                return result.clone();
-            }
-            float gamma = getGamma();
-            float powAG = (float) Math.pow(a, gamma);
-            result = convXYZtoRGB(powAG, powAG, powAG);
-            map1.put(a, result.clone());
-            return result;
-        }
-        else
-        {
-            return new float[] { value[0], value[0], value[0] };
+            return result.clone();
         }
+        float gamma = getGamma();
+        float powAG = (float) Math.pow(a, gamma);
+        // X, Y and Z are the whitepoint scaled by the gamma-corrected value. 
Calibrating only for
+        // whitepoint (1 1 1) and skipping it otherwise (PDFBOX-2971) was a 
workaround for the
+        // missing chromatic adaptation of the whitepoint (PDFBOX-6260); it 
ignored the gamma.
+        result = convXYZtoRGB(wpX * powAG, wpY * powAG, wpZ * powAG);
+        map1.put(a, result.clone());
+        return result;
     }
 
     /**

Added: 
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/color/PDCalGrayTest.java
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ 
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/color/PDCalGrayTest.java
       Sun Sep 20 15:23:58 2026        (r1938392)
@@ -0,0 +1,104 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests the calibration of CalGray. A neutral gray must only depend on the 
gamma, not on the
+ * whitepoint the space is declared relative to.
+ */
+class PDCalGrayTest
+{
+    private static final float[][] WHITEPOINTS =
+    {
+        { 1f, 1f, 1f },
+        { 0.9642f, 1f, 0.8249f },  // D50
+        { 0.9505f, 1f, 1.089f },   // D65
+        { 0.95045f, 1f, 1.08905f } // D65, more precise
+    };
+
+    private static PDCalGray create(float[] whitepoint, float gamma)
+    {
+        PDCalGray calGray = new PDCalGray();
+        PDTristimulus tristimulus = new PDTristimulus();
+        tristimulus.setX(whitepoint[0]);
+        tristimulus.setY(whitepoint[1]);
+        tristimulus.setZ(whitepoint[2]);
+        calGray.setWhitePoint(tristimulus);
+        calGray.setGamma(gamma);
+        return calGray;
+    }
+
+    /**
+     * PDFBOX-2971 and PDFBOX-6260: white and black must stay white and black 
for every whitepoint
+     * (white was once rendered as cyan because the whitepoint wasn't adapted).
+     */
+    @Test
+    void testWhiteAndBlack()
+    {
+        for (float[] whitepoint : WHITEPOINTS)
+        {
+            PDCalGray calGray = create(whitepoint, 2.2f);
+            assertEquals(1f, calGray.toRGB(new float[] { 1f })[0], 0.01f);
+            assertEquals(1f, calGray.toRGB(new float[] { 1f })[1], 0.01f);
+            assertEquals(1f, calGray.toRGB(new float[] { 1f })[2], 0.01f);
+            assertEquals(0f, calGray.toRGB(new float[] { 0f })[0], 0.01f);
+            assertEquals(0f, calGray.toRGB(new float[] { 0f })[1], 0.01f);
+            assertEquals(0f, calGray.toRGB(new float[] { 0f })[2], 0.01f);
+        }
+    }
+
+    /**
+     * A gray must be neutral and must not depend on the whitepoint, only on 
the gamma.
+     */
+    @Test
+    void testIndependentOfWhitepoint()
+    {
+        for (float gamma : new float[] { 1f, 1.8f, 2.2f, 3f })
+        {
+            float[] reference = create(WHITEPOINTS[0], gamma).toRGB(new 
float[] { 0.5f });
+            for (float[] whitepoint : WHITEPOINTS)
+            {
+                float[] rgb = create(whitepoint, gamma).toRGB(new float[] { 
0.5f });
+                for (int i = 0; i < 3; i++)
+                {
+                    assertEquals(rgb[0], rgb[i], 0.01f, "not neutral, gamma " 
+ gamma);
+                    assertEquals(reference[i], rgb[i], 0.01f, "gamma " + 
gamma);
+                }
+            }
+        }
+    }
+
+    /**
+     * The gamma must be applied also for a whitepoint other than (1 1 1); 
before, it was ignored.
+     * The expected values are the sRGB encoding of the linear value 0.5^gamma.
+     */
+    @Test
+    void testGamma()
+    {
+        for (float[] whitepoint : WHITEPOINTS)
+        {
+            // linear value 0.5 is 0.735 in sRGB
+            assertEquals(0.735f, create(whitepoint, 1f).toRGB(new float[] { 
0.5f })[0], 0.01f);
+            // gamma 2.2 is close to the sRGB curve, so the value stays about 
the same
+            assertEquals(0.5f, create(whitepoint, 2.2f).toRGB(new float[] { 
0.5f })[0], 0.02f);
+        }
+    }
+}

Reply via email to