001: /*
002: * $Id: FunctionType0.java,v 1.3 2007/12/20 18:33:35 rbair Exp $
003: *
004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005: * Santa Clara, California 95054, U.S.A. All rights reserved.
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020: */
021:
022: package com.sun.pdfview.function;
023:
024: import java.io.IOException;
025: import java.nio.ByteBuffer;
026:
027: import com.sun.pdfview.PDFObject;
028: import com.sun.pdfview.PDFParseException;
029:
030: /**
031: * A sampled function maps input values to output values by interpolating
032: * along a line or cubic between two known values.
033: */
034: public class FunctionType0 extends PDFFunction {
035: /** the valid interpolation methods */
036: protected static final int LINEAR_INTERPOLATION = 1;
037: protected static final int CUBIC_INTERPOLATION = 3;
038:
039: /** the size of each input dimension, as an array of <i>m</i> integers */
040: private int[] size;
041:
042: /** the number of bits in each sample */
043: private int bitsPerSample;
044:
045: /** the interpolation type, from the list above */
046: private int order = 1;
047:
048: /** the optional encoding array, tells how to map input parameters to values */
049: private float[] encode;
050:
051: /** the optional decoding array, tells how to map output parameters to values */
052: private float[] decode;
053:
054: /**
055: * the actual samples, converted to integers. The first index is
056: * input values (from 0 to size[m - 1] * size[m - 2] * ... * size[0]),
057: * and the second is the output dimension within the sample (from 0 to n)
058: */
059: private int[][] samples;
060:
061: /** Creates a new instance of FunctionType0 */
062: protected FunctionType0() {
063: super (TYPE_0);
064: }
065:
066: /** Read the function information from a PDF Object */
067: protected void parse(PDFObject obj) throws IOException {
068: // read the size array (required)
069: PDFObject sizeObj = obj.getDictRef("Size");
070: if (sizeObj == null) {
071: throw new PDFParseException(
072: "Size required for function type 0!");
073: }
074: PDFObject[] sizeAry = sizeObj.getArray();
075: int[] size = new int[sizeAry.length];
076: for (int i = 0; i < sizeAry.length; i++) {
077: size[i] = sizeAry[i].getIntValue();
078: }
079: setSize(size);
080:
081: // read the # bits per sample (required)
082: PDFObject bpsObj = obj.getDictRef("BitsPerSample");
083: if (bpsObj == null) {
084: throw new PDFParseException(
085: "BitsPerSample required for function type 0!");
086: }
087: setBitsPerSample(bpsObj.getIntValue());
088:
089: // read the order (optional)
090: PDFObject orderObj = obj.getDictRef("Order");
091: if (orderObj != null) {
092: setOrder(orderObj.getIntValue());
093: }
094:
095: // read the encode array (optional)
096: PDFObject encodeObj = obj.getDictRef("Encode");
097: if (encodeObj != null) {
098: PDFObject[] encodeAry = encodeObj.getArray();
099: float[] encode = new float[encodeAry.length];
100: for (int i = 0; i < encodeAry.length; i++) {
101: encode[i] = encodeAry[i].getFloatValue();
102: }
103: setEncode(encode);
104: }
105:
106: // read the decode array (optional)
107: PDFObject decodeObj = obj.getDictRef("Decode");
108: if (decodeObj != null) {
109: PDFObject[] decodeAry = decodeObj.getArray();
110: float[] decode = new float[decodeAry.length];
111: for (int i = 0; i < decodeAry.length; i++) {
112: decode[i] = decodeAry[i].getFloatValue();
113: }
114: setDecode(decode);
115: }
116:
117: // finally, read the samples
118: setSamples(readSamples(obj.getStreamBuffer()));
119: }
120:
121: /**
122: * Map from <i>m</i> input values to <i>n</i> output values.
123: * The number of inputs <i>m</i> must be exactly one half the size of the
124: * domain. The number of outputs should match one half the size of the
125: * range.
126: *
127: * @param inputs an array of <i>m</i> input values
128: * @param outputs an array of size <i>n</i> which will be filled
129: * with the output values, or null to return a new array
130: */
131: protected void doFunction(float[] inputs, int inputOffset,
132: float[] outputs, int outputOffset) {
133: // calculate the encoded values for each input
134: float[] encoded = new float[getNumInputs()];
135: for (int i = 0; i < getNumInputs(); i++) {
136: // encode -- interpolate(x<i>, domain<2i>, domain<2i + 1>,
137: // encode<2i>, encode<2i + 1>)
138: encoded[i] = interpolate(inputs[i + inputOffset],
139: getDomain(2 * i), getDomain((2 * i) + 1),
140: getEncode(2 * i), getEncode((2 * i) + 1));
141:
142: // clip to size of sample table -- min(max(e<i>, 0), size<i> - 1)
143: encoded[i] = Math.max(encoded[i], 0);
144: encoded[i] = Math.min(encoded[i], size[i] - 1);
145: }
146:
147: // do some magic
148: for (int i = 0; i < getNumOutputs(); i++) {
149: if (getOrder() == 1) {
150: outputs[i + outputOffset] = multilinearInterpolate(
151: encoded, i);
152: } else {
153: outputs[i + outputOffset] = multicubicInterpolate(
154: encoded, i);
155: }
156: }
157:
158: // now adjust the output to be within range
159: for (int i = 0; i < outputs.length; i++) {
160: // decode -- interpolate(r<i>, 0, 2^bps - 1,
161: // decode<2i>, decode<2i + 1>)
162: outputs[i + outputOffset] = interpolate(outputs[i
163: + outputOffset], 0, (float) Math.pow(2,
164: getBitsPerSample()) - 1, getDecode(2 * i),
165: getDecode((2 * i) + 1));
166: }
167: }
168:
169: /**
170: * Get the size of a given input dimension
171: *
172: * @param dimension the input dimension to get the size of
173: * @return the number of samples in the given dimension
174: */
175: protected int getSize(int dimension) {
176: return size[dimension];
177: }
178:
179: /**
180: * Set the size of all input dimensions
181: */
182: protected void setSize(int[] size) {
183: this .size = size;
184: }
185:
186: /**
187: * Get the number of bits per sample
188: */
189: protected int getBitsPerSample() {
190: return bitsPerSample;
191: }
192:
193: /**
194: * Set the number of bits per sample
195: */
196: protected void setBitsPerSample(int bits) {
197: this .bitsPerSample = bits;
198: }
199:
200: /**
201: * Get the interpolation type
202: */
203: protected int getOrder() {
204: return order;
205: }
206:
207: /**
208: * Set the interpolation type
209: */
210: protected void setOrder(int order) {
211: this .order = order;
212: }
213:
214: /**
215: * Get the encoding for a particular input parameter
216: *
217: * @param i the index into the encoding array, which has size 2 * <i>m</i>.
218: * the <i>i</i>th entry in the array has index 2<i>i</i>,
219: * 2<i>i</i> + 1
220: * @return the encoding value if the encoding array is set, or the default
221: */
222: protected float getEncode(int i) {
223: if (encode != null) {
224: return encode[i];
225: } else if ((i % 2) == 0) {
226: return 0f;
227: } else {
228: return (getSize(i / 2) - 1);
229: }
230: }
231:
232: /**
233: * Set the encode array
234: */
235: protected void setEncode(float[] encode) {
236: this .encode = encode;
237: }
238:
239: /**
240: * Get the decoding for a particular input parameter
241: *
242: * @param i the index into the decoding array, which has size 2 * <i>n</i>.
243: * the <i>i</i>th entry in the array has index 2<i>i</i>,
244: * 2<i>i</i> + 1
245: * @return the decoding value if the decoding array is set, or the default
246: */
247: protected float getDecode(int i) {
248: if (decode != null) {
249: return decode[i];
250: } else {
251: return getRange(i);
252: }
253: }
254:
255: /**
256: * Set the decode array
257: */
258: protected void setDecode(float[] decode) {
259: this .decode = decode;
260: }
261:
262: /**
263: * Get a component for a sample given <i>m</i> indices and output
264: * dimension.
265: *
266: * @param values an array of <i>m</i> values determining which sample
267: * to select
268: * @param od the output dimension (0 - <i>n</i>) to get the sample in
269: * @return the sample for the given values and index
270: */
271: protected int getSample(int[] values, int od) {
272: int mult = 1;
273: int index = 0;
274: for (int i = 0; i < values.length; i++) {
275: index += mult * values[i];
276: mult *= getSize(i);
277: }
278:
279: return samples[index][od];
280: }
281:
282: /**
283: * Set the table of samples
284: */
285: protected void setSamples(int[][] samples) {
286: this .samples = samples;
287: }
288:
289: /**
290: * Read the samples from the input stream. Each sample is made up
291: * of <i>n</i> components, each of which has length <i>bitsPerSample</i>
292: * bits. The samples are arranged by dimension, then range
293: */
294: private int[][] readSamples(ByteBuffer buf) {
295: // calculate the number of samples in the table
296: int size = 1;
297: for (int i = 0; i < getNumInputs(); i++) {
298: size *= getSize(i);
299: }
300:
301: // create the samples table
302: int[][] samples = new int[size][getNumOutputs()];
303:
304: // the current location in the buffer, in bits from byteLoc
305: int bitLoc = 0;
306:
307: // the current location in the buffer, in bytes
308: int byteLoc = 0;
309:
310: // the current index in the samples array
311: int index = 0;
312:
313: for (int i = 0; i < getNumInputs(); i++) {
314: for (int j = 0; j < getSize(i); j++) {
315: for (int k = 0; k < getNumOutputs(); k++) {
316: /** [JK FIXME one bit at a time is really inefficient */
317: int value = 0;
318:
319: int toRead = getBitsPerSample();
320: byte curByte = buf.get(byteLoc);
321:
322: while (toRead > 0) {
323: int nextBit = ((curByte >> (7 - bitLoc)) & 0x1);
324: value |= nextBit << (toRead - 1);
325:
326: if (++bitLoc == 8) {
327: bitLoc = 0;
328: byteLoc++;
329:
330: if (toRead > 1) {
331: curByte = buf.get(byteLoc);
332: }
333: }
334:
335: toRead--;
336: }
337:
338: samples[index][k] = value;
339: }
340:
341: index++;
342: }
343: }
344:
345: return samples;
346: }
347:
348: /**
349: * Perform a piecewise multilinear interpolation. The provides a
350: * close approximation to the standard linear interpolation, at
351: * a far lower cost, since every element is not evaluated at every
352: * iteration. Instead, a walk of the most significant axes is performed,
353: * following the algorithm desribed at:
354: * http://osl.iu.edu/~tveldhui/papers/MAScThesis/node33.html
355: *
356: * @param encoded the encoded input values
357: * @param od the output dimension
358: */
359: private float multilinearInterpolate(float[] encoded, int od) {
360: // first calculate the distances -- the differences between
361: // each encoded value and the integer below it.
362: float[] dists = new float[encoded.length];
363:
364: for (int i = 0; i < dists.length; i++) {
365: dists[i] = (float) (encoded[i] - Math.floor(encoded[i]));
366: }
367:
368: // initialize the map of axes. Each bit in this map represents
369: // whether the control value in that dimension should be the integer
370: // above or below encoded[i]
371: int map = 0;
372:
373: // the initial values
374: float val = getSample(encoded, map, od);
375: float prev = val;
376:
377: // walk the axes
378: for (int i = 0; i < dists.length; i++) {
379: // find the largest value of dist remaining
380: int idx = 0;
381: float largest = -1;
382: for (int c = 0; c < dists.length; c++) {
383: if (dists[c] > largest) {
384: largest = dists[c];
385: idx = c;
386: }
387: }
388:
389: // now find the sample with that axis set to 1
390: map |= (0x1 << idx);
391: float cur = getSample(encoded, map, od);
392:
393: // calculate the value and remember it
394: val += dists[idx] * (cur - prev);
395: prev = val;
396:
397: // make sure we won't find this distance again
398: dists[idx] = -1;
399: }
400:
401: // voila
402: return val;
403: }
404:
405: /**
406: * Perform a multicubic interpolation
407: *
408: * @param encoded the encoded input values
409: * @param od the output dimension
410: */
411: private float multicubicInterpolate(float[] encoded, int od) {
412: System.out.println("Cubic interpolation not supported!");
413: return multilinearInterpolate(encoded, od);
414: }
415:
416: /**
417: * Perform a linear interpolation. Given a value x, and two points,
418: * (xmin, ymin), (xmax, ymax), where xmin <= x <= xmax, calculate a value
419: * y on the line from (xmin, ymin) to (xmax, ymax).
420: *
421: * @param x the x value of the input
422: * @param xmin the minimum x value
423: * @param ymin the minimum y value
424: * @param xmax the maximum x value
425: * @param ymax the maximum y value
426: * @return the y value interpolated from the given x
427: */
428: public static float interpolate(float x, float xmin, float xmax,
429: float ymin, float ymax) {
430: float value = (ymax - ymin) / (xmax - xmin);
431: value *= x - xmin;
432: value += ymin;
433:
434: return value;
435: }
436:
437: /**
438: * Get a sample based on an array of encoded values and a control
439: * map. For each bit in the map, if that bit is 0 the integer below
440: * the encoded value is selected, or if the bit is 1, the interger
441: * above is selected.
442: *
443: * @param encoded the encoded values
444: * @param map the bit map of control values
445: * @param od the output dimension to read the sample for
446: */
447: private float getSample(float[] encoded, int map, int od) {
448: int[] controls = new int[encoded.length];
449:
450: // fill in the controls array with appropriate ints
451: for (int i = 0; i < controls.length; i++) {
452: if ((map & (0x1 << i)) == 0) {
453: controls[i] = (int) Math.floor(encoded[i]);
454: } else {
455: controls[i] = (int) Math.ceil(encoded[i]);
456: }
457: }
458:
459: // now return the actual sample
460: return getSample(controls, od);
461: }
462: }
|