/*
 * 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.
 */

/* $Id: NamedColorProfileParser.java 1681108 2015-05-22 13:26:12Z ssteiner $ */

package org.apache.xmlgraphics.java2d.color.profile;

import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;

import org.apache.xmlgraphics.java2d.color.CIELabColorSpace;
import org.apache.xmlgraphics.java2d.color.ColorSpaces;
import org.apache.xmlgraphics.java2d.color.NamedColorSpace;
import org.apache.xmlgraphics.java2d.color.RenderingIntent;

This class is a parser for ICC named color profiles. It uses Java's ICC_Profile class for parsing the basic structure but adds functionality to parse certain profile tags.
/** * This class is a parser for ICC named color profiles. It uses Java's {@link ICC_Profile} class * for parsing the basic structure but adds functionality to parse certain profile tags. */
public class NamedColorProfileParser { private static final int MLUC = 0x6D6C7563; //'mluc' private static final int NCL2 = 0x6E636C32; //'ncl2'
Indicates whether the profile is a named color profile.
Params:
  • profile – the color profile
Returns:true if the profile is a named color profile, false otherwise
/** * Indicates whether the profile is a named color profile. * @param profile the color profile * @return true if the profile is a named color profile, false otherwise */
public static boolean isNamedColorProfile(ICC_Profile profile) { return profile.getProfileClass() == ICC_Profile.CLASS_NAMEDCOLOR; }
Parses a named color profile (NCP).
Params:
  • profile – the profile to analyze
  • profileName – Optional profile name associated with this color profile
  • profileURI – Optional profile URI associated with this color profile
Throws:
Returns:an object representing the parsed NCP
/** * Parses a named color profile (NCP). * @param profile the profile to analyze * @param profileName Optional profile name associated with this color profile * @param profileURI Optional profile URI associated with this color profile * @return an object representing the parsed NCP * @throws IOException if an I/O error occurs */
public NamedColorProfile parseProfile(ICC_Profile profile, String profileName, String profileURI) throws IOException { if (!isNamedColorProfile(profile)) { throw new IllegalArgumentException("Given profile is not a named color profile (NCP)"); } String profileDescription = getProfileDescription(profile); String copyright = getCopyright(profile); RenderingIntent intent = getRenderingIntent(profile); NamedColorSpace[] ncs = readNamedColors(profile, profileName, profileURI); return new NamedColorProfile(profileDescription, copyright, ncs, intent); }
Parses a named color profile (NCP).
Params:
  • profile – the profile to analyze
Throws:
Returns:an object representing the parsed NCP
/** * Parses a named color profile (NCP). * @param profile the profile to analyze * @return an object representing the parsed NCP * @throws IOException if an I/O error occurs */
public NamedColorProfile parseProfile(ICC_Profile profile) throws IOException { return parseProfile(profile, null, null); } private String getProfileDescription(ICC_Profile profile) throws IOException { byte[] tag = profile.getData(ICC_Profile.icSigProfileDescriptionTag); return readSimpleString(tag); } private String getCopyright(ICC_Profile profile) throws IOException { byte[] tag = profile.getData(ICC_Profile.icSigCopyrightTag); return readSimpleString(tag); } private RenderingIntent getRenderingIntent(ICC_Profile profile) throws IOException { byte[] hdr = profile.getData(ICC_Profile.icSigHead); int value = hdr[ICC_Profile.icHdrRenderingIntent]; return RenderingIntent.fromICCValue(value); } private NamedColorSpace[] readNamedColors(ICC_Profile profile, String profileName, String profileURI) throws IOException { byte[] tag = profile.getData(ICC_Profile.icSigNamedColor2Tag); DataInput din = new DataInputStream(new ByteArrayInputStream(tag)); int sig = din.readInt(); if (sig != NCL2) { throw new UnsupportedOperationException("Unsupported structure type: " + toSignatureString(sig) + ". Expected " + toSignatureString(NCL2)); } din.skipBytes(8); int numColors = din.readInt(); NamedColorSpace[] result = new NamedColorSpace[numColors]; int numDeviceCoord = din.readInt(); String prefix = readAscii(din, 32); String suffix = readAscii(din, 32); for (int i = 0; i < numColors; i++) { String name = prefix + readAscii(din, 32) + suffix; int[] pcs = readUInt16Array(din, 3); float[] colorvalue = new float[3]; for (int j = 0; j < pcs.length; j++) { colorvalue[j] = ((float)pcs[j]) / 0x8000; } //device coordinates are ignored for now /*int[] deviceCoord =*/ readUInt16Array(din, numDeviceCoord); switch (profile.getPCSType()) { case ColorSpace.TYPE_XYZ: result[i] = new NamedColorSpace(name, colorvalue, profileName, profileURI); break; case ColorSpace.TYPE_Lab: //Not sure if this always D50 here, //but the illuminant in the header is fixed to D50. CIELabColorSpace labCS = ColorSpaces.getCIELabColorSpaceD50(); result[i] = new NamedColorSpace(name, labCS.toColor(colorvalue, 1.0f), profileName, profileURI); break; default: throw new UnsupportedOperationException( "PCS type is not supported: " + profile.getPCSType()); } } return result; } private int[] readUInt16Array(DataInput din, int count) throws IOException { if (count == 0) { return new int[0]; } int[] result = new int[count]; for (int i = 0; i < count; i++) { int v = din.readUnsignedShort(); result[i] = v; } return result; } private String readAscii(DataInput in, int maxLength) throws IOException { byte[] data = new byte[maxLength]; in.readFully(data); String result = new String(data, "US-ASCII"); int idx = result.indexOf('\0'); if (idx >= 0) { result = result.substring(0, idx); } return result; } private String readSimpleString(byte[] tag) throws IOException { DataInput din = new DataInputStream(new ByteArrayInputStream(tag)); int sig = din.readInt(); if (sig == MLUC) { return readMLUC(din); } else { return null; //Unsupported tag structure type } } private String readMLUC(DataInput din) throws IOException { din.skipBytes(16); int firstLength = din.readInt(); int firstOffset = din.readInt(); int offset = 28; din.skipBytes(firstOffset - offset); byte[] utf16 = new byte[firstLength]; din.readFully(utf16); return new String(utf16, "UTF-16BE"); } private String toSignatureString(int sig) { StringBuffer sb = new StringBuffer(); sb.append((char)(sig >> 24 & 0xFF)); sb.append((char)(sig >> 16 & 0xFF)); sb.append((char)(sig >> 8 & 0xFF)); sb.append((char)(sig & 0xFF)); return sb.toString(); } }