package org.icepdf.core.pobjects.graphics;
import org.icepdf.core.io.BitStream;
import org.icepdf.core.pobjects.ImageStream;
import org.icepdf.core.pobjects.Name;
import org.icepdf.core.pobjects.Stream;
import org.icepdf.core.pobjects.functions.Function;
import org.icepdf.core.util.Library;
import java.awt.*;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.HashMap;
import java.util.logging.Logger;
/**
* Base class for Mesh shading types 4-7. Each subtype parses the shading vertex information slighly differently
* but the decode and base parse for flag, coordinate and colour is the same.
*
* @since 6.2
*/
public abstract class ShadingMeshPattern extends ShadingPattern implements Pattern {
private static final Logger logger =
Logger.getLogger(ShadingMeshPattern.class.toString());
public static final Name BITS_PER_FLAG_KEY = new Name("BitsPerFlag");
public static final Name BITS_PER_COORDINATE_KEY = new Name("BitsPerCoordinate");
protected static final int DECODE_X_MIN = 0;
protected static final int DECODE_X_MAX = 1;
protected static final int DECODE_Y_MIN = 2;
protected static final int DECODE_Y_MAX = 3;
// (Required) The number of bits used to represent the edge flag for each vertex (see below). The value of
// BitsPerFlag shall be 2, 4, or 8, but only the least significant 2 bits in each flag value shall be used.
// The value for the edge flag shall be 0, 1, or 2.
protected int bitsPerFlag;
// (Required) The number of bits used to represent each vertex coordinate.
// The value shall be 1, 2, 4, 8, 12, 16, 24, or 32.
protected int bitsPerCoordinate;
// (Required) The number of bits used to represent each colour component.
// The value shall be 1, 2, 4, 8, 12, or 16.
protected int bitsPerComponent;
// colour space component count.
protected int colorSpaceCompCount;
// vertex data
protected BitStream vertexBitStream;
protected Stream meshDataStream;
// converted decode data to simply process later on, taken from our DecodeRasterOp class.
protected float[] decode;
public ShadingMeshPattern(Library l, HashMap h, Stream meshDataStream) {
super(l, h);
this.meshDataStream = meshDataStream;
shadingDictionary = meshDataStream.getEntries();
bitsPerFlag = library.getInt(shadingDictionary, BITS_PER_FLAG_KEY);
bitsPerCoordinate = library.getInt(shadingDictionary, BITS_PER_COORDINATE_KEY);
bitsPerComponent = library.getInt(shadingDictionary, ImageStream.BITSPERCOMPONENT_KEY);
colorSpace = PColorSpace.getColorSpace(library, library.getObject(shadingDictionary, COLORSPACE_KEY));
colorSpaceCompCount = colorSpace.getNumComponents();
// Function is optional and cannot be used with indexed colour models.
Object tmp = library.getObject(shadingDictionary, FUNCTION_KEY);
if (tmp != null) {
if (!(tmp instanceof java.util.List)) {
function = new Function[]{Function.getFunction(library,
tmp)};
} else {
java.util.List functionTemp = (java.util.List) tmp;
function = new Function[functionTemp.size()];
for (int i = 0; i < functionTemp.size(); i++) {
function[i] = Function.getFunction(library, functionTemp.get(i));
}
}
}
decode = processDecode();
vertexBitStream = new BitStream(meshDataStream.getDecodedByteArrayInputStream());
}
public abstract Paint getPaint() throws InterruptedException;
/**
* An array of numbers specifying how to map vertex coordinates and colour components into the
* appropriate ranges of values. The decoding method is similar to that used in image dictionaries
* (see 8.9.5.2, "Decode Arrays"). The ranges shall be specified as follows:
* [xmin xmax ymin ymax c1,min c1,max … cn,min cn,max]
* Only one pair of c values shall be specified if a Function entry is present.
*/
protected float[] processDecode() {
float[] decode = new float[6];
if (function == null) {
decode = new float[4 + 2 * colorSpaceCompCount];
}
java.util.List<Number> decodeVec = (java.util.List<Number>) library.getObject(shadingDictionary, ImageStream.DECODE_KEY);
float maxValue = bitsPerCoordinate < 32 ? (float) ((1 << bitsPerCoordinate) - 1) : (float) 2.3283064365386963e-10; // 2^-32;
for (int i = 0; i <= DECODE_Y_MAX; ) {
float Dmin = decodeVec.get(i).floatValue();
float Dmax = decodeVec.get(i + 1).floatValue();
decode[i++] = Dmin;
decode[i++] = (Dmax - Dmin) / maxValue;
}
maxValue = ((int) Math.pow(2, bitsPerComponent)) - 1;
for (int i = 4; i < decode.length; ) {
float Dmin = decodeVec.get(i).floatValue();
float Dmax = decodeVec.get(i + 1).floatValue();
decode[i++] = Dmin;
decode[i++] = (Dmax - Dmin) / maxValue;
}
return decode;
}
/**
* Reads the vertex descriptor flag, length of flag is defined by the bitsPerFlag dictionary entry.
*
* @return int value of the vertex flag.
* @throws IOException bit stream issue.
*/
protected int readFlag() throws IOException {
return vertexBitStream.getBits(bitsPerFlag);
}
/**
* Reads the vertex coordinate data, length of flag is defined by the bitsPerCoordinate dictionary entry.
*
* @return int value of the vertex coordinate.
* @throws IOException bit stream issue.
*/
protected Point2D.Float readCoord() throws IOException {
float x = vertexBitStream.getBits(bitsPerCoordinate);
float y = vertexBitStream.getBits(bitsPerCoordinate);
// normalize components to decode array
x *= decode[DECODE_X_MAX] - decode[DECODE_X_MIN] + decode[DECODE_X_MIN];
y *= decode[DECODE_Y_MAX] - decode[DECODE_Y_MIN] + decode[DECODE_Y_MIN];
return new Point2D.Float(x, y);
}
/**
* Reads the vertex colour data, length of flag is defined by the colorSpaceCompCount dictionary entry.
* Color data is generate using the function if present as well as the defined colour space.
*
* @return int value of the vertex colour.
* @throws IOException bit stream issue.
*/
protected Color readColor() throws IOException {
float[] primitives;
if (function == null) {
primitives = new float[colorSpaceCompCount];
for (int i = 0, j = 4; i < colorSpaceCompCount; i++, j += 2) {
primitives[i] = vertexBitStream.getBits(bitsPerComponent);
// normalize
primitives[i] *= decode[j + 1] - decode[j] + decode[j];
}
primitives = PColorSpace.reverse(primitives);
return colorSpace.getColor(primitives, true);
} else {
float value = vertexBitStream.getBits(bitsPerComponent);
// normalize
value *= (decode[5] - decode[4]) + decode[4];
primitives = new float[]{value};
float[] output = calculateValues(primitives);
if (output != null) {
output = PColorSpace.reverse(output);
return colorSpace.getColor(output, true);
}
}
return null;
}
}