package org.icepdf.core.pobjects.filters;
import org.icepdf.core.pobjects.Name;
import org.icepdf.core.util.Library;
import org.icepdf.core.util.Utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
/**
* Predictor decoder for LZW and Flate data streams. Uses the same streaming
* as our other Filters but simplifies how the bytes are read in as we treat
* the parent (LZW or Flate) stream as a regular ChunkingInputStream.
*
* @since 5.0.6
*/
public class PredictorDecode extends ChunkingInputStream {
/**
* No predictor function is used
*/
protected static final int PREDICTOR_NONE = 1;
/**
* For every row, each component is derived from corresponding component in entry to left
*/
protected static final int PREDICTOR_TIFF_2 = 2;
/**
* For current row, PNG predictor to do nothing
*/
protected static final int PREDICTOR_PNG_NONE = 10;
/**
* For current row, derive each byte from byte left-by-bytesPerPixel
*/
protected static final int PREDICTOR_PNG_SUB = 11;
/**
* For current row, derive each byte from byte above
*/
protected static final int PREDICTOR_PNG_UP = 12;
/**
* For current row, derive each byte from average of byte left-by-bytesPerPixel and byte above
*/
protected static final int PREDICTOR_PNG_AVG = 13;
/**
* For current row, derive each byte from non-linear function of byte left-by-bytesPerPixel and byte above and byte left-by-bytesPerPixel of above
*/
protected static final int PREDICTOR_PNG_PAETH = 14;
/**
* When given in DecodeParms dict, in stream dict, means first byte of each row is row's predictor
*/
protected static final int PREDICTOR_PNG_OPTIMUM = 15;
protected static final Name DECODE_PARMS_VALUE = new Name("DecodeParms");
protected static final Name PREDICTOR_VALUE = new Name("Predictor");
protected static final Name WIDTH_VALUE = new Name("Width");
protected static final Name COLUMNS_VALUE = new Name("Columns");
protected static final Name COLORS_VALUE = new Name("Colors");
protected static final Name BITS_PER_COMPONENT_VALUE = new Name("BitsPerComponent");
protected static final Name EARLY_CHANGE_VALUE = new Name("EarlyChange");
protected int predictor;
protected int numComponents;
protected int bitsPerComponent;
protected int width;
protected int bytesPerPixel = 1;// From RFC 2083 (PNG), it's bytes per pixel, rounded up to 1
// reference to previous buffer
protected byte[] aboveBuffer;
public PredictorDecode(InputStream input, Library library, HashMap entries) {
super();
// get decode parameters from stream properties
HashMap decodeParmsDictionary = library.getDictionary(entries, DECODE_PARMS_VALUE);
predictor = library.getInt(decodeParmsDictionary, PREDICTOR_VALUE);
Number widthNumber = library.getNumber(entries, WIDTH_VALUE);
if (widthNumber != null) {
width = widthNumber.intValue();
} else {
width = library.getInt(decodeParmsDictionary, COLUMNS_VALUE);
}
// Since DecodeParms.BitsPerComponent has a default value, I don't think we'd
// look at entries.ColorSpace to know the number of components. But, here's the info:
// /ColorSpace /DeviceGray: 1 comp, /DeviceRBG: 3 comps, /DeviceCMYK: 4 comps, /DeviceN: N comps
// I'm going to extend that to mean I won't look at entries.BitsPerComponent either
numComponents = 1; // DecodeParms.Colors: 1,2,3,4 Default=1
bitsPerComponent = 8; // DecodeParms.BitsPerComponent: 1,2,4,8,16 Default=8
Object numComponentsDecodeParmsObj = library.getObject(decodeParmsDictionary, COLORS_VALUE);
if (numComponentsDecodeParmsObj instanceof Number) {
numComponents = ((Number) numComponentsDecodeParmsObj).intValue();
}
Object bitsPerComponentDecodeParmsObj = library.getObject(decodeParmsDictionary, BITS_PER_COMPONENT_VALUE);
if (bitsPerComponentDecodeParmsObj instanceof Number) {
bitsPerComponent = ((Number) bitsPerComponentDecodeParmsObj).intValue();
}
bytesPerPixel = Math.max(1, Utils.numBytesToHoldBits(numComponents * bitsPerComponent));
// Make buffer exactly large enough for one row of data (without predictor)
int intermediateBufferSize = Utils.numBytesToHoldBits(
width * numComponents * bitsPerComponent);
// last row of data above our current buffer
aboveBuffer = new byte[intermediateBufferSize];
setBufferSize(intermediateBufferSize);
setInputStream(input);
}
@Override
protected int fillInternalBuffer() throws IOException {
byte[] temp = aboveBuffer;
aboveBuffer = buffer;
buffer = temp;
int currPredictor;
int cp = in.read();
if (cp < 0) return -1;
// I've seen code that conditionally updates currPredictor:
// if predictor == PREDICTOR_PNG_OPTIMUM
// currPredictor = cp + PREDICTOR_PNG_NONE
//if( predictor == PREDICTOR_PNG_OPTIMUM )
currPredictor = cp + PREDICTOR_PNG_NONE;
// fill the buffer
int numRead = fillBufferFromInputStream();
if (numRead <= 0) return -1;
// apply predictor logic
applyPredictor(numRead, currPredictor);
return numRead;
}
/**
* Apply predictor logic to buffer[] using aboveBuffer[] from previous pass.
*
* @param numRead number of bytes read in last pass.
* @param currPredictor predictor to apply to buffer data.
*/
protected void applyPredictor(int numRead, int currPredictor) {
// loop back over the buffer and update with predicted values.
for (int i = 0; i < numRead; i++) {
// For current row, PNG predictor to do nothing
if (currPredictor == PREDICTOR_PNG_NONE) {
break; // We could continue, but we'd do that numRead times
}
// For current row, derive each byte from byte left-by-bpp
else if (currPredictor == PREDICTOR_PNG_SUB) {
if ((i - bytesPerPixel) >= 0) {
buffer[i] += applyLeftPredictor(buffer, bytesPerPixel, i);
}
}
// For current row, derive each byte from byte above
else if (currPredictor == PREDICTOR_PNG_UP) {
if (aboveBuffer != null) {
buffer[i] += applyAbovePredictor(aboveBuffer, i);
}
}
// For current row, derive each byte from average of byte left-by-bpp and byte above
else if (currPredictor == PREDICTOR_PNG_AVG) {
// PNG AVG: output(x) = curr_line(x) + floor((curr_line(x-bpp)+above(x))/2)
// From RFC 2083 (PNG), sum with no overflow, using >= 9 bit arithmatic
int left = 0;
if ((i - bytesPerPixel) >= 0) {
left = applyLeftPredictor(buffer, bytesPerPixel, i);
}
int above = 0;
if (aboveBuffer != null) {
above = applyAbovePredictor(aboveBuffer, i);
}
int sum = left + above;
byte avg = (byte) ((sum >>> 1) & 0xFF);
buffer[i] += avg;
}
// For current row, derive each byte from non-linear function of
// byte left-by-bpp and byte above and byte left-by-bpp of above
else if (currPredictor == PREDICTOR_PNG_PAETH) {
// From RFC 2083 (PNG)
// PNG PAETH: output(x) = curr_line(x) + PaethPredictor(curr_line(x-bpp), above(x), above(x-bpp))
// PaethPredictor(left, above, aboveLeft)
// p = left + above - aboveLeft
// pLeft = abs(p - left)
// pAbove = abs(p - above)
// pAboveLeft = abs(p - aboveLeft)
// if( pLeft <= pAbove && pLeft <= pAboveLeft ) return left
// if( pAbove <= pAboveLeft ) return above
// return aboveLeft
int left = 0;
if ((i - bytesPerPixel) >= 0) {
left = applyLeftPredictor(buffer, bytesPerPixel, i);
}
int above = 0;
if (aboveBuffer != null) {
above = applyAbovePredictor(aboveBuffer, i);
}
int aboveLeft = 0;
if ((i - bytesPerPixel) >= 0 && aboveBuffer != null) {
aboveLeft = applyAboveLeftPredictor(aboveBuffer, bytesPerPixel, i);
}
int p = left + above - aboveLeft;
int pLeft = Math.abs(p - left);
int pAbove = Math.abs(p - above);
int pAboveLeft = Math.abs(p - aboveLeft);
int paeth = ((pLeft <= pAbove && pLeft <= pAboveLeft)
? left
: ((pAbove <= pAboveLeft)
? above
: aboveLeft));
buffer[i] += ((byte) (paeth & 0xFF));
}
}
}
private static int applyLeftPredictor(byte[] buffer, int bytesPerPixel, int i) {
return (((int) buffer[(i - bytesPerPixel)]) & 0xFF);
}
private static int applyAbovePredictor(byte[] aboveBuffer, int i) {
return (((int) aboveBuffer[i]) & 0xFF);
}
private static int applyAboveLeftPredictor(byte[] aboveBuffer, int bytesPerPixel, int i) {
return (((int) aboveBuffer[i - bytesPerPixel]) & 0xFF);
}
public static boolean isPredictor(Library library, HashMap entries) {
HashMap decodeParmsDictionary = library.getDictionary(entries, DECODE_PARMS_VALUE);
if (decodeParmsDictionary == null) {
return false;
}
int predictor = library.getInt(decodeParmsDictionary, PREDICTOR_VALUE);
if (predictor != PREDICTOR_PNG_NONE && predictor != PREDICTOR_PNG_SUB &&
predictor != PREDICTOR_PNG_UP && predictor != PREDICTOR_PNG_AVG &&
predictor != PREDICTOR_PNG_PAETH && predictor != PREDICTOR_PNG_OPTIMUM) {
return false;
}
return true;
}
}