/*
* 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.
*/
package org.apache.pdfbox.pdmodel.graphics.color;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNull;
import org.apache.pdfbox.pdmodel.common.function.PDFunction;
/**
* A Separation color space used to specify either additional colorants or for isolating the
* control of individual colour components of a device colour space for a subtractive device.
* When such a space is the current colour space, the current colour shall be a single-component
* value, called a tint, that controls the given colorant or colour components only.
*
* @author Ben Litchfield
* @author John Hewson
*/
public class PDSeparation extends PDSpecialColorSpace
{
private final PDColor initialColor = new PDColor(new float[] { 1 }, this);
// array indexes
private static final int COLORANT_NAMES = 1;
private static final int ALTERNATE_CS = 2;
private static final int TINT_TRANSFORM = 3;
// fields
private PDColorSpace alternateColorSpace = null;
private PDFunction tintTransform = null;
/**
* Creates a new Separation color space.
*/
public PDSeparation()
{
array = new COSArray();
array.add(COSName.SEPARATION);
array.add(COSName.getPDFName(""));
// add some placeholder
array.add(COSNull.NULL);
array.add(COSNull.NULL);
}
/**
* Creates a new Separation color space from a PDF color space array.
* @param separation an array containing all separation information.
* @throws IOException if the color space or the function could not be created.
*/
public PDSeparation(COSArray separation) throws IOException
{
array = separation;
alternateColorSpace = PDColorSpace.create(array.getObject(ALTERNATE_CS));
tintTransform = PDFunction.create(array.getObject(TINT_TRANSFORM));
}
@Override
public String getName()
{
return COSName.SEPARATION.getName();
}
@Override
public int getNumberOfComponents()
{
return 1;
}
@Override
public float[] getDefaultDecode(int bitsPerComponent)
{
return new float[] { 0, 1 };
}
@Override
public PDColor getInitialColor()
{
return initialColor;
}
@Override
public float[] toRGB(float[] value) throws IOException
{
float[] altColor = tintTransform.eval(value);
return alternateColorSpace.toRGB(altColor);
}
//
// WARNING: this method is performance sensitive, modify with care!
//
@Override
public BufferedImage toRGBImage(WritableRaster raster) throws IOException
{
if (alternateColorSpace instanceof PDLab)
{
// PDFBOX-3622 - regular converter fails for Lab colorspaces
return toRGBImage2(raster);
}
// use the tint transform to convert the sample into
// the alternate color space (this is usually 1:many)
WritableRaster altRaster = Raster.createBandedRaster(DataBuffer.TYPE_BYTE,
raster.getWidth(), raster.getHeight(),
alternateColorSpace.getNumberOfComponents(),
new Point(0, 0));
int numAltComponents = alternateColorSpace.getNumberOfComponents();
int width = raster.getWidth();
int height = raster.getHeight();
float[] samples = new float[1];
Map<Integer, int[]> calculatedValues = new HashMap<>();
Integer hash;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
raster.getPixel(x, y, samples);
int[] alt = calculatedValues.get(hash = Float.floatToIntBits(samples[0]));
if (alt == null)
{
alt = new int[numAltComponents];
tintTransform(samples, alt);
calculatedValues.put(hash, alt);
}
altRaster.setPixel(x, y, alt);
}
}
// convert the alternate color space to RGB
return alternateColorSpace.toRGBImage(altRaster);
}
// converter that works without using super implementation of toRGBImage()
private BufferedImage toRGBImage2(WritableRaster raster) throws IOException
{
int width = raster.getWidth();
int height = raster.getHeight();
BufferedImage rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
WritableRaster rgbRaster = rgbImage.getRaster();
float[] samples = new float[1];
Map<Integer, int[]> calculatedValues = new HashMap<>();
Integer hash;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
raster.getPixel(x, y, samples);
int[] rgb = calculatedValues.get(hash = Float.floatToIntBits(samples[0]));
if (rgb == null)
{
samples[0] /= 255;
float[] altColor = tintTransform.eval(samples);
float[] fltab = alternateColorSpace.toRGB(altColor);
rgb = new int[3];
rgb[0] = (int) (fltab[0] * 255);
rgb[1] = (int) (fltab[1] * 255);
rgb[2] = (int) (fltab[2] * 255);
calculatedValues.put(hash, rgb);
}
rgbRaster.setPixel(x, y, rgb);
}
}
return rgbImage;
}
protected void tintTransform(float[] samples, int[] alt) throws IOException
{
samples[0] /= 255; // 0..1
float[] result = tintTransform.eval(samples);
for (int s = 0; s < alt.length; s++)
{
// scale to 0..255
alt[s] = (int) (result[s] * 255);
}
}
/**
* Returns the colorant name.
* @return the name of the colorant
*/
public PDColorSpace getAlternateColorSpace()
{
return alternateColorSpace;
}
/**
* Returns the colorant name.
* @return the name of the colorant
*/
public String getColorantName()
{
COSName name = (COSName)array.getObject(COLORANT_NAMES);
return name.getName();
}
/**
* Sets the colorant name.
* @param name the name of the colorant
*/
public void setColorantName(String name)
{
array.set(1, COSName.getPDFName(name));
}
/**
* Sets the alternate color space.
* @param colorSpace The alternate color space.
*/
public void setAlternateColorSpace(PDColorSpace colorSpace)
{
alternateColorSpace = colorSpace;
COSBase space = null;
if (colorSpace != null)
{
space = colorSpace.getCOSObject();
}
array.set(ALTERNATE_CS, space);
}
/**
* Sets the tint transform function.
* @param tint the tint transform function
*/
public void setTintTransform(PDFunction tint)
{
tintTransform = tint;
array.set(TINT_TRANSFORM, tint);
}
@Override
public String toString()
{
return getName() + "{" +
"\"" + getColorantName() + "\"" + " " +
alternateColorSpace.getName() + " " +
tintTransform + "}";
}
}