/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed 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.icepdf.core.pobjects.graphics; import org.icepdf.core.pobjects.Name; import org.icepdf.core.pobjects.Stream; import org.icepdf.core.util.Library; import org.icepdf.core.util.Utils; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.color.ICC_ColorSpace; import java.awt.color.ICC_Profile; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * put your documentation comment here */ public class ICCBased extends PColorSpace { private static final Logger logger = Logger.getLogger(ICCBased.class.toString()); public static final Name ICCBASED_KEY = new Name("ICCBased"); public static final Name N_KEY = new Name("N"); private int numcomp; private PColorSpace alternate; private Stream stream; private ColorSpace colorSpace; // basic cache to speed up the lookup, can't be static as we handle // 3 and 4 band colours. private ConcurrentHashMap<Integer, Color> iccColorCache3B; private ConcurrentHashMap<Integer, Color> iccColorCache4B; // setting up an ICC colour look up is expensive, so if we get a failure // we just fallback to the alternative space to safe cpu time. private boolean failed; public ICCBased(Library l, Stream h) { super(l, h.getEntries()); iccColorCache3B = new ConcurrentHashMap<Integer, Color>(); iccColorCache4B = new ConcurrentHashMap<Integer, Color>(); numcomp = h.getInt(N_KEY); switch (numcomp) { case 1: alternate = new DeviceGray(l, null); break; case 3: alternate = new DeviceRGB(l, null); break; case 4: alternate = new DeviceCMYK(l, null); break; } stream = h; } /** * */ public synchronized void init() { if (inited) { return; } byte[] in; try { stream.init(); in = stream.getDecodedStreamBytes(0); if (logger.isLoggable(Level.FINEST)) { String content = Utils.convertByteArrayToByteString(in); logger.finest("Content = " + content); } if (in != null) { ICC_Profile profile = ICC_Profile.getInstance(in); colorSpace = new ICC_ColorSpace(profile); } } catch (Exception e) { logger.log(Level.FINE, "Error Processing ICCBased Colour Profile", e); } inited = true; } /** * Get the alternative colour specified by the N dictionary entry. DeviceGray, * DeviceRGB, or DeviceCMYK, depending on whether the value of N is 1, 3 * or 4, respectively. * * @return PDF colour space represented by the N (number of components)key. */ public PColorSpace getAlternate() { return alternate; } private static int generateKey(float[] f) { int key = 0; if (f.length == 1) { key = ((int) (f[0] * 255) & 0xff); } else if (f.length == 2) { key = (((int) (f[0] * 255) & 0xff) << 8) | (((int) (f[1] * 255) & 0xff) & 0xff); } else if (f.length == 3) { key = (((int) (f[0] * 255) & 0xff) << 16) | (((int) (f[1] * 255) & 0xff) << 8) | (((int) (f[2] * 255) & 0xff) & 0xff); } else if (f.length == 4) { key = (((int) (f[0] * 255) & 0xff) << 24) | (((int) (f[1] * 255) & 0xff) << 16) | (((int) (f[2] * 255) & 0xff) << 8) | (((int) (f[3] * 255) & 0xff) & 0xff); } return key; } private static Color addColorToCache( ConcurrentHashMap<Integer, Color> iccColorCache, int key, ColorSpace colorSpace, float[] f) { Color color = iccColorCache.get(key); if (color != null) { return color; } else { color = new Color(calculateColor(f, colorSpace)); iccColorCache.put(key, color); return color; } } public Color getColor(float[] f, boolean fillAndStroke) { init(); if (colorSpace != null && !failed) { try { // generate a key for the colour int key = generateKey(f); if (f.length <= 3) { return addColorToCache(iccColorCache3B, key, colorSpace, f); } else { return addColorToCache(iccColorCache4B, key, colorSpace, f); } } catch (Exception e) { logger.log(Level.FINE, "Error getting ICCBased colour", e); failed = true; } } return alternate.getColor(f); } private static int calculateColor(float[] f, ColorSpace colorSpace) { int n = colorSpace.getNumComponents(); // Get the reverse of f, and only take n values // Might as well limit the bounds while we're at it float[] fvalue = new float[n]; int toCopy = n; int fLength = f.length; if (fLength < toCopy) { toCopy = fLength; } for (int i = 0; i < toCopy; i++) { int j = fLength - 1 - i; float curr = f[j]; if (curr < colorSpace.getMinValue(j)) { curr = 0.0f; } else if (curr > colorSpace.getMaxValue(j)) { curr = 1.0f; } fvalue[i] = curr; } float[] frgbvalue = colorSpace.toRGB(fvalue); return (0xFF000000) | ((((int) (frgbvalue[0] * 255)) & 0xFF) << 16) | ((((int) (frgbvalue[1] * 255)) & 0xFF) << 8) | ((((int) (frgbvalue[2] * 255)) & 0xFF)); } public ColorSpace getColorSpace() { return colorSpace; } /** * Gets the number of components specified by the N entry. * * @return number of colour components in color space */ public int getNumComponents() { return numcomp; } }