pdfbox-trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDCIEDictionaryBasedColorSpace.java
/*
 * Copyright 2014 The Apache Software Foundation.
 *
 * 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.apache.pdfbox.pdmodel.graphics.color;

import java.awt.color.ColorSpace;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSFloat;
import org.apache.pdfbox.cos.COSName;

/**
 * CIE-based colour spaces that use a dictionary.
 *
 * @author Ben Litchfield
 * @author John Hewson
 */
public abstract class PDCIEDictionaryBasedColorSpace extends PDCIEBasedColorSpace
{
    protected final COSDictionary dictionary;

    private static final ColorSpace CIEXYZ = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);

    // we need to cache whitepoint values, because using getWhitePoint()
    // would create a new default object for each pixel conversion if the original
    // PDF didn't have a whitepoint array
    protected float wpX = 1;
    protected float wpY = 1;
    protected float wpZ = 1;

    protected PDCIEDictionaryBasedColorSpace(COSName cosName)
    {
        array = new COSArray();
        dictionary = new COSDictionary();
        array.add(cosName);
        array.add(dictionary);

        fillWhitepointCache(getWhitepoint());
    }

    /**
     * Creates a new CalRGB color space using the given COS array.
     *
     * @param rgb the cos array which represents this color space
     */
    protected PDCIEDictionaryBasedColorSpace(COSArray rgb)
    {
        array = rgb;
        dictionary = (COSDictionary) array.getObject(1);

        fillWhitepointCache(getWhitepoint());
    }

    /**
     * Tests if the current point is the white point.
     *
     * @return true if the current point is the white point.
     */
    protected boolean isWhitePoint()
    {
        return  Float.compare(wpX, 1) == 0 &&
                Float.compare(wpY, 1) == 0 && 
                Float.compare(wpZ, 1)  == 0;
    }

    private void fillWhitepointCache(PDTristimulus whitepoint)
    {
        wpX = whitepoint.getX();
        wpY = whitepoint.getY();
        wpZ = whitepoint.getZ();
    }

    protected float[] convXYZtoRGB(float x, float y, float z)
    {
        // toRGB() malfunctions with negative values
        // XYZ must be non-negative anyway:
        // http://ninedegreesbelow.com/photography/icc-profile-negative-tristimulus.html
        if (x < 0)
        {
            x = 0;
        }
        if (y < 0)
        {
            y = 0;
        }
        if (z < 0)
        {
            z = 0;
        }
        return CIEXYZ.toRGB(new float[]
        {
            x, y, z
        });
    }

    /**
     * This will return the whitepoint tristimulus. As this is a required field
     * this will never return null. A default of 1,1,1 will be returned if the
     * pdf does not have any values yet.
     *
     * @return the whitepoint tristimulus
     */
    public final PDTristimulus getWhitepoint()
    {
        COSArray wp = dictionary.getCOSArray(COSName.WHITE_POINT);
        if (wp == null)
        {
            wp = new COSArray();
            wp.add(new COSFloat(1.0f));
            wp.add(new COSFloat(1.0f));
            wp.add(new COSFloat(1.0f));
        }
        return new PDTristimulus(wp);
    }

    /**
     * This will return the BlackPoint tristimulus. This is an optional field
     * but has defaults so this will never return null. A default of 0,0,0 will
     * be returned if the pdf does not have any values yet.
     *
     * @return the blackpoint tristimulus
     */
    public final PDTristimulus getBlackPoint()
    {
        COSArray bp = dictionary.getCOSArray(COSName.BLACK_POINT);
        if (bp == null)
        {
            bp = new COSArray();
            bp.add(new COSFloat(0.0f));
            bp.add(new COSFloat(0.0f));
            bp.add(new COSFloat(0.0f));
        }
        return new PDTristimulus(bp);
    }

    /**
     * This will set the whitepoint tristimulus. As this is a required field, null should not be
     * passed into this function.
     *
     * @param whitepoint the whitepoint tristimulus.
     * @throws IllegalArgumentException if null is passed as argument.
     */
    public void setWhitePoint(PDTristimulus whitepoint)
    {
        if (whitepoint == null)
        {
            throw new IllegalArgumentException("Whitepoint may not be null");
        }
        dictionary.setItem(COSName.WHITE_POINT, whitepoint);
        fillWhitepointCache(whitepoint);
    }

    /**
     * This will set the BlackPoint tristimulus.
     *
     * @param blackpoint the BlackPoint tristimulus
     */
    public void setBlackPoint(PDTristimulus blackpoint)
    {
        dictionary.setItem(COSName.BLACK_POINT, blackpoint);
    }

}
