PDMeshBasedShadingType.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.pdfbox.pdmodel.graphics.shading;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.pdmodel.common.PDRange;
import org.apache.pdfbox.util.Matrix;
/**
* Common resources for shading types 6 and 7
*/
abstract class PDMeshBasedShadingType extends PDShadingType4
{
private static final Log LOG = LogFactory.getLog(PDMeshBasedShadingType.class);
PDMeshBasedShadingType(COSDictionary shadingDictionary)
{
super(shadingDictionary);
}
/**
* Create a patch list from a data stream, the returned list contains all the patches contained in the data stream.
*
* @param xform transformation for user to device space
* @param matrix the pattern matrix concatenated with that of the parent content stream
* @param controlPoints number of control points, 12 for type 6 shading and 16 for type 7 shading
* @return the obtained patch list
* @throws IOException when something went wrong
*/
@SuppressWarnings({ "squid:S2583", "squid:S1166" })
final List<Patch> collectPatches(AffineTransform xform, Matrix matrix, int controlPoints)
throws IOException
{
COSDictionary dict = getCOSObject();
if (!(dict instanceof COSStream))
{
return Collections.emptyList();
}
PDRange rangeX = getDecodeForParameter(0);
PDRange rangeY = getDecodeForParameter(1);
if (rangeX == null || rangeY == null ||
Float.compare(rangeX.getMin(), rangeX.getMax()) == 0 ||
Float.compare(rangeY.getMin(), rangeY.getMax()) == 0)
{
return Collections.emptyList();
}
int bitsPerFlag = getBitsPerFlag();
PDRange[] colRange = new PDRange[getNumberOfColorComponents()];
for (int i = 0; i < colRange.length; ++i)
{
colRange[i] = getDecodeForParameter(2 + i);
if (colRange[i] == null)
{
throw new IOException("Range missing in shading /Decode entry");
}
}
List<Patch> list = new ArrayList<>();
long maxSrcCoord = (long) Math.pow(2, getBitsPerCoordinate()) - 1;
long maxSrcColor = (long) Math.pow(2, getBitsPerComponent()) - 1;
COSStream cosStream = (COSStream) dict;
try (ImageInputStream mciis = new MemoryCacheImageInputStream(
cosStream.createInputStream()))
{
Point2D[] implicitEdge = new Point2D[4];
float[][] implicitCornerColor = new float[2][colRange.length];
byte flag = 0;
try
{
flag = (byte) (mciis.readBits(bitsPerFlag) & 3);
}
catch (EOFException ex)
{
LOG.error(ex);
return list;
}
boolean eof = false;
while (!eof)
{
try
{
boolean isFree = (flag == 0);
Patch current = readPatch(mciis, isFree, implicitEdge, implicitCornerColor,
maxSrcCoord, maxSrcColor, rangeX, rangeY, colRange, matrix, xform,
controlPoints);
if (current == null)
{
break;
}
list.add(current);
flag = (byte) (mciis.readBits(bitsPerFlag) & 3);
switch (flag)
{
case 0:
break;
case 1:
implicitEdge = current.getFlag1Edge();
implicitCornerColor = current.getFlag1Color();
break;
case 2:
implicitEdge = current.getFlag2Edge();
implicitCornerColor = current.getFlag2Color();
break;
case 3:
implicitEdge = current.getFlag3Edge();
implicitCornerColor = current.getFlag3Color();
break;
default:
LOG.warn("bad flag: " + flag);
break;
}
}
catch (EOFException ex)
{
eof = true;
}
}
}
return list;
}
/**
* Read a single patch from a data stream, a patch contains information of its coordinates and color parameters.
*
* @param input the image source data stream
* @param isFree whether this is a free patch
* @param implicitEdge implicit edge when a patch is not free, otherwise it's not used
* @param implicitCornerColor implicit colors when a patch is not free, otherwise it's not used
* @param maxSrcCoord the maximum coordinate value calculated from source data
* @param maxSrcColor the maximum color value calculated from source data
* @param rangeX range for coordinate x
* @param rangeY range for coordinate y
* @param colRange range for color
* @param matrix the pattern matrix concatenated with that of the parent content stream
* @param xform transformation for user to device space
* @param controlPoints number of control points, 12 for type 6 shading and 16 for type 7 shading
* @return a single patch
* @throws IOException when something went wrong
*/
protected Patch readPatch(ImageInputStream input, boolean isFree, Point2D[] implicitEdge,
float[][] implicitCornerColor, long maxSrcCoord, long maxSrcColor, PDRange rangeX,
PDRange rangeY, PDRange[] colRange, Matrix matrix, AffineTransform xform,
int controlPoints) throws IOException
{
int numberOfColorComponents = getNumberOfColorComponents();
float[][] color = new float[4][numberOfColorComponents];
Point2D[] points = new Point2D[controlPoints];
int pStart = 4;
int cStart = 2;
if (isFree)
{
pStart = 0;
cStart = 0;
}
else
{
points[0] = implicitEdge[0];
points[1] = implicitEdge[1];
points[2] = implicitEdge[2];
points[3] = implicitEdge[3];
for (int i = 0; i < numberOfColorComponents; i++)
{
color[0][i] = implicitCornerColor[0][i];
color[1][i] = implicitCornerColor[1][i];
}
}
try
{
for (int i = pStart; i < controlPoints; i++)
{
long x = input.readBits(getBitsPerCoordinate());
long y = input.readBits(getBitsPerCoordinate());
float px = interpolate(x, maxSrcCoord, rangeX.getMin(), rangeX.getMax());
float py = interpolate(y, maxSrcCoord, rangeY.getMin(), rangeY.getMax());
Point2D p = matrix.transformPoint(px, py);
xform.transform(p, p);
points[i] = p;
}
for (int i = cStart; i < 4; i++)
{
for (int j = 0; j < numberOfColorComponents; j++)
{
long c = input.readBits(getBitsPerComponent());
color[i][j] = interpolate(c, maxSrcColor, colRange[j].getMin(),
colRange[j].getMax());
}
}
}
catch (EOFException ex)
{
LOG.debug("EOF", ex);
return null;
}
return generatePatch(points, color);
}
/**
* Create a patch using control points and 4 corner color values, in Type6ShadingContext, a CoonsPatch is returned;
* in Type6ShadingContext, a TensorPatch is returned.
*
* @param points 12 or 16 control points
* @param color 4 corner colors
* @return a patch instance
*/
abstract Patch generatePatch(Point2D[] points, float[][] color);
@Override
public abstract Rectangle2D getBounds(AffineTransform xform, Matrix matrix) throws IOException;
Rectangle2D getBounds(AffineTransform xform, Matrix matrix, int controlPoints)
throws IOException
{
Rectangle2D bounds = null;
for (Patch patch : collectPatches(xform, matrix, controlPoints))
{
for (ShadedTriangle shadedTriangle : patch.listOfTriangles)
{
if (bounds == null)
{
bounds = new Rectangle2D.Double(shadedTriangle.corner[0].getX(),
shadedTriangle.corner[0].getY(), 0, 0);
}
bounds.add(shadedTriangle.corner[0]);
bounds.add(shadedTriangle.corner[1]);
bounds.add(shadedTriangle.corner[2]);
}
}
return bounds;
}
}