Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -140,10 +140,11 @@ public ICC_ColorSpace(ICC_Profile profile) {
if (profileClass != ICC_Profile.CLASS_INPUT
&& profileClass != ICC_Profile.CLASS_DISPLAY
&& profileClass != ICC_Profile.CLASS_OUTPUT
&& profileClass != ICC_Profile.CLASS_DEVICELINK
&& profileClass != ICC_Profile.CLASS_COLORSPACECONVERSION
&& profileClass != ICC_Profile.CLASS_NAMEDCOLOR
&& profileClass != ICC_Profile.CLASS_ABSTRACT) {
throw new IllegalArgumentException("Invalid profile type");
throw new IllegalArgumentException("Invalid profile class");
}

thisProfile = profile;
Expand Down
102 changes: 98 additions & 4 deletions src/java.desktop/share/classes/java/awt/color/ICC_Profile.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -110,15 +110,23 @@ public sealed class ICC_Profile implements Serializable
*/
private transient volatile ProfileDeferralInfo deferralInfo;


/**
* Set to {@code true} for {@code BuiltInProfile}, {@code false} otherwise.
* This flag is used in {@link #setData(int, byte[])} to prevent modifying
* built-in profiles.
*/
private final transient boolean builtIn;

/**
* The lazy registry of singleton profile objects for specific built-in
* color spaces defined in the ColorSpace class (e.g. CS_sRGB),
* see getInstance(int cspace) factory method.
*/
private interface BuiltInProfile {
/*
* Deferral is only used for standard profiles. Enabling the appropriate
* access privileges is handled at a lower level.
* ProfileDeferralInfo is used for built-in profile creation only,
* and all built-in profiles should be constructed using it.
*/
ICC_Profile SRGB = new ICC_ProfileRGB(new ProfileDeferralInfo(
"sRGB.pf", ColorSpace.TYPE_RGB, 3, CLASS_DISPLAY));
Expand Down Expand Up @@ -759,20 +767,27 @@ private interface BuiltInProfile {
*/
public static final int icXYZNumberX = 8;

private static final int HEADER_SIZE = 128;

/**
* Constructs an {@code ICC_Profile} object with a given ID.
*/
ICC_Profile(Profile p) {
cmmProfile = p;
builtIn = false;
}

/**
* Constructs an {@code ICC_Profile} object whose loading will be deferred.
* The ID will be 0 until the profile is loaded.
*
* <p>
* Note: {@code ProfileDeferralInfo} is used for built-in profile
* creation only, and all built-in profiles should be constructed using it.
*/
ICC_Profile(ProfileDeferralInfo pdi) {
deferralInfo = pdi;
builtIn = true;
}

/**
Expand All @@ -789,10 +804,15 @@ public static ICC_Profile getInstance(byte[] data) {
ProfileDataVerifier.verify(data);
Profile p;
try {
byte[] theHeader = new byte[HEADER_SIZE];
System.arraycopy(data, 0, theHeader, 0, HEADER_SIZE);
verifyHeader(theHeader);

p = CMSManager.getModule().loadProfile(data);
} catch (CMMException c) {
throw new IllegalArgumentException("Invalid ICC Profile Data");
}

try {
if (getColorSpaceType(p) == ColorSpace.TYPE_GRAY
&& getData(p, icSigMediaWhitePointTag) != null
Expand Down Expand Up @@ -978,6 +998,10 @@ public int getProfileClass() {
return info.profileClass;
}
byte[] theHeader = getData(icSigHead);
return getProfileClass(theHeader);
}

private static int getProfileClass(byte[] theHeader) {
int theClassSig = intFromBigEndian(theHeader, icHdrDeviceClass);
return switch (theClassSig) {
case icSigInputClass -> CLASS_INPUT;
Expand Down Expand Up @@ -1019,6 +1043,11 @@ private static int getColorSpaceType(Profile p) {
return iccCStoJCS(theColorSpaceSig);
}

private static int getColorSpaceType(byte[] theHeader) {
int theColorSpaceSig = intFromBigEndian(theHeader, icHdrColorSpace);
return iccCStoJCS(theColorSpaceSig);
}

/**
* Returns the color space type of the Profile Connection Space (PCS).
* Returns one of the color space type constants defined by the ColorSpace
Expand All @@ -1038,6 +1067,21 @@ public int getPCSType() {
return iccCStoJCS(thePCSSig);
}

private static int getPCSType(byte[] theHeader) {
int thePCSSig = intFromBigEndian(theHeader, icHdrPcs);
int theDeviceClass = intFromBigEndian(theHeader, icHdrDeviceClass);

if (theDeviceClass == icSigLinkClass) {
return iccCStoJCS(thePCSSig);
} else {
return switch (thePCSSig) {
case icSigXYZData -> ColorSpace.TYPE_XYZ;
case icSigLabData -> ColorSpace.TYPE_Lab;
default -> throw new IllegalArgumentException("Unexpected PCS type");
};
}
}

/**
* Write this {@code ICC_Profile} to a file.
*
Expand Down Expand Up @@ -1107,20 +1151,70 @@ private static byte[] getData(Profile p, int tagSignature) {
* This method is useful for advanced applications which need to access
* profile data directly.
*
* <p>
* Note: JDK built-in ICC Profiles cannot be updated using this method
* as it will result in {@code IllegalArgumentException}. JDK built-in
* profiles are those obtained by {@code ICC_Profile.getInstance(int colorSpaceID)}
* where {@code colorSpaceID} is one of the following:
* {@link ColorSpace#CS_sRGB}, {@link ColorSpace#CS_LINEAR_RGB},
* {@link ColorSpace#CS_PYCC}, {@link ColorSpace#CS_GRAY} or
* {@link ColorSpace#CS_CIEXYZ}.
*
* @param tagSignature the ICC tag signature for the data element you want
* to set
* @param tagData the data to set for the specified tag signature
* @throws IllegalArgumentException if {@code tagSignature} is not a
* signature as defined in the ICC specification.
* @throws IllegalArgumentException if a content of the {@code tagData}
* @throws IllegalArgumentException if the content of the {@code tagData}
* array can not be interpreted as valid tag data, corresponding to
* the {@code tagSignature}
* @throws IllegalArgumentException if this is a built-in profile for one
* of the pre-defined color spaces, that is those which can be obtained
* by calling {@code ICC_Profile.getInstance(int colorSpaceID)}
* @see #getData
* @see ColorSpace
*/
public void setData(int tagSignature, byte[] tagData) {
if (builtIn) {
throw new IllegalArgumentException("Built-in profile cannot be modified");
}

if (tagSignature == ICC_Profile.icSigHead) {
verifyHeader(tagData);
}
CMSManager.getModule().setTagData(cmmProfile(), tagSignature, tagData);
}

private static void verifyHeader(byte[] data) {
if (data == null || data.length < HEADER_SIZE) {
throw new IllegalArgumentException("Invalid header data");
}
getProfileClass(data);
getColorSpaceType(data);
getPCSType(data);
checkRenderingIntent(data);
}

private static boolean checkRenderingIntent(byte[] header) {
int index = ICC_Profile.icHdrRenderingIntent;

/* According to ICC spec, only the least-significant 16 bits shall be
* used to encode the rendering intent. The most significant 16 bits
* shall be set to zero. Thus, we are ignoring two most significant
* bytes here. Please refer ICC Spec Document for more details.
*/
int renderingIntent = ((header[index+2] & 0xff) << 8) |
(header[index+3] & 0xff);

switch (renderingIntent) {
case icPerceptual, icMediaRelativeColorimetric,
icSaturation, icAbsoluteColorimetric -> {
return true;
}
default -> throw new IllegalArgumentException("Unknown Rendering Intent");
}
}

/**
* Returns the number of color components in the "input" color space of this
* profile. For example if the color space type of this profile is
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
* @test
* @bug 8346465
* @summary Tests if setData() throws IAE for BuiltIn profiles
*/

import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Map;

public class BuiltInProfileCheck {
private static final int HEADER_TAG = ICC_Profile.icSigHead;
private static final int INDEX = ICC_Profile.icHdrDeviceClass;
private static final String EXCEPTION_MSG = "Built-in profile cannot be modified";
/**
* {@link #prepareTestProfile(String, boolean, int)}
* stores the profile to test in testProfile.
*/
private static ICC_Profile testProfile;

private static final Map<Integer, String> colorSpace = Map.of(
ColorSpace.CS_sRGB, "CS_sRGB",
ColorSpace.CS_PYCC, "CS_PYCC",
ColorSpace.CS_GRAY, "CS_GRAY",
ColorSpace.CS_CIEXYZ, "CS_CIEXYZ",
ColorSpace.CS_LINEAR_RGB, "CS_LINEAR_RGB"
);

public static void main(String[] args) throws Exception {
System.out.println("CASE 1: Testing BuiltIn Profile");
for (int cs : colorSpace.keySet()) {
prepareTestProfile("Default", true, cs);
testProfile(true, cs);
}
System.out.println("Passed\n");

System.out.println("CASE 2: Testing Custom Profile");
prepareTestProfile("Default", false, ColorSpace.CS_sRGB);
testProfile(false, ColorSpace.CS_sRGB);
System.out.println("Passed\n");

System.out.println("CASE 3: Testing Built-In Profile"
+ " Serialization & Deserialization");
for (int cs : colorSpace.keySet()) {
prepareTestProfile("Serialize", true, cs);
testProfile(true, cs);
}
System.out.println("Passed\n");

System.out.println("CASE 4: Testing Custom Profile"
+ " Serialization & Deserialization");
prepareTestProfile("Serialize", false, ColorSpace.CS_sRGB);
testProfile(false, ColorSpace.CS_sRGB);
System.out.println("Passed\n");

System.out.println("CASE 5: Test reading Built-In profile from .icc file");
prepareTestProfile("ReadFromFile", true, ColorSpace.CS_sRGB);
testProfile(true, ColorSpace.CS_sRGB);
System.out.println("Passed\n");

System.out.println("CASE 6: Test reading Custom profile from .icc file");
prepareTestProfile("ReadFromFile", false, ColorSpace.CS_sRGB);
testProfile(false, ColorSpace.CS_sRGB);
System.out.println("Passed\n");
}

private static void prepareTestProfile(String testCase,
boolean isBuiltIn, int cs) {
ICC_Profile builtInProfile = ICC_Profile.getInstance(cs);
// if isBuiltIn=true use builtInProfile else create a copy
testProfile = isBuiltIn
? builtInProfile
: ICC_Profile.getInstance(builtInProfile.getData());

switch (testCase) {
case "Default" -> {
// empty case block
// no further processing of testProfile required for default case
}
case "Serialize" -> {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(testProfile);

byte[] array = baos.toByteArray();
try (ObjectInputStream ois =
new ObjectInputStream(new ByteArrayInputStream(array))) {
testProfile = (ICC_Profile) ois.readObject();
}
} catch (Exception e) {
throw new RuntimeException("Test Failed ! Serial-Deserialization"
+ " case failed", e);
}
}
case "ReadFromFile" -> {
// .icc files serialized on older JDK version
String filename = isBuiltIn ? "builtIn.icc" : "custom.icc";
String testDir = System.getProperty("test.src")
+ System.getProperty("file.separator");
filename = testDir + filename;

try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream ois = new ObjectInputStream(fileIn)) {
testProfile = (ICC_Profile) ois.readObject();
} catch (Exception e) {
throw new RuntimeException("Test Failed ! Unable to fetch"
+ " .icc files", e);
}
}
}
}

private static void testProfile(boolean isBuiltIn, int cs) {
byte[] headerData = testProfile.getData(HEADER_TAG);
// Set profile class to valid icSigInputClass = 0x73636E72
headerData[INDEX] = 0x73;
headerData[INDEX + 1] = 0x63;
headerData[INDEX + 2] = 0x6E;
headerData[INDEX + 3] = 0x72;

if (isBuiltIn) {
System.out.println("Testing: " + colorSpace.get(cs));
try {
// Try updating a built-in profile, IAE is expected
testProfile.setData(HEADER_TAG, headerData);
throw new RuntimeException("Test Failed! IAE NOT thrown for profile "
+ colorSpace.get(cs));
} catch (IllegalArgumentException iae) {
if (!iae.getMessage().equals(EXCEPTION_MSG)) {
throw new RuntimeException("Test Failed! IAE with exception msg \""
+ EXCEPTION_MSG + "\" NOT thrown for profile "
+ colorSpace.get(cs));
}
}
} else {
// Modifying custom profile should NOT throw IAE
testProfile.setData(HEADER_TAG, headerData);
}
}
}
Binary file not shown.
Binary file not shown.
Loading