/* * 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.Stream; import org.icepdf.core.pobjects.functions.postscript.Lexer; import org.icepdf.core.util.Utils; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * <p>Type 4 Function (PDF 1.3), also called a PostScript calculator function, * shall be represented as a stream containing code written n a small subset of * the PostScript language. </p> * <p>Type 4 functions offer greater flexibility and potentially greater * accuracy then exponential functions (type 2 functions). Type 4 functions * also make it possible to include a wide variety of halftone spots functions * without the loss of accuracy that comes from sampling, and without adding to * the list a predefined spot function (10.5.3 spot functions). All of the * predefined spot functions can be written as type 4 functions. </p> * * @author ICEsoft Technologies Inc. * @since 4.2 */ public class Function_4 extends Function { private static final Logger logger = Logger.getLogger(Function_4.class.toString()); // decoded content that makes up the type 4 functions. private byte[] functionContent; // cache for calculated colour values private ConcurrentHashMap<Integer, float[]> resultCache; public Function_4(Dictionary d) { super(d); // decode the stream for parsing. if (d instanceof Stream) { Stream functionStream = (Stream) d; functionContent = functionStream.getDecodedStreamBytes(0); if (logger.isLoggable(Level.FINER)) { logger.finer("Function 4: " + Utils.convertByteArrayToByteString(functionContent)); } } else { logger.finer("Type 4 function operands could not be found."); } // cache for type 4 function results. resultCache = new ConcurrentHashMap<Integer, float[]>(); } /** * <p>Puts the value x thought the function type 4 algorithm. * * @param x input values m * @return output values n */ public float[] calculate(float[] x) { // check the cache in case we've already made the calculation. Integer colourKey = calculateColourKey(x); float[] result = resultCache.get(colourKey); if (result != null) { return result; } // setup the lexer stream InputStream content = new ByteArrayInputStream(functionContent); Lexer lex = new Lexer(); lex.setInputStream(content); // parse/evaluate the type 4 functions with the input value(s) x. try { lex.parse(x); } catch (Throwable e) { logger.log(Level.FINER, "Error Processing Type 4 definition", e); } // get the remaining number on the stack which are the return values. Stack stack = lex.getStack(); // length of output array int n = range.length / 2; // ready output array float y[] = new float[n]; // pop remaining items off the stack and apply the range bounds. for (int i = 0; i < n; i++) { y[i] = Math.min(Math.max((Float) stack.elementAt(i), range[2 * i]), range[2 * i + 1]); } // add the new value to the cache. resultCache.put(colourKey, y); return y; } /** * Utility for creating a comparable colour key for colour components. * * @param colours one or more colour values, usually maxes out at four. * @return concatenation of colour values. */ private Integer calculateColourKey(float[] colours) { int length = colours.length; // only works for colour vlues 0-255 if (!(colours[0] <= 1.0)) { if (length == 1) { return (int) colours[0]; } else if (length == 2) { return ((int) colours[1] << 8) | (int) colours[0]; } else if (length == 3) { return ((int) colours[2] << 16) | ((int) colours[1] << 8) | (int) colours[0]; } } // otherwise expensive hash generation. StringBuilder builder = new StringBuilder(); for (float colour : colours) { builder.append(colour); } return builder.toString().hashCode(); } }