class Rinterpol
    {
        // coordinate that is to be interpolated
        private final float[] in;
        // coordinate of the "ceil" point
        private final int[] inPrev;
        // coordinate of the "floor" point
        private final int[] inNext;
        private final int numberOfInputValues;
        private final int numberOfOutputValues = getNumberOfOutputParameters();

        /**
         * Constructor.
         *
         * @param input the input coordinates
         * @param inputPrev coordinate of the "ceil" point
         * @param inputNext coordinate of the "floor" point
         *
         */
        Rinterpol(float[] input, int[] inputPrev, int[] inputNext)
        {
            in = input;
            inPrev = inputPrev;
            inNext = inputNext;
            numberOfInputValues = input.length;
        }

        /**
         * Calculate the interpolation.
         *
         * @return interpolated result sample
         */
        float[] rinterpolate()
        {
            return rinterpol(new int[numberOfInputValues], 0);
        }

        /**
         * Do a linear interpolation if the two coordinates can be known, or
         * call itself recursively twice.
         *
         * @param coord coord partially set coordinate (not set from step
         * upwards); gets fully filled in the last call ("leaf"), where it is
         * used to get the correct sample
         * @param step between 0 (first call) and dimension - 1
         * @return interpolated result sample
         */
        private float[] rinterpol(int[] coord, int step)
        {
            float[] resultSample = new float[numberOfOutputValues];
            if (step == in.length - 1)
            {
                // leaf
                if (inPrev[step] == inNext[step])
                {
                    coord[step] = inPrev[step];
                    int[] tmpSample = getSamples()[calcSampleIndex(coord)];
                    for (int i = 0; i < numberOfOutputValues; ++i)
                    {
                        resultSample[i] = tmpSample[i];
                    }
                    return resultSample;
                }
                coord[step] = inPrev[step];
                int[] sample1 = getSamples()[calcSampleIndex(coord)];
                coord[step] = inNext[step];
                int[] sample2 = getSamples()[calcSampleIndex(coord)];
                for (int i = 0; i < numberOfOutputValues; ++i)
                {
                    resultSample[i] = interpolate(in[step], inPrev[step], inNext[step], sample1[i], sample2[i]);
                }
                return resultSample;
            }
            else
            {
                // branch
                if (inPrev[step] == inNext[step])
                {
                    coord[step] = inPrev[step];
                    return rinterpol(coord, step + 1);
                }
                coord[step] = inPrev[step];
                float[] sample1 = rinterpol(coord, step + 1);
                coord[step] = inNext[step];
                float[] sample2 = rinterpol(coord, step + 1);
                for (int i = 0; i < numberOfOutputValues; ++i)
                {
                    resultSample[i] = interpolate(in[step], inPrev[step], inNext[step], sample1[i], sample2[i]);
                }
                return resultSample;
            }
        }

        /**
         * calculate array index (structure described in p.171 PDF spec 1.7) in multiple dimensions.
         *
         * @param vector with coordinates
         * @return index in flat array
         */
        private int calcSampleIndex(int[] vector)
        {
            // inspiration: http://stackoverflow.com/a/12113479/535646
            // but used in reverse
            float[] sizeValues = getSize().toFloatArray();
            int index = 0;
            int sizeProduct = 1;
            int dimension = vector.length;
            for (int i = dimension - 2; i >= 0; --i)
            {
                sizeProduct *= sizeValues[i];
            }
            for (int i = dimension - 1; i >= 0; --i)
            {
                index += sizeProduct * vector[i];
                if (i - 1 >= 0)
                {
                    sizeProduct /= sizeValues[i - 1];
                }
            }
            return index;
        }

        /**
         * Get all sample values of this function.
         *
         * @return an array with all samples.
         */
        private int[][] getSamples()
        {<FILL_FUNCTION_BODY>}
    }

            if (samples == null)
            {
                int arraySize = 1;
                int nIn = getNumberOfInputParameters();
                int nOut = getNumberOfOutputParameters();
                COSArray sizes = getSize();
                for (int i = 0; i < nIn; i++)
                {
                    arraySize *= sizes.getInt(i);
                }
                samples = new int[arraySize][nOut];
                int bitsPerSample = getBitsPerSample();
                int index = 0;
                try (InputStream is = getPDStream().createInputStream())
                {
                    // PDF spec 1.7 p.171:
                    // Each sample value is represented as a sequence of BitsPerSample bits. 
                    // Successive values are adjacent in the bit stream; there is no padding at byte boundaries.
                    try (ImageInputStream mciis = new MemoryCacheImageInputStream(is))
                    {
                        for (int i = 0; i < arraySize; i++)
                        {
                            for (int k = 0; k < nOut; k++)
                            {
                                // TODO will this cast work properly for 32 bitsPerSample or should we use long[]?
                                samples[index][k] = (int) mciis.readBits(bitsPerSample);
                            }
                            index++;
                        }
                    }
                }
                catch (IOException exception)
                {
                    LOG.error("IOException while reading the sample values of this function.", exception);
                }
            }
            return samples;
        
<methods>public void <init>(org.apache.pdfbox.cos.COSBase) ,public static org.apache.pdfbox.pdmodel.common.function.PDFunction create(org.apache.pdfbox.cos.COSBase) throws java.io.IOException,public abstract float[] eval(float[]) throws java.io.IOException,public final org.apache.pdfbox.cos.COSDictionary getCOSObject() ,public org.apache.pdfbox.pdmodel.common.PDRange getDomainForInput(int) ,public abstract int getFunctionType() ,public int getNumberOfInputParameters() ,public int getNumberOfOutputParameters() ,public org.apache.pdfbox.pdmodel.common.PDRange getRangeForOutput(int) ,public void setDomainValues(org.apache.pdfbox.cos.COSArray) ,public void setRangeValues(org.apache.pdfbox.cos.COSArray) ,public java.lang.String toString() <variables>private org.apache.pdfbox.cos.COSArray domain,private org.apache.pdfbox.cos.COSDictionary functionDictionary,private org.apache.pdfbox.pdmodel.common.PDStream functionStream,private int numberOfInputValues,private int numberOfOutputValues,private org.apache.pdfbox.cos.COSArray range