/* * 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.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSObject; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSStream; import org.apache.pdfbox.pdmodel.common.COSObjectable; import org.apache.pdfbox.pdmodel.common.PDRange; import org.apache.pdfbox.pdmodel.common.PDStream; /** * This class represents a function in a PDF document. * * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> * @version $Revision: 1.3 $ */ public abstract class PDFunction implements COSObjectable { private PDStream functionStream = null; private COSDictionary functionDictionary = null; private COSArray domain = null; private COSArray range = null; /** * Constructor. * * @param function The function stream. * */ public PDFunction( COSBase function ) { if (function instanceof COSStream) { functionStream = new PDStream( (COSStream)function ); functionStream.getStream().setName( COSName.TYPE, "Function" ); } else if (function instanceof COSDictionary) { functionDictionary = (COSDictionary)function; } } /** * Returns the function type. * * Possible values are: * * 0 - Sampled function * 2 - Exponential interpolation function * 3 - Stitching function * 4 - PostScript calculator function * * @return the function type. */ public abstract int getFunctionType(); /** * Returns the COSObject. * * {@inheritDoc} */ public COSBase getCOSObject() { if (functionStream != null) { return functionStream.getCOSObject(); } else { return functionDictionary; } } /** * Returns the stream. * @return The stream for this object. */ public COSDictionary getDictionary() { if (functionStream != null) { return functionStream.getStream(); } else { return functionDictionary; } } /** * Returns the underlying PDStream. * @return The stream. */ protected PDStream getPDStream() { return functionStream; } /** * Create the correct PD Model function based on the COS base function. * * @param function The COS function dictionary. * * @return The PDModel Function object. * * @throws IOException If we are unable to create the PDFunction object. */ public static PDFunction create( COSBase function ) throws IOException { PDFunction retval = null; if( function instanceof COSObject ) { function = ((COSObject)function).getObject(); } COSDictionary functionDictionary = (COSDictionary)function; int functionType = functionDictionary.getInt( COSName.FUNCTION_TYPE ); if( functionType == 0 ) { retval = new PDFunctionType0(functionDictionary); } else if( functionType == 2 ) { retval = new PDFunctionType2(functionDictionary); } else if( functionType == 3 ) { retval = new PDFunctionType3(functionDictionary); } else if( functionType == 4 ) { retval = new PDFunctionType4(functionDictionary); } else { throw new IOException( "Error: Unknown function type " + functionType ); } return retval; } /** * This will get the number of output parameters that * have a range specified. A range for output parameters * is optional so this may return zero for a function * that does have output parameters, this will simply return the * number that have the rnage specified. * * @return The number of input parameters that have a range * specified. */ public int getNumberOfOutputParameters() { COSArray rangeValues = getRangeValues(); return rangeValues.size() / 2; } /** * This will get the range for a certain output parameters. This is will never * return null. If it is not present then the range 0 to 0 will * be returned. * * @param n The output parameter number to get the range for. * * @return The range for this component. */ public PDRange getRangeForOutput(int n) { COSArray rangeValues = getRangeValues(); return new PDRange( rangeValues, n ); } /** * This will set the range values. * * @param rangeValues The new range values. */ public void setRangeValues(COSArray rangeValues) { range = rangeValues; getDictionary().setItem(COSName.RANGE, rangeValues); } /** * This will get the number of input parameters that * have a domain specified. * * @return The number of input parameters that have a domain * specified. */ public int getNumberOfInputParameters() { COSArray array = getDomainValues(); return array.size() / 2; } /** * This will get the range for a certain input parameter. This is will never * return null. If it is not present then the range 0 to 0 will * be returned. * * @param n The parameter number to get the domain for. * * @return The domain range for this component. */ public PDRange getDomainForInput(int n) { COSArray domainValues = getDomainValues(); return new PDRange( domainValues, n ); } /** * This will set the domain values. * * @param domainValues The new domain values. */ public void setDomainValues(COSArray domainValues) { domain = domainValues; getDictionary().setItem(COSName.DOMAIN, domainValues); } /** * Evaluates the function at the given input. * ReturnValue = f(input) * * @param input The COSArray of input values for the function. * In many cases will be an array of a single value, but not always. * * @return The of outputs the function returns based on those inputs. * In many cases will be an COSArray of a single value, but not always. * * @throws IOException an IOExcpetion is thrown if something went wrong processing the function. * */ public COSArray eval(COSArray input) throws IOException { // TODO should we mark this method as deprecated? float[] outputValues = eval(input.toFloatArray()); COSArray array = new COSArray(); array.setFloatArray(outputValues); return array; } /** * Evaluates the function at the given input. * ReturnValue = f(input) * * @param input The array of input values for the function. * In many cases will be an array of a single value, but not always. * * @return The of outputs the function returns based on those inputs. * In many cases will be an array of a single value, but not always. * * @throws IOException an IOExcpetion is thrown if something went wrong processing the function. */ public abstract float[] eval(float[] input) throws IOException; /** * Returns all ranges for the output values as COSArray . * Required for type 0 and type 4 functions * @return the ranges array. */ protected COSArray getRangeValues() { if (range == null) { range = (COSArray)getDictionary().getDictionaryObject( COSName.RANGE ); } return range; } /** * Returns all domains for the input values as COSArray. * Required for all function types. * @return the domains array. */ private COSArray getDomainValues() { if (domain == null) { domain = (COSArray)getDictionary().getDictionaryObject( COSName.DOMAIN ); } return domain; } /** * Clip the given input values to the ranges. * * @param inputArray the input values * @return the clipped values */ protected float[] clipToRange(float[] inputValues) { COSArray rangesArray = getRangeValues(); float[] result = null; if (rangesArray != null) { float[] rangeValues = rangesArray.toFloatArray(); int numberOfRanges = rangeValues.length/2; result = new float[numberOfRanges]; for (int i=0; i<numberOfRanges; i++) { result[i] = clipToRange(inputValues[i], rangeValues[2*i], rangeValues[2*i+1]); } } else { result = inputValues; } return result; } /** * Clip the given input value to the given range. * * @param x the input value * @param rangeMin the min value of the range * @param rangeMax the max value of the range * @return the clipped value */ protected float clipToRange(float x, float rangeMin, float rangeMax) { return Math.min(Math.max(x, rangeMin), rangeMax); } /** * For a given value of x, interpolate calculates the y value * on the line defined by the two points (xRangeMin , xRangeMax ) * and (yRangeMin , yRangeMax ). * * @param x the to be interpolated value. * @param xRangeMin the min value of the x range * @param xRangeMax the max value of the x range * @param yRangeMin the min value of the y range * @param yRangeMax the max value of the y range * @return the interpolated y value */ protected float interpolate(float x, float xRangeMin, float xRangeMax, float yRangeMin, float yRangeMax) { return yRangeMin + ((x - xRangeMin) * (yRangeMax - yRangeMin)/(xRangeMax - xRangeMin)); } }