/*
 * 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
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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.
 */

/* ********************************************************************
 **********************************************************************
 **********************************************************************
 *** COPYRIGHT (c) Eastman Kodak Company, 1997                      ***
 *** As  an unpublished  work pursuant to Title 17 of the United    ***
 *** States Code.  All rights reserved.                             ***
 **********************************************************************
 **********************************************************************
 **********************************************************************/

package java.awt.color;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.Serial;
import java.io.Serializable;
import java.util.Objects;
import java.util.StringTokenizer;

import sun.awt.AWTAccessor;
import sun.java2d.cmm.BuiltinProfileInfo;
import sun.java2d.cmm.CMSManager;
import sun.java2d.cmm.PCMM;
import sun.java2d.cmm.Profile;
import sun.java2d.cmm.ProfileDataVerifier;

import static sun.java2d.cmm.ProfileDataVerifier.HEADER_SIZE;

/**
 * A representation of color profile data for device independent and device
 * dependent color spaces based on the International Color Consortium
 * Specification ICC.1:2001-12, File Format for Color Profiles, (see
 * <a href="https://www.color.org"> https://www.color.org</a>).
 * <p>
 * An {@code ICC_ColorSpace} object can be constructed from an appropriate
 * {@code ICC_Profile}. Typically, an {@code ICC_ColorSpace} would be associated
 * with an ICC Profile which is either an input, display, or output profile (see
 * the ICC specification). There are also device link, abstract, color space
 * conversion, and named color profiles. These are less useful for tagging a
 * color or image, but are useful for other purposes (in particular device link
 * profiles can provide improved performance for converting from one device's
 * color space to another's).
 * <p>
 * ICC Profiles represent transformations from the color space of the profile
 * (e.g. a monitor) to a Profile Connection Space (PCS). Profiles of interest
 * for tagging images or colors have a PCS which is one of the two specific
 * device independent spaces (one CIEXYZ space and one CIELab space) defined in
 * the ICC Profile Format Specification. Most profiles of interest either have
 * invertible transformations or explicitly specify transformations going both
 * directions.
 *
 * @see ICC_ColorSpace
 */
