/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * 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.icepdf.core.pobjects.functions; import org.icepdf.core.pobjects.Dictionary; import org.icepdf.core.pobjects.Name; import java.util.List; /** * <p>Type 3 Function (PDF 1.3) defines a stitching of the sub-domains of * several 1-input functions to produce a single new 1-input function.</p> * * @author ICEsoft Technologies Inc. * @since 3.0 */ public class Function_3 extends Function { public static final Name BOUNDS_KEY = new Name("Bounds"); public static final Name ENCODE_KEY = new Name("Encode"); public static final Name FUNCTIONS_KEY = new Name("Functions"); // An array of k-1 numbers that, in combination with Domain, define the // intervals to which each function from the Functions array applies. Bounds // must be in order of increasing value and each value must be with in the // domain defined by Domain private float bounds[]; // An array of 2xk numbers that, taken in pairs, cMap each subset of the // domain defined by Domain and the bounds array to the domain of the // corresponding function. private float encode[]; // An array of k 1--input functions making up the stitching function. The // output dimensionality of all functions must be the same, and compatible // with the values of the Range if Range is represent. private Function functions[]; /** * Creates a new instance of a type 2 function. * * @param d function's dictionary. */ Function_3(Dictionary d) { super(d); // Convert bounds dictionary values. List boundTemp = (List) d.getObject(BOUNDS_KEY); if (boundTemp != null) { bounds = new float[boundTemp.size()]; for (int i = 0; i < boundTemp.size(); i++) { bounds[i] = ((Number) boundTemp.get(i)).floatValue(); } } // convert encode dictionary. List encodeTemp = (List) d.getObject(ENCODE_KEY); if (encodeTemp != null) { encode = new float[encodeTemp.size()]; for (int i = 0; i < encodeTemp.size(); i++) { encode[i] = ((Number) encodeTemp.get(i)).floatValue(); } } List functionTemp = (List) d.getObject(FUNCTIONS_KEY); if (encodeTemp != null) { functions = new Function[functionTemp.size()]; for (int i = 0; i < functionTemp.size(); i++) { functions[i] = Function.getFunction(d.getLibrary(), functionTemp.get(i)); // System.out.println("Function " + functions[i].toString()); } } } /** * <p>Puts the value x thought the function type 3 algorithm. * * @param x input values m * @return output values n */ public float[] calculate(float[] x) { int k = functions.length; if (k == 1 && bounds.length == 0) { if (domain[0] <= x[0] && x[0] <= domain[1]) { return encode(x, functions[0], 0); } } // Find where x finds into the following range: // Domain0 < Bounds0 < Bounds1 < ... < Boundsk-2 < Domain1 // where k = functions length. The found bound is the equivalent function // to use to encode the x value. for (int b = 0; b < bounds.length; b++) { // first sub domain if (b == 0) { // check if domain0 <= x < bounds0, return function if true if (domain[0] <= x[0] && x[0] < bounds[b]) { return encode(x, functions[b], b); } } // last sub domain if (b == k - 2) { // check if bounds k-2 <= x <= domain 0, return function if true if (bounds[b] <= x[0] && x[0] <= domain[1]) { return encode(x, functions[k - 1], k - 1); } } // bounds <= x < bounds b + 1, return function if true if (bounds[b] <= x[0] && x[0] < bounds[b + 1]) { return encode(x, functions[b], b); } } return null; } /** * Utility method to apply the interpolation rules and finally calculate * the return value using the selected function. The method also checks * to see if the values fall in the specified range and makes the * appropriate adjustments, if range is present. * * @param x one element array, * @param function function to be applied can be of any type. * @param i i th subdomain, selected subdomain. * @return n length array of calculated values. n length is defined by the * colour space component count. */ private float[] encode(float[] x, Function function, int i) { int k = functions.length; if (i <= 0 && i < k && bounds.length > 0) { float b1; float b2; if (i - 1 == -1) { // domain 0 b1 = domain[0]; } else { b1 = bounds[i - 1]; } if (i == k - 1) { // domain 1 b2 = domain[1]; } else { b2 = bounds[i]; } if (k - 2 < bounds.length && bounds[k - 2] == domain[1]) { x[0] = encode[2 * i]; } x[0] = interpolate(x[0], b1, b2, encode[2 * i], encode[2 * i + 1]); x = function.calculate(x); } else { x[0] = interpolate(x[0], domain[0], domain[1], encode[2 * i], encode[2 * i + 1]); x = function.calculate(x); } // Have seen a few corner cases where the bounds are not defined or are null. There is nothing // in the spec about how to handle this, so the work around below is experimental. if (x != null) { return validateAgainstRange(x); } else { return new float[]{1, 1, 1, 1}; } } /** * Utility method to check if the values fall within the functions range. * * @param values values to test against range. * @return correct values that fall within the functions range. */ private float[] validateAgainstRange(float[] values) { // Range is an array of 2xn numbers, where n is the number of output // values. for (int j = 0, max = values.length; j < max; j++) { if (range != null && values[j] < range[2 * j]) { values[j] = range[2 * j]; } else if (range != null && values[j] > range[(2 * j) + 1]) { values[j] = range[(2 * j) + 1]; } else if (values[j] < 0) { values[j] = 0.0f; } } return values; } }