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);
+ }
+ }
+}