public sealed class ICC_Profile implements Serializable
    permits ICC_ProfileGray,
            ICC_ProfileRGB {

    /**
     * Use serialVersionUID from JDK 1.2 for interoperability.
     */
    @Serial
    private static final long serialVersionUID = -3938515861990936766L;

    /**
     * The implementation specific CMM profile, {@code null} if this
     * {@code ICC_Profile} is not activated by the {@link #cmmProfile()} method.
     * This field must not be used directly and only via {@link #cmmProfile()}.
     */
    private transient volatile Profile cmmProfile;

    /**
     * Stores information about a built-in profile without triggering profile
     * loading. If this field is null, {@link #cmmProfile} should be used to
     * access profile data. If not null, the profile is considered immutable.
     */
    private transient final BuiltinProfileInfo builtInInfo;

    /**
     * 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 {
        /*
         * BuiltinProfileInfo 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 BuiltinProfileInfo(
               "sRGB.pf", ColorSpace.TYPE_RGB, 3, CLASS_DISPLAY));

        ICC_Profile LRGB = new ICC_ProfileRGB(new BuiltinProfileInfo(
               "LINEAR_RGB.pf", ColorSpace.TYPE_RGB, 3, CLASS_DISPLAY));

        ICC_Profile XYZ = new ICC_Profile(new BuiltinProfileInfo(
               "CIEXYZ.pf", ColorSpace.TYPE_XYZ, 3, CLASS_ABSTRACT));

        ICC_Profile PYCC = new ICC_Profile(new BuiltinProfileInfo(
               "PYCC.pf", ColorSpace.TYPE_3CLR, 3, CLASS_COLORSPACECONVERSION));

        ICC_Profile GRAY = new ICC_ProfileGray(new BuiltinProfileInfo(
               "GRAY.pf", ColorSpace.TYPE_GRAY, 1, CLASS_DISPLAY));
    }

    static {
        AWTAccessor.setICC_ProfileAccessor(ICC_Profile::cmmProfile);
    }

    /**
     * Profile class is input.
     */
    public static final int CLASS_INPUT = 0;

    /**
     * Profile class is display.
     */
    public static final int CLASS_DISPLAY = 1;

    /**
     * Profile class is output.
     */
    public static final int CLASS_OUTPUT = 2;

    /**
     * Profile class is device link.
     */
    public static final int CLASS_DEVICELINK = 3;

    /**
     * Profile class is color space conversion.
     */
    public static final int CLASS_COLORSPACECONVERSION = 4;

    /**
     * Profile class is abstract.
     */
    public static final int CLASS_ABSTRACT = 5;

    /**
     * Profile class is named color.
     */
    public static final int CLASS_NAMEDCOLOR = 6;


    /**
     * ICC Profile Color Space Type Signature: 'XYZ '.
     */
    public static final int icSigXYZData = 0x58595A20;

    /**
     * ICC Profile Color Space Type Signature: 'Lab '.
     */
    public static final int icSigLabData = 0x4C616220;

    /**
     * ICC Profile Color Space Type Signature: 'Luv '.
     */
    public static final int icSigLuvData = 0x4C757620;

    /**
     * ICC Profile Color Space Type Signature: 'YCbr'.
     */
    public static final int icSigYCbCrData = 0x59436272;

    /**
     * ICC Profile Color Space Type Signature: 'Yxy '.
     */
    public static final int icSigYxyData = 0x59787920;

    /**
     * ICC Profile Color Space Type Signature: 'RGB '.
     */
    public static final int icSigRgbData = 0x52474220;

    /**
     * ICC Profile Color Space Type Signature: 'GRAY'.
     */
    public static final int icSigGrayData = 0x47524159;

    /**
     * ICC Profile Color Space Type Signature: 'HSV'.
     */
    public static final int icSigHsvData = 0x48535620;

    /**
     * ICC Profile Color Space Type Signature: 'HLS'.
     */
    public static final int icSigHlsData = 0x484C5320;

    /**
     * ICC Profile Color Space Type Signature: 'CMYK'.
     */
    public static final int icSigCmykData = 0x434D594B;

    /**
     * ICC Profile Color Space Type Signature: 'CMY '.
     */
    public static final int icSigCmyData = 0x434D5920;

    /**
     * ICC Profile Color Space Type Signature: '2CLR'.
     */
    public static final int icSigSpace2CLR = 0x32434C52;

    /**
     * ICC Profile Color Space Type Signature: '3CLR'.
     */
    public static final int icSigSpace3CLR = 0x33434C52;

    /**
     * ICC Profile Color Space Type Signature: '4CLR'.
     */
    public static final int icSigSpace4CLR = 0x34434C52;

    /**
     * ICC Profile Color Space Type Signature: '5CLR'.
     */
    public static final int icSigSpace5CLR = 0x35434C52;

    /**
     * ICC Profile Color Space Type Signature: '6CLR'.
     */
    public static final int icSigSpace6CLR = 0x36434C52;

    /**
     * ICC Profile Color Space Type Signature: '7CLR'.
     */
    public static final int icSigSpace7CLR = 0x37434C52;

    /**
     * ICC Profile Color Space Type Signature: '8CLR'.
     */
    public static final int icSigSpace8CLR = 0x38434C52;

    /**
     * ICC Profile Color Space Type Signature: '9CLR'.
     */
    public static final int icSigSpace9CLR = 0x39434C52;

    /**
     * ICC Profile Color Space Type Signature: 'ACLR'.
     */
    public static final int icSigSpaceACLR = 0x41434C52;

    /**
     * ICC Profile Color Space Type Signature: 'BCLR'.
     */
    public static final int icSigSpaceBCLR = 0x42434C52;

    /**
     * ICC Profile Color Space Type Signature: 'CCLR'.
     */
    public static final int icSigSpaceCCLR = 0x43434C52;

    /**
     * ICC Profile Color Space Type Signature: 'DCLR'.
     */
    public static final int icSigSpaceDCLR = 0x44434C52;

    /**
     * ICC Profile Color Space Type Signature: 'ECLR'.
     */
    public static final int icSigSpaceECLR = 0x45434C52;

    /**
     * ICC Profile Color Space Type Signature: 'FCLR'.
     */
    public static final int icSigSpaceFCLR = 0x46434C52;


    /**
     * ICC Profile Class Signature: 'scnr'.
     */
    public static final int icSigInputClass = 0x73636E72;

    /**
     * ICC Profile Class Signature: 'mntr'.
     */
    public static final int icSigDisplayClass = 0x6D6E7472;

    /**
     * ICC Profile Class Signature: 'prtr'.
     */
    public static final int icSigOutputClass = 0x70727472;

    /**
     * ICC Profile Class Signature: 'link'.
     */
    public static final int icSigLinkClass = 0x6C696E6B;

    /**
     * ICC Profile Class Signature: 'abst'.
     */
    public static final int icSigAbstractClass = 0x61627374;

    /**
     * ICC Profile Class Signature: 'spac'.
     */
    public static final int icSigColorSpaceClass = 0x73706163;

    /**
     * ICC Profile Class Signature: 'nmcl'.
     */
    public static final int icSigNamedColorClass = 0x6e6d636c;


    /**
     * ICC Profile Rendering Intent: Perceptual.
     */
    public static final int icPerceptual = 0;

    /**
     * ICC Profile Rendering Intent: RelativeColorimetric.
     */
    public static final int icRelativeColorimetric = 1;

    /**
     * ICC Profile Rendering Intent: Media-RelativeColorimetric.
     *
     * @since 1.5
     */
    public static final int icMediaRelativeColorimetric = 1;

    /**
     * ICC Profile Rendering Intent: Saturation.
     */
    public static final int icSaturation = 2;

    /**
     * ICC Profile Rendering Intent: AbsoluteColorimetric.
     */
    public static final int icAbsoluteColorimetric = 3;

    /**
     * ICC Profile Rendering Intent: ICC-AbsoluteColorimetric.
     *
     * @since 1.5
     */
    public static final int icICCAbsoluteColorimetric = 3;


    /**
     * ICC Profile Tag Signature: 'head' - special.
     */
    public static final int icSigHead = 0x68656164;

    /**
     * ICC Profile Tag Signature: 'A2B0'.
     */
    public static final int icSigAToB0Tag = 0x41324230;

    /**
     * ICC Profile Tag Signature: 'A2B1'.
     */
    public static final int icSigAToB1Tag = 0x41324231;

    /**
     * ICC Profile Tag Signature: 'A2B2'.
     */
    public static final int icSigAToB2Tag = 0x41324232;

    /**
     * ICC Profile Tag Signature: 'bXYZ'.
     */
    public static final int icSigBlueColorantTag = 0x6258595A;

    /**
     * ICC Profile Tag Signature: 'bXYZ'.
     *
     * @since 1.5
     */
    public static final int icSigBlueMatrixColumnTag = 0x6258595A;

    /**
     * ICC Profile Tag Signature: 'bTRC'.
     */
    public static final int icSigBlueTRCTag = 0x62545243;

    /**
     * ICC Profile Tag Signature: 'B2A0'.
     */
    public static final int icSigBToA0Tag = 0x42324130;

    /**
     * ICC Profile Tag Signature: 'B2A1'.
     */
    public static final int icSigBToA1Tag = 0x42324131;

    /**
     * ICC Profile Tag Signature: 'B2A2'.
     */
    public static final int icSigBToA2Tag = 0x42324132;

    /**
     * ICC Profile Tag Signature: 'calt'.
     */
    public static final int icSigCalibrationDateTimeTag = 0x63616C74;

    /**
     * ICC Profile Tag Signature: 'targ'.
     */
    public static final int icSigCharTargetTag = 0x74617267;

    /**
     * ICC Profile Tag Signature: 'cprt'.
     */
    public static final int icSigCopyrightTag = 0x63707274;

    /**
     * ICC Profile Tag Signature: 'crdi'.
     */
    public static final int icSigCrdInfoTag = 0x63726469;

    /**
     * ICC Profile Tag Signature: 'dmnd'.
     */
    public static final int icSigDeviceMfgDescTag = 0x646D6E64;

    /**
     * ICC Profile Tag Signature: 'dmdd'.
     */
    public static final int icSigDeviceModelDescTag = 0x646D6464;

    /**
     * ICC Profile Tag Signature: 'devs'.
     */
    public static final int icSigDeviceSettingsTag = 0x64657673;

    /**
     * ICC Profile Tag Signature: 'gamt'.
     */
    public static final int icSigGamutTag = 0x67616D74;

    /**
     * ICC Profile Tag Signature: 'kTRC'.
     */
    public static final int icSigGrayTRCTag = 0x6b545243;

    /**
     * ICC Profile Tag Signature: 'gXYZ'.
     */
    public static final int icSigGreenColorantTag = 0x6758595A;

    /**
     * ICC Profile Tag Signature: 'gXYZ'.
     *
     * @since 1.5
     */
    public static final int icSigGreenMatrixColumnTag = 0x6758595A;

    /**
     * ICC Profile Tag Signature: 'gTRC'.
     */
    public static final int icSigGreenTRCTag = 0x67545243;

    /**
     * ICC Profile Tag Signature: 'lumi'.
     */
    public static final int icSigLuminanceTag = 0x6C756d69;

    /**
     * ICC Profile Tag Signature: 'meas'.
     */
    public static final int icSigMeasurementTag = 0x6D656173;

    /**
     * ICC Profile Tag Signature: 'bkpt'.
     */
    public static final int icSigMediaBlackPointTag = 0x626B7074;

    /**
     * ICC Profile Tag Signature: 'wtpt'.
     */
    public static final int icSigMediaWhitePointTag = 0x77747074;

    /**
     * ICC Profile Tag Signature: 'ncl2'.
     */
    public static final int icSigNamedColor2Tag = 0x6E636C32;

    /**
     * ICC Profile Tag Signature: 'resp'.
     */
    public static final int icSigOutputResponseTag = 0x72657370;

    /**
     * ICC Profile Tag Signature: 'pre0'.
     */
    public static final int icSigPreview0Tag = 0x70726530;

    /**
     * ICC Profile Tag Signature: 'pre1'.
     */
    public static final int icSigPreview1Tag = 0x70726531;

    /**
     * ICC Profile Tag Signature: 'pre2'.
     */
    public static final int icSigPreview2Tag = 0x70726532;

    /**
     * ICC Profile Tag Signature: 'desc'.
     */
    public static final int icSigProfileDescriptionTag = 0x64657363;

    /**
     * ICC Profile Tag Signature: 'pseq'.
     */
    public static final int icSigProfileSequenceDescTag = 0x70736571;

    /**
     * ICC Profile Tag Signature: 'psd0'.
     */
    public static final int icSigPs2CRD0Tag = 0x70736430;

    /**
     * ICC Profile Tag Signature: 'psd1'.
     */
    public static final int icSigPs2CRD1Tag = 0x70736431;

    /**
     * ICC Profile Tag Signature: 'psd2'.
     */
    public static final int icSigPs2CRD2Tag = 0x70736432;

    /**
     * ICC Profile Tag Signature: 'psd3'.
     */
    public static final int icSigPs2CRD3Tag = 0x70736433;

    /**
     * ICC Profile Tag Signature: 'ps2s'.
     */
    public static final int icSigPs2CSATag = 0x70733273;

    /**
     * ICC Profile Tag Signature: 'ps2i'.
     */
    public static final int icSigPs2RenderingIntentTag = 0x70733269;

    /**
     * ICC Profile Tag Signature: 'rXYZ'.
     */
    public static final int icSigRedColorantTag = 0x7258595A;

    /**
     * ICC Profile Tag Signature: 'rXYZ'.
     *
     * @since 1.5
     */
    public static final int icSigRedMatrixColumnTag = 0x7258595A;

    /**
     * ICC Profile Tag Signature: 'rTRC'.
     */
    public static final int icSigRedTRCTag = 0x72545243;

    /**
     * ICC Profile Tag Signature: 'scrd'.
     */
    public static final int icSigScreeningDescTag = 0x73637264;

    /**
     * ICC Profile Tag Signature: 'scrn'.
     */
    public static final int icSigScreeningTag = 0x7363726E;

    /**
     * ICC Profile Tag Signature: 'tech'.
     */
    public static final int icSigTechnologyTag = 0x74656368;

    /**
     * ICC Profile Tag Signature: 'bfd '.
     */
    public static final int icSigUcrBgTag = 0x62666420;

    /**
     * ICC Profile Tag Signature: 'vued'.
     */
    public static final int icSigViewingCondDescTag = 0x76756564;

    /**
     * ICC Profile Tag Signature: 'view'.
     */
    public static final int icSigViewingConditionsTag = 0x76696577;

    /**
     * ICC Profile Tag Signature: 'chrm'.
     */
    public static final int icSigChromaticityTag = 0x6368726d;

    /**
     * ICC Profile Tag Signature: 'chad'.
     *
     * @since 1.5
     */
    public static final int icSigChromaticAdaptationTag = 0x63686164;

    /**
     * ICC Profile Tag Signature: 'clro'.
     *
     * @since 1.5
     */
    public static final int icSigColorantOrderTag = 0x636C726F;

    /**
     * ICC Profile Tag Signature: 'clrt'.
     *
     * @since 1.5
     */
    public static final int icSigColorantTableTag = 0x636C7274;


    /**
     * ICC Profile Header Location: profile size in bytes.
     */
    public static final int icHdrSize = 0;

    /**
     * ICC Profile Header Location: CMM for this profile.
     */
    public static final int icHdrCmmId = 4;

    /**
     * ICC Profile Header Location: format version number.
     */
    public static final int icHdrVersion = 8;

    /**
     * ICC Profile Header Location: type of profile.
     */
    public static final int icHdrDeviceClass = 12;

    /**
     * ICC Profile Header Location: color space of data.
     */
    public static final int icHdrColorSpace = 16;

    /**
     * ICC Profile Header Location: PCS - XYZ or Lab only.
     */
    public static final int icHdrPcs = 20;

    /**
     * ICC Profile Header Location: date profile was created.
     */
    public static final int icHdrDate = 24;

    /**
     * ICC Profile Header Location: icMagicNumber.
     */
    public static final int icHdrMagic = 36;

    /**
     * ICC Profile Header Location: primary platform.
     */
    public static final int icHdrPlatform = 40;

    /**
     * ICC Profile Header Location: various bit settings.
     */
    public static final int icHdrFlags = 44;

    /**
     * ICC Profile Header Location: device manufacturer.
     */
    public static final int icHdrManufacturer = 48;

    /**
     * ICC Profile Header Location: device model number.
     */
    public static final int icHdrModel = 52;

    /**
     * ICC Profile Header Location: device attributes.
     */
    public static final int icHdrAttributes = 56;

    /**
     * ICC Profile Header Location: rendering intent.
     */
    public static final int icHdrRenderingIntent = 64;

    /**
     * ICC Profile Header Location: profile illuminant.
     */
    public static final int icHdrIlluminant = 68;

    /**
     * ICC Profile Header Location: profile creator.
     */
    public static final int icHdrCreator = 80;

    /**
     * ICC Profile Header Location: profile's ID.
     *
     * @since 1.5
     */
    public static final int icHdrProfileID = 84;


    /**
     * ICC Profile Constant: tag type signature.
     */
    public static final int icTagType = 0;

    /**
     * ICC Profile Constant: reserved.
     */
    public static final int icTagReserved = 4;

    /**
     * ICC Profile Constant: curveType count.
     */
    public static final int icCurveCount = 8;

    /**
     * ICC Profile Constant: curveType data.
     */
    public static final int icCurveData = 12;

    /**
     * ICC Profile Constant: XYZNumber X.
     */
    public static final int icXYZNumberX = 8;

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

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

    /**
     * Constructs an {@code ICC_Profile} object corresponding to the data in a
     * byte array.
     *
     * @param  data the specified ICC Profile data
     * @return an {@code ICC_Profile} object corresponding to the data in the
     *         specified {@code data} array
     * @throws IllegalArgumentException If the byte array does not contain valid
     *         ICC Profile data
     */
    public static ICC_Profile getInstance(byte[] data) {
        ProfileDataVerifier.verify(data);
        verifyHeader(data);
        Profile p;
        try {
            p = CMSManager.getModule().loadProfile(data);
        } catch (CMMException c) {
            throw new IllegalArgumentException("Invalid ICC Profile Data");
        }

        try {
            int type = getColorSpaceType(data);
            if (type == ColorSpace.TYPE_GRAY) {
                return new ICC_ProfileGray(p);
            } else if (type == ColorSpace.TYPE_RGB) {
                return new ICC_ProfileRGB(p);
            }
        } catch (CMMException c) {
            // will try to create a general icc profile
        }
        return new ICC_Profile(p);
    }

    /**
     * Constructs an {@code ICC_Profile} corresponding to one of the specific
     * color spaces defined by the {@code ColorSpace} class (for example
     * {@code CS_sRGB}).
     *
     * @param  cspace the type of color space to create a profile for. The
     *         specified type is one of the color space constants defined in the
     *         {@code ColorSpace} class.
     * @return an {@code ICC_Profile} object corresponding to the specified
     *         {@code ColorSpace} type
     * @throws IllegalArgumentException If {@code cspace} is not one of the
     *         predefined color space types
     */
    public static ICC_Profile getInstance(int cspace) {
        return switch (cspace) {
            case ColorSpace.CS_sRGB -> BuiltInProfile.SRGB;
            case ColorSpace.CS_LINEAR_RGB -> BuiltInProfile.LRGB;
            case ColorSpace.CS_CIEXYZ -> BuiltInProfile.XYZ;
            case ColorSpace.CS_PYCC -> BuiltInProfile.PYCC;
            case ColorSpace.CS_GRAY -> BuiltInProfile.GRAY;
            default -> {
                throw new IllegalArgumentException("Unknown color space");
            }
        };
    }

    /**
     * Constructs an {@code ICC_Profile} corresponding to the data in a file.
     * {@code fileName} may be an absolute or a relative file specification.
     * Relative file names are looked for in several places: first, relative to
     * any directories specified by the {@code java.iccprofile.path} property;
     * second, relative to any directories specified by the
     * {@code java.class.path} property; finally, in a directory used to store
     * profiles always available, such as the profile for sRGB. Built-in
     * profiles use {@code .pf} as the file name extension for profiles, e.g.
     * {@code sRGB.pf}.
     *
     * @param  fileName the file that contains the data for the profile
     * @return an {@code ICC_Profile} object corresponding to the data in the
     *         specified file
     * @throws IOException If the specified file cannot be opened or an I/O
     *         error occurs while reading the file
     * @throws IllegalArgumentException If the file does not contain valid ICC
     *         Profile data
     * @throws NullPointerException if {@code fileName} is {@code null}
     */
    public static ICC_Profile getInstance(String fileName) throws IOException {
        InputStream is;
        File f = getProfileFile(fileName);
        if (f != null) {
            is = new FileInputStream(f);
        } else {
            is = getStandardProfileInputStream(fileName);
        }
        if (is == null) {
            throw new IOException("Cannot open file " + fileName);
        }
        try (is) {
            return getInstance(is);
        }
    }

    /**
     * Constructs an {@code ICC_Profile} corresponding to the data in an
     * {@code InputStream}.
     *
     * @param  s the input stream from which to read the profile data
     * @return an {@code ICC_Profile} object corresponding to the data in the
     *         specified {@code InputStream}
     * @throws IOException If an I/O error occurs while reading the stream
     * @throws IllegalArgumentException If the stream does not contain valid ICC
     *         Profile data
     * @throws NullPointerException if {@code s} is {@code null}
     */
    public static ICC_Profile getInstance(InputStream s) throws IOException {
        Objects.requireNonNull(s);
        return getInstance(getProfileDataFromStream(s));
    }

    static byte[] getProfileDataFromStream(InputStream s) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(s);
        bis.mark(HEADER_SIZE);

        byte[] header = bis.readNBytes(HEADER_SIZE);
        if (header.length < HEADER_SIZE || header[36] != 0x61 ||
            header[37] != 0x63 || header[38] != 0x73 || header[39] != 0x70) {
            return null;   /* not a valid profile */
        }
        int profileSize = intFromBigEndian(header, 0);
        bis.reset();
        try {
            return bis.readNBytes(profileSize);
        } catch (OutOfMemoryError e) {
            throw new IOException("Color profile is too big");
        }
    }

    /**
     * Activates and returns the deferred standard profiles. Implementation of
     * this method mimics the old behaviour when the {@code CMMException} and
     * {@code IOException} were wrapped by the {@code ProfileDataException}, and
     * the {@code ProfileDataException} itself was ignored during activation.
     *
     * @return the implementation specific CMM profile, or {@code null}
     */
    private Profile cmmProfile() {
        Profile p = cmmProfile;
        if (p != null) {
            return p; // one volatile read on common path
        }
        synchronized (this) {
            if (cmmProfile != null) {
                return cmmProfile;
            }
            var is = getStandardProfileInputStream(builtInInfo.filename);
            if (is == null) {
                return null;
            }
            try (is) {
                byte[] data = getProfileDataFromStream(is);
                if (data != null) {
                    p = cmmProfile = CMSManager.getModule().loadProfile(data);
                }
            } catch (CMMException | IOException ignore) {
            }
        }
        return p;
    }

    /**
     * Returns profile major version.
     *
     * @return the major version of the profile
     */
    public int getMajorVersion() {
        return getData(cmmProfile(), icSigHead)[8];
    }

    /**
     * Returns profile minor version.
     *
     * @return the minor version of the profile
     */
    public int getMinorVersion() {
        return getData(cmmProfile(), icSigHead)[9];
    }

    /**
     * Returns the profile class.
     *
     * @return one of the predefined profile class constants
     */
    public int getProfileClass() {
        if (builtInInfo != null) {
            return builtInInfo.profileClass;
        }
        byte[] theHeader = getData(cmmProfile(), icSigHead);
        return getProfileClass(theHeader);
    }

    private static int getProfileClass(byte[] data) {
        int theClassSig = intFromBigEndian(data, icHdrDeviceClass);
        return switch (theClassSig) {
            case icSigInputClass -> CLASS_INPUT;
            case icSigDisplayClass -> CLASS_DISPLAY;
            case icSigOutputClass -> CLASS_OUTPUT;
            case icSigLinkClass -> CLASS_DEVICELINK;
            case icSigColorSpaceClass -> CLASS_COLORSPACECONVERSION;
            case icSigAbstractClass -> CLASS_ABSTRACT;
            case icSigNamedColorClass -> CLASS_NAMEDCOLOR;
            default -> {
                throw new IllegalArgumentException("Unknown profile class");
            }
        };
    }

    /**
     * Returns the color space type. Returns one of the color space type
     * constants defined by the {@code ColorSpace} class. This is the "input"
     * color space of the profile. The type defines the number of components of
     * the color space and the interpretation, e.g. {@code TYPE_RGB} identifies
     * a color space with three components - red, green, and blue. It does not
     * define the particular color characteristics of the space, e.g. the
     * chromaticities of the primaries.
     *
     * @return one of the color space type constants defined in the
     *         {@code ColorSpace} class
     */
    public int getColorSpaceType() {
        if (builtInInfo != null) {
            return builtInInfo.colorSpaceType;
        }
        byte[] theHeader = getData(cmmProfile(), icSigHead);
        return getColorSpaceType(theHeader);
    }

    private static int getColorSpaceType(byte[] data) {
        int theColorSpaceSig = intFromBigEndian(data, 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
     * class. This is the "output" color space of the profile. For an input,
     * display, or output profile useful for tagging colors or images, this will
     * be either {@code TYPE_XYZ} or {@code TYPE_Lab} and should be interpreted
     * as the corresponding specific color space defined in the ICC
     * specification. For a device link profile, this could be any of the color
     * space type constants.
     *
     * @return one of the color space type constants defined in the
     *         {@code ColorSpace} class
     */
    public int getPCSType() {
        byte[] theHeader = getData(cmmProfile(), icSigHead);
        return getPCSType(theHeader);
    }

    private static int getPCSType(byte[] data) {
        int thePCSSig = intFromBigEndian(data, icHdrPcs);
        int theDeviceClass = intFromBigEndian(data, 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.
     *
     * @param  fileName the file to write the profile data to
     * @throws IOException If the file cannot be opened for writing or an I/O
     *         error occurs while writing to the file
     * @throws NullPointerException if {@code fileName} is {@code null}
     */
    public void write(String fileName) throws IOException {
        try (OutputStream out = new FileOutputStream(fileName)) {
            write(out);
        }
    }

    /**
     * Write this {@code ICC_Profile} to an {@code OutputStream}.
     *
     * @param  s the stream to write the profile data to
     * @throws IOException If an I/O error occurs while writing to the stream
     * @throws NullPointerException if {@code s} is {@code null}
     */
    public void write(OutputStream s) throws IOException {
        s.write(getData());
    }

    /**
     * Returns a byte array corresponding to the data of this
     * {@code ICC_Profile}.
     *
     * @return a byte array that contains the profile data
     * @see #setData(int, byte[])
     */
    public byte[] getData() {
        return CMSManager.getModule().getProfileData(cmmProfile());
    }

    /**
     * Returns a particular tagged data element from the profile as a byte
     * array. Elements are identified by signatures as defined in the ICC
     * specification. The signature icSigHead can be used to get the header.
     * This method is useful for advanced applications which need to access
     * profile data directly.
     *
     * @param  tagSignature the ICC tag signature for the data element you want
     *         to get
     * @return a byte array that contains the tagged data element. Returns
     *         {@code null} if the specified tag doesn't exist.
     * @see #setData(int, byte[])
     */
    public byte[] getData(int tagSignature) {
        try {
            return getData(cmmProfile(), tagSignature).clone();
        } catch (CMMException c) {
            return null;
        }
    }

    /**
     * Returns a particular tagged data element from the profile as a non-null
     * byte array. The returned byte array is not cloned. It must not be exposed
     * to or used by public APIs. It is intended strictly for internal use only.
     *
     * @param  p the CMM profile from which to retrieve the tag data
     * @param  tagSignature the ICC tag signature for the data to retrieve
     * @return a non-null byte array containing the tag data
     * @throws CMMException if the specified tag doesn't exist
     */
    static byte[] getData(Profile p, int tagSignature) {
        return CMSManager.getModule().getTagData(p, tagSignature);
    }

    /**
     * Sets a particular tagged data element in the profile from a byte array.
     * The array should contain data in a format, corresponded to the
     * {@code tagSignature} as defined in the ICC specification, section 10.
     * 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 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 (builtInInfo != null) {
            throw new IllegalArgumentException("Can't modify built-in profile");
        }

        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 void checkRenderingIntent(byte[] data) {
        int index = ICC_Profile.icHdrRenderingIntent;
        /*
         * ICC spec: only the least-significant 16 bits encode the rendering
         * intent. The most significant 16 bits must be zero and can be ignored.
         * https://www.color.org/specification/ICC.1-2022-05.pdf, section 7.2.15
         */
        // Extract 16-bit unsigned rendering intent (0–65535)
        int intent = (data[index + 2] & 0xff) << 8 | data[index + 3] & 0xff;
        // Only check upper bound since intent can't be negative
        if (intent > icICCAbsoluteColorimetric) {
            throw new IllegalArgumentException(
                    "Unknown Rendering Intent: %d".formatted(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
     * {@code TYPE_RGB}, then this method will return 3.
     *
     * @return the number of color components in the profile's input color space
     * @throws ProfileDataException if color space is in the profile is invalid
     */
    public int getNumComponents() {
        if (builtInInfo != null) {
            return builtInInfo.numComponents;
        }
        byte[] theHeader = getData(cmmProfile(), icSigHead);
        int theColorSpaceSig = intFromBigEndian(theHeader, icHdrColorSpace);
        return switch (theColorSpaceSig) {
            case icSigGrayData -> 1;
            case icSigSpace2CLR -> 2;
            case icSigXYZData, icSigLabData, icSigLuvData, icSigYCbCrData,
                    icSigYxyData, icSigRgbData, icSigHsvData, icSigHlsData,
                    icSigCmyData, icSigSpace3CLR -> 3;
            case icSigCmykData, icSigSpace4CLR -> 4;
            case icSigSpace5CLR -> 5;
            case icSigSpace6CLR -> 6;
            case icSigSpace7CLR -> 7;
            case icSigSpace8CLR -> 8;
            case icSigSpace9CLR -> 9;
            case icSigSpaceACLR -> 10;
            case icSigSpaceBCLR -> 11;
            case icSigSpaceCCLR -> 12;
            case icSigSpaceDCLR -> 13;
            case icSigSpaceECLR -> 14;
            case icSigSpaceFCLR -> 15;
            default -> {
                throw new ProfileDataException("invalid ICC color space");
            }
        };
    }

    /**
     * Returns a float array of length 3 containing the X, Y, and Z components
     * of the mediaWhitePointTag in the ICC profile.
     */
    float[] getMediaWhitePoint() {
        return getXYZTag(icSigMediaWhitePointTag);
    }

    /**
     * Returns a float array of length 3 containing the X, Y, and Z components
     * encoded in an XYZType tag.
     */
    final float[] getXYZTag(int tagSignature) {
        byte[] theData = getData(cmmProfile(), tagSignature);
        float[] theXYZNumber = new float[3]; /* array to return */

        /* convert s15Fixed16Number to float */
        for (int i1 = 0, i2 = icXYZNumberX; i1 < 3; i1++, i2 += 4) {
            int theS15Fixed16 = intFromBigEndian(theData, i2);
            theXYZNumber[i1] = ((float) theS15Fixed16) / 65536.0f;
        }
        return theXYZNumber;
    }

    /**
     * Returns a gamma value representing a tone reproduction curve (TRC). If
     * the profile represents the TRC as a table rather than a single gamma
     * value, then an exception is thrown. In this case the actual table can be
     * obtained via {@link #getTRC}. {@code tagSignature} should be one of
     * {@code icSigGrayTRCTag}, {@code icSigRedTRCTag},
     * {@code icSigGreenTRCTag}, or {@code icSigBlueTRCTag}.
     *
     * @return the gamma value as a float
     * @throws ProfileDataException if the profile does not specify the TRC as a
     *         single gamma value
     */
    float getGamma(int tagSignature) {
        byte[] theTRCData = getData(cmmProfile(), tagSignature);
        if (intFromBigEndian(theTRCData, icCurveCount) != 1) {
            throw new ProfileDataException("TRC is not a gamma");
        }

        /* convert u8Fixed8 to float */
        int theU8Fixed8 = shortFromBigEndian(theTRCData, icCurveData) & 0xffff;
        return theU8Fixed8 / 256.0f;
    }

    /**
     * Returns the TRC as an array of shorts. If the profile has specified the
     * TRC as linear (gamma = 1.0) or as a simple gamma value, this method
     * throws an exception, and the {@link #getGamma} method should be used to
     * get the gamma value. Otherwise the short array returned here represents a
     * lookup table where the input Gray value is conceptually in the range
     * [0.0, 1.0]. Value 0.0 maps to array index 0 and value 1.0 maps to array
     * index length-1. Interpolation may be used to generate output values for
     * input values which do not map exactly to an index in the array. Output
     * values also map linearly to the range [0.0, 1.0]. Value 0.0 is
     * represented by an array value of 0x0000 and value 1.0 by 0xFFFF, i.e. the
     * values are really unsigned short values, although they are returned in a
     * short array. {@code tagSignature} should be one of
     * {@code icSigGrayTRCTag}, {@code icSigRedTRCTag},
     * {@code icSigGreenTRCTag}, or {@code icSigBlueTRCTag}.
     *
     * @return a short array representing the TRC
     * @throws ProfileDataException if the profile does not specify the TRC as a
     *         table
     */
    short[] getTRC(int tagSignature) {
        byte[] theTRCData = getData(cmmProfile(), tagSignature);
        int nElements = intFromBigEndian(theTRCData, icCurveCount);
        if (nElements == 1) {
            throw new ProfileDataException("TRC is not a table");
        }

        short[] theTRC = new short[nElements]; /* array to return */
        for (int i1 = 0, i2 = icCurveData; i1 < nElements; i1++, i2 += 2) {
            theTRC[i1] = shortFromBigEndian(theTRCData, i2);
        }
        return theTRC;
    }

    /**
     * Convert an ICC color space signature into a Java color space type.
     */
    private static int iccCStoJCS(int theColorSpaceSig) {
        return switch (theColorSpaceSig) {
            case icSigXYZData -> ColorSpace.TYPE_XYZ;
            case icSigLabData -> ColorSpace.TYPE_Lab;
            case icSigLuvData -> ColorSpace.TYPE_Luv;
            case icSigYCbCrData -> ColorSpace.TYPE_YCbCr;
            case icSigYxyData -> ColorSpace.TYPE_Yxy;
            case icSigRgbData -> ColorSpace.TYPE_RGB;
            case icSigGrayData -> ColorSpace.TYPE_GRAY;
            case icSigHsvData -> ColorSpace.TYPE_HSV;
            case icSigHlsData -> ColorSpace.TYPE_HLS;
            case icSigCmykData -> ColorSpace.TYPE_CMYK;
            case icSigCmyData -> ColorSpace.TYPE_CMY;
            case icSigSpace2CLR -> ColorSpace.TYPE_2CLR;
            case icSigSpace3CLR -> ColorSpace.TYPE_3CLR;
            case icSigSpace4CLR -> ColorSpace.TYPE_4CLR;
            case icSigSpace5CLR -> ColorSpace.TYPE_5CLR;
            case icSigSpace6CLR -> ColorSpace.TYPE_6CLR;
            case icSigSpace7CLR -> ColorSpace.TYPE_7CLR;
            case icSigSpace8CLR -> ColorSpace.TYPE_8CLR;
            case icSigSpace9CLR -> ColorSpace.TYPE_9CLR;
            case icSigSpaceACLR -> ColorSpace.TYPE_ACLR;
            case icSigSpaceBCLR -> ColorSpace.TYPE_BCLR;
            case icSigSpaceCCLR -> ColorSpace.TYPE_CCLR;
            case icSigSpaceDCLR -> ColorSpace.TYPE_DCLR;
            case icSigSpaceECLR -> ColorSpace.TYPE_ECLR;
            case icSigSpaceFCLR -> ColorSpace.TYPE_FCLR;
            default -> {
                throw new IllegalArgumentException("Unknown color space");
            }
        };
    }

    private static int intFromBigEndian(byte[] array, int index) {
        return (((array[index]   & 0xff) << 24) |
                ((array[index+1] & 0xff) << 16) |
                ((array[index+2] & 0xff) <<  8) |
                 (array[index+3] & 0xff));
    }

    private static short shortFromBigEndian(byte[] array, int index) {
        return (short) (((array[index]   & 0xff) << 8) |
                         (array[index+1] & 0xff));
    }

    /**
     * {@code fileName} may be an absolute or a relative file specification.
     * Relative file names are looked for in several places: first, relative to
     * any directories specified by the {@code java.iccprofile.path} property;
     * second, relative to any directories specified by the
     * {@code java.class.path}. The built-in profile files are now loaded as
     * resources, since they may not be individual disk files, and so this
     * method will not find these and on a {@code null} return, the caller needs
     * to try as resources. Built-in profiles use {@code .pf} as the file name
     * extension for profiles, e.g. {@code sRGB.pf}.
     */
    private static File getProfileFile(String fileName) {
        File f = new File(fileName); /* try absolute file name */
        if (f.isAbsolute()) {
            /* Rest of code has little sense for an absolute pathname,
               so return here. */
            return f.isFile() ? f : null;
        }
        String path, dir, fullPath;
        if (!f.isFile() &&
                (path = System.getProperty("java.iccprofile.path")) != null) {
            /* try relative to java.iccprofile.path */
            StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
            while (st.hasMoreTokens() && ((f == null) || (!f.isFile()))) {
                dir = st.nextToken();
                fullPath = dir + File.separatorChar + fileName;
                f = new File(fullPath);
                if (!isChildOf(f, dir)) {
                    f = null;
                }
            }
        }

        if ((f == null || !f.isFile())
                && (path = System.getProperty("java.class.path")) != null) {
            /* try relative to java.class.path */
            StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
            while (st.hasMoreTokens() && ((f == null) || (!f.isFile()))) {
                dir = st.nextToken();
                fullPath = dir + File.separatorChar + fileName;
                f = new File(fullPath);
            }
        }

        if (f != null && !f.isFile()) {
            f = null;
        }
        return f;
    }

    /**
     * Returns a stream corresponding to a built-in profile specified by
     * fileName. If there is no built-in profile with such name, then the method
     * returns {@code null}.
     */
    private static InputStream getStandardProfileInputStream(String fileName) {
        return PCMM.class.getResourceAsStream("profiles/" + fileName);
    }

    /**
     * Checks whether given file resides inside give directory.
     */
    private static boolean isChildOf(File f, String dirName) {
        try {
            File dir = new File(dirName);
            String canonicalDirName = dir.getCanonicalPath();
            if (!canonicalDirName.endsWith(File.separator)) {
                canonicalDirName += File.separator;
            }
            String canonicalFileName = f.getCanonicalPath();
            return canonicalFileName.startsWith(canonicalDirName);
        } catch (IOException e) {
            /* we do not expect the IOException here, because invocation
             * of this function is always preceded by isFile() call.
             */
            return false;
        }
    }

    /*
     * Serialization support.
     *
     * Directly deserialized profiles are useless since they are not registered
     * with CMM. We don't allow constructor to be called directly and instead
     * have clients to call one of getInstance factory methods that will
     * register the profile with CMM. For deserialization we implement
     * readResolve method that will resolve the bogus deserialized profile
     * object with one obtained with getInstance as well.
     *
     * There are two primary factory methods for construction of ICC profiles:
     * getInstance(int cspace) and getInstance(byte[] data). This implementation
     * of ICC_Profile uses the former to return a cached singleton profile
     * object, other implementations will likely use this technique too. To
     * preserve the singleton pattern across serialization we serialize cached
     * singleton profiles in such a way that deserializing VM could call
     * getInstance(int cspace) method that will resolve deserialized object into
     * the corresponding singleton as well.
     *
     * Since the singletons are private to ICC_Profile the readResolve method
     * have to be `protected' instead of `private' so that singletons that are
     * instances of subclasses of ICC_Profile could be correctly deserialized.
     */

    /**
     * Version of the format of additional serialized data in the stream.
     * Version&nbsp;{@code 1} corresponds to Java&nbsp;2 Platform,&nbsp;v1.3.
     *
     * @serial
     * @since 1.3
     */
    private int iccProfileSerializedDataVersion = 1;

    /**
     * Writes default serializable fields to the stream. Writes a string and an
     * array of bytes to the stream as additional data.
     *
     * @param  s stream used for serialization
     * @throws IOException thrown by {@code ObjectInputStream}
     * @throws NullPointerException if {@code s} is {@code null}
     * @serialData the {@code String} is the name of one of
     *         <code>CS_<var>*</var></code> constants defined in the
     *         {@link ColorSpace} class if the profile object is a profile for a
     *         predefined color space (for example {@code "CS_sRGB"}). The
     *         string is {@code null} otherwise.
     *         <p>
     *         The {@code byte[]} array is the profile data for the profile. For
     *         predefined color spaces {@code null} is written instead of the
     *         profile data. If in the future versions of Java API new
     *         predefined color spaces will be added, future versions of this
     *         class may choose to write for new predefined color spaces not
     *         only the color space name, but the profile data as well so that
     *         older versions could still deserialize the object.
     */
    @Serial
    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();

        String csName = null;
        if (this == BuiltInProfile.SRGB) {
            csName = "CS_sRGB";
        } else if (this == BuiltInProfile.XYZ) {
            csName = "CS_CIEXYZ";
        } else if (this == BuiltInProfile.PYCC) {
            csName = "CS_PYCC";
        } else if (this == BuiltInProfile.GRAY) {
            csName = "CS_GRAY";
        } else if (this == BuiltInProfile.LRGB) {
            csName = "CS_LINEAR_RGB";
        }

        // Future versions may choose to write profile data for new
        // predefined color spaces as well, if any will be introduced,
        // so that old versions that don't recognize the new CS name
        // may fall back to constructing profile from the data.
        byte[] data = null;
        if (csName == null) {
            // getData will activate deferred profile if necessary
            data = getData();
        }

        s.writeObject(csName);
        s.writeObject(data);
    }

    // Temporary storage used by readObject to store resolved profile
    // (obtained with getInstance) for readResolve to return.
    private transient ICC_Profile resolvedDeserializedProfile;

    /**
     * Reads default serializable fields from the stream. Reads from the stream
     * a string and an array of bytes as additional data.
     *
     * @param  s stream used for deserialization
     * @throws IOException thrown by {@code ObjectInputStream}
     * @throws ClassNotFoundException thrown by {@code ObjectInputStream}
     * @throws NullPointerException if {@code s} is {@code null}
     * @serialData the {@code String} is the name of one of
     *         <code>CS_<var>*</var></code> constants defined in the
     *         {@link ColorSpace} class if the profile object is a profile for a
     *         predefined color space (for example {@code "CS_sRGB"}). The
     *         string is {@code null} otherwise.
     *         <p>
     *         The {@code byte[]} array is the profile data for the profile. It
     *         will usually be {@code null} for the predefined profiles.
     *         <p>
     *         If the string is recognized as a constant name for predefined
     *         color space the object will be resolved into profile obtained
     *         with
     *         <code>getInstance(int&nbsp;cspace)</code> and the profile data
     *         are ignored. Otherwise the object will be resolved into profile
     *         obtained with
     *         <code>getInstance(byte[]&nbsp;data)</code>.
     * @see #readResolve()
     * @see #getInstance(int)
     * @see #getInstance(byte[])
     */
    @Serial
    private void readObject(ObjectInputStream s)
            throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        try {
            String csName = (String) s.readObject();
            byte[] data = (byte[]) s.readObject();
            resolvedDeserializedProfile = switch (csName) {
                case "CS_sRGB" -> getInstance(ColorSpace.CS_sRGB);
                case "CS_CIEXYZ" -> getInstance(ColorSpace.CS_CIEXYZ);
                case "CS_PYCC" -> getInstance(ColorSpace.CS_PYCC);
                case "CS_GRAY" -> getInstance(ColorSpace.CS_GRAY);
                case "CS_LINEAR_RGB" -> getInstance(ColorSpace.CS_LINEAR_RGB);
                case null, default -> getInstance(data);
            };
        } catch (ClassCastException | IllegalArgumentException e) {
            throw new InvalidObjectException("Invalid ICC Profile Data", e);
        }
    }

    /**
     * Resolves instances being deserialized into instances registered with CMM.
     *
     * @return ICC_Profile object for profile registered with CMM
     * @throws ObjectStreamException never thrown, but mandated by the
     *         serialization spec
     * @since 1.3
     */
    @Serial
    protected Object readResolve() throws ObjectStreamException {
        return resolvedDeserializedProfile;
    }
}
