/* * 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 org.icepdf.core.pobjects.Stream; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * <p>This class <code>Function_0</code> represents a generic Type 0, sampled function * type. Type 0 functions use a sequence of sampled values (contained in a stream) * to produce an approximation for function whose domains and ranges are bounded. * The samples are organized as an m-dimensional table in which each entry has n * components. </p> * <br> * <p>Sampled functions are highly general and offer reasonably accurate * representations of arbitrary analytic functions at low expense. The * dimensionality of a sampled function is restricted only by the implementation * limits.</p> * * @see Function * @since 1.0 */ public class Function_0 extends Function { private static final Logger logger = Logger.getLogger(Function_0.class.toString()); public static final Name SIZE_KEY = new Name("Size"); public static final Name BITSPERSAMPLE_KEY = new Name("BitsPerSample"); public static final Name ENCODE_KEY = new Name("Encode"); public static final Name DECODE_KEY = new Name("Decode"); // An array of m positive integers specifying the number of samples in each // input dimension of the sample table. private int size[]; // The number of bits used to represent each sample. If the function has // multiple output values, each one occupies BitsPerSample bits. Valid // values are 1,2,4,8,12,16,24, and 32. private int bitsPerSample; // The order of interpolation between samples. Valid values are 1 and 3, // specifying linear and cubic spline interpolation, respectively. Default 1 private int order; // An array of 2 x m numbers specifying the linear mapping of input values // into the domain of the function's sample table. Default value: // [0 (size<sub>0</sub>-1) 0 size<sub>1</sub> ...]. private float encode[]; // An array of 2 x n numbers specifying the linear mapping of sample values // into the range the range appropriate for the function's output values. // Default same as Range. private float[] decode; private int[][] samples; /** * Creates a new instance of a type 0 function. * * @param d function's dictionary. */ Function_0(Dictionary d) { // initiate, domain and range super(d); List s = (List) d.getObject(SIZE_KEY); // setup size array, each entry represents the number of samples for // each input dimension. size = new int[s.size()]; for (int i = 0; i < s.size(); i++) { size[i] = (int) (((Number) s.get(i)).floatValue()); } // setup bitsPerSample array, each entry represents the number of bits used // for each sample bitsPerSample = d.getInt(BITSPERSAMPLE_KEY); // setup of encode table, specifies the linear mapping of input values // into the domain of the function's sample table. List enc = (List) d.getObject(ENCODE_KEY); encode = new float[size.length * 2]; if (enc != null) { for (int i = 0; i < size.length * 2; i++) { encode[i] = ((Number) enc.get(i)).floatValue(); } } else { // encoding is optional, so fill up encode area with uniform // mapping of 0,size[0]-1, 0,size[1]-1, 0,size[2]-1 which is // the default value which is defined in the spec. for (int i = 0; i < size.length; i++) { encode[2 * i] = 0; encode[2 * i + 1] = size[i] - 1; } } // setup decode, an array of 2 x n numbers specifying the linear mapping // of sample values into the range appropriate for the function's output values. List dec = (List) d.getObject(DECODE_KEY); decode = new float[range.length]; if (dec != null) { for (int i = 0; i < range.length; i++) { decode[i] = ((Number) dec.get(i)).floatValue(); } } else { // decode is optional, so we should copy range as a default values System.arraycopy(range, 0, decode, 0, range.length); } // lastly get the stream byte data if any. Stream stream = (Stream) d; convertToSamples(stream.getDecodedStreamBytes(0), bitsPerSample); } /** * Calculates the y values for the given x values using a sampled function. * * @param x array of input values m. * @return array of output value n. */ public float[] calculate(float[] x) { // length of output array int n = range.length / 2; // ready output array float y[] = new float[n]; // work throw all input data and store in y[] try { // sampled each input value xi for 0 & i < m for (int i = 0; i < size.length; i++) { // clip input value appropriately for the given domain // xi' = min (max(xi, Domain2i), Domain2i+1) x[i] = Math.min(Math.max(x[i], domain[2 * i]), domain[2 * i + 1]); // find the encoded value // ei = interpolate (xi', Domain2i, Domain2i+1, Encode2i, Encode2i+1) float e = interpolate(x[i], domain[2 * i], domain[2 * i + 1], encode[2 * i], encode[2 * i + 1]); // clip to the size of the sampled table in that dimension: // ei' = min (max(ei, 0), Sizei-1) e = Math.min(Math.max(e, 0), size[i] - 1); // pretty sure that e1 and e2 are used to for a bilinear interpolation? // Output values are are calculated from the nearest surrounding values // in the sample table in the sample table. int e1 = (int) Math.floor(e); int e2 = (int) Math.ceil(e); int index; // Calculate the final output values for (int j = 0; j < n; j++) { // find nearest surrounding values in the sample table int b1 = samples[e1][j]; int b2 = samples[e2][j]; // get the average float r = ((float) b1 + (float) b2) / 2; // interpolate to get output values r = interpolate(r, 0f, (float) Math.pow(2, bitsPerSample) - 1, decode[2 * j], decode[2 * j + 1]); // finally, decoded values are clipped ot the range // yj = min(max(rj', Range2j), Range2j+1) r = Math.min(Math.max(r, range[2 * j]), range[2 * j + 1]); index = i * n + j; // make sure we y can contain the calculated r value if (index < y.length) { y[index] = r; } } } } catch (Exception e) { logger.log(Level.FINER, "Error calculating function 0 values", e); } return y; } /** * Utility for converting sample bytes to integers of the correct bits per sample. * * @param bytes byte array to convert. * @param bitsPerSample bits per sample value */ private void convertToSamples(byte[] bytes, int bitsPerSample) { int size = 1; int inputMax = domain.length / 2; int outputMax = range.length / 2; for (int i = 0; i < inputMax; i++) { size *= this.size[i]; } samples = new int[size][outputMax]; int sampleIndex = 0; int byteLocation = 0; int bitLocation = 0; for (int i = 0; i < inputMax; i++) { for (int j = 0; j < this.size[i]; j++) { for (int k = 0; k < outputMax; k++) { int value = 0; int bitsToRead = bitsPerSample; byte byteCount = bytes[byteLocation]; while (bitsToRead > 0) { int nextBit = ((byteCount >> (7 - bitLocation)) & 0x1); value |= nextBit << (bitsToRead - 1); bitLocation++; // skip to the next bit. if (bitLocation == 8) { bitLocation = 0; byteLocation++; if (bitsToRead > 1) { byteCount = bytes[byteLocation]; } } bitsToRead--; } samples[sampleIndex][k] = value; } sampleIndex++; } } } }