/* * 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.common.function; import java.io.IOException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSInteger; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.common.PDRange; /** * This class represents a type 0 function in a PDF document. * * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> * @version $Revision: 1.2 $ */ public class PDFunctionType0 extends PDFunction { /** * Log instance. */ private static final Log log = LogFactory.getLog(PDFunctionType0.class); /** * An array of 2 × m numbers specifying the linear mapping of input values * into the domain of the function’s sample table. * Default value: [ 0 (Size0 − 1) 0 (Size1 − 1) … ]. */ private COSArray encode = null; /** * An array of 2 × n numbers specifying the linear mapping of sample values * into the range appropriate for the function’s output values. * Default value: same as the value of Range */ private COSArray decode = null; /** * An array of m positive integers specifying the number of samples in each * input dimension of the sample table. */ private COSArray size = null; /** * The samples of the function. */ private int[][] samples = null; /** * Constructor. * * @param functionStream The function . */ public PDFunctionType0(COSBase function) { super( function ); } /** * {@inheritDoc} */ public int getFunctionType() { return 0; } /** * The "Size" entry, which is the number of samples in * each input dimension of the sample table. * * @return A List of java.lang.Integer objects. */ public COSArray getSize() { if (size == null) { size = (COSArray)getDictionary().getDictionaryObject( COSName.SIZE ); } return size; } /** * Get all sample values of this function. * * @return an array with all samples. */ public int[][] getSamples() { if (samples == null) { int arraySize = 1; int numberOfInputValues = getNumberOfInputParameters(); int numberOfOutputValues = getNumberOfOutputParameters(); COSArray sizes = getSize(); for (int i=0;i<numberOfInputValues;i++) { arraySize *= sizes.getInt(i); } samples = new int[arraySize][getNumberOfOutputParameters()]; int bitsPerSample = getBitsPerSample(); int index = 0; int arrayIndex = 0; try { byte[] samplesArray = getPDStream().getByteArray(); for (int i=0;i<numberOfInputValues;i++) { int sizeInputValues = sizes.getInt(i); for (int j=0;j<sizeInputValues;j++) { int bitsLeft = 0; int bitsToRead = bitsPerSample; int currentValue = 0; for (int k=0;k<numberOfOutputValues;k++) { if (bitsLeft == 0) { currentValue = (samplesArray[arrayIndex++]+256)%256; bitsLeft = 8; } int value = 0; while (bitsToRead > 0) { int bits = Math.min(bitsToRead, bitsLeft); value = value << bits; int valueToAdd = currentValue >> (8 - bits); value |= valueToAdd; bitsToRead -= bits; bitsLeft -= bits; if (bitsLeft == 0 && bitsToRead > 0) { currentValue = (samplesArray[arrayIndex++]+256)%256; bitsLeft = 8; } } samples[index][k] = value; bitsToRead = bitsPerSample; } index++; } } } catch (IOException exception) { log.error("IOException while reading the sample values of this function."); } } return samples; } /** * Get the number of bits that the output value will take up. * * Valid values are 1,2,4,8,12,16,24,32. * * @return Number of bits for each output value. */ public int getBitsPerSample() { return getDictionary().getInt( COSName.BITS_PER_SAMPLE ); } /** * Set the number of bits that the output value will take up. Valid values * are 1,2,4,8,12,16,24,32. * * @param bps The number of bits for each output value. */ public void setBitsPerSample( int bps ) { getDictionary().setInt( COSName.BITS_PER_SAMPLE, bps ); } /** * Returns all encode values as COSArray. * * @return the encode array. */ private COSArray getEncodeValues() { if (encode == null) { encode = (COSArray)getDictionary().getDictionaryObject( COSName.ENCODE ); // the default value is [0 (size[0]-1) 0 (size[1]-1) ...] if (encode == null) { encode = new COSArray(); COSArray sizeValues = getSize(); int sizeValuesSize = sizeValues.size(); for (int i=0; i <sizeValuesSize; i++) { encode.add( COSInteger.ZERO ); encode.add( COSInteger.get( sizeValues.getInt(i) - 1) ); } } } return encode; } /** * Returns all decode values as COSArray. * * @return the decode array. */ private COSArray getDecodeValues() { if (decode == null) { decode = (COSArray)getDictionary().getDictionaryObject( COSName.DECODE ); // if decode is null, the default values are the range values if (decode == null) { decode = getRangeValues(); } } return decode; } /** * Get the encode for the input parameter. * * @param paramNum The function parameter number. * * @return The encode parameter range or null if none is set. */ public PDRange getEncodeForParameter( int paramNum ) { PDRange retval = null; COSArray encodeValues = getEncodeValues(); if( encodeValues != null && encodeValues.size() >= paramNum*2+1 ) { retval = new PDRange(encodeValues, paramNum ); } return retval; } /** * This will set the encode values. * * @param range The new encode values. */ public void setEncodeValues(COSArray encodeValues) { encode = encodeValues; getDictionary().setItem(COSName.ENCODE, encodeValues); } /** * Get the decode for the input parameter. * * @param paramNum The function parameter number. * * @return The decode parameter range or null if none is set. */ public PDRange getDecodeForParameter( int paramNum ) { PDRange retval = null; COSArray decodeValues = getDecodeValues(); if( decodeValues != null && decodeValues.size() >= paramNum*2+1 ) { retval = new PDRange(decodeValues, paramNum ); } return retval; } /** * This will set the decode values. * * @param range The new decode values. */ public void setDecodeValues(COSArray decodeValues) { decode = decodeValues; getDictionary().setItem(COSName.DECODE, decodeValues); } /** * {@inheritDoc} */ public float[] eval(float[] input) throws IOException { //This involves linear interpolation based on a set of sample points. //Theoretically it's not that difficult ... see section 3.9.1 of the PDF Reference. float[] sizeValues = getSize().toFloatArray(); int bitsPerSample = getBitsPerSample(); int numberOfInputValues = input.length; int numberOfOutputValues = getNumberOfOutputParameters(); int[] intInputValuesPrevious = new int[numberOfInputValues]; int[] intInputValuesNext = new int[numberOfInputValues]; for (int i=0; i<numberOfInputValues; i++) { PDRange domain = getDomainForInput(i); PDRange encode = getEncodeForParameter(i); input[i] = clipToRange(input[i], domain.getMin(), domain.getMax()); input[i] = interpolate(input[i], domain.getMin(), domain.getMax(), encode.getMin(), encode.getMax()); input[i] = clipToRange(input[i], 0, sizeValues[i]-1); intInputValuesPrevious[i] = (int)Math.floor(input[i]); intInputValuesNext[i] = (int)Math.ceil(input[i]); } float[] outputValuesPrevious = null; float[] outputValuesNext = null; outputValuesPrevious = getSample(intInputValuesPrevious); outputValuesNext = getSample(intInputValuesNext); float[] outputValues = new float[numberOfOutputValues]; for (int i=0;i<numberOfOutputValues;i++) { PDRange range = getRangeForOutput(i); PDRange decode = getDecodeForParameter(i); // TODO using only a linear interpolation. // See "Order" entry in table 3.36 of the PDF reference outputValues[i] = (outputValuesPrevious[i] + outputValuesNext[i]) / 2; outputValues[i] = interpolate(outputValues[i], 0, (float)Math.pow(2, bitsPerSample), decode.getMin(), decode.getMax()); outputValues[i] = clipToRange(outputValues[i], range.getMin(), range.getMax()); } return outputValues; } /** * Get the samples for the given input values. * * @param inputValues an array containing the input values * @return an array with the corresponding samples */ private float[] getSample(int[] inputValues) { int[][] sampleValues = getSamples(); COSArray sizes = getSize(); int numberOfInputValues = getNumberOfInputParameters(); int index = 0; int previousSize = 1; for (int i=0;i<numberOfInputValues;i++) { index += inputValues[i]; previousSize *= sizes.getInt(i); } int numberOfOutputValues = getNumberOfOutputParameters(); float[] result = new float[numberOfOutputValues]; for (int i=0;i<numberOfOutputValues;i++) { result[i] = sampleValues[index][i]; } return result; } }