/*
* Copyright 2014 The Apache Software Foundation.
*
* 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.apache.pdfbox.pdmodel.graphics.shading;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.HashSet;
import java.util.Set;
/**
* This is an assistant class for accomplishing type 4, 5, 6 and 7 shading. It
* describes a triangle actually, which is used to compose a patch. It contains
* the degenerated cases, a triangle degenerates to a line or to a point. This
* was done as part of GSoC2014, Tilman Hausherr is the mentor.
*
* @author Shaola Ren
*/
class ShadedTriangle
{
protected final Point2D[] corner; // vertices coordinates of a triangle
protected final float[][] color;
private final double area; // area of the triangle
/*
degree = 3 describes a normal triangle,
degree = 2 when a triangle degenerates to a line,
degree = 1 when a triangle degenerates to a point
*/
private final int degree;
// describes a rasterized line when a triangle degerates to a line, otherwise null
private final Line line;
// corner's edge (the opposite edge of a corner) equation value
private final double v0;
private final double v1;
private final double v2;
/**
* Constructor.
*
* @param p an array of the 3 vertices of a triangle
* @param c an array of color corresponding the vertex array p
*/
ShadedTriangle(Point2D[] p, float[][] c)
{
corner = p.clone();
color = c.clone();
area = getArea(p[0], p[1], p[2]);
degree = calcDeg(p);
if (degree == 2)
{
if (overlaps(corner[1], corner[2]) && !overlaps(corner[0], corner[2]))
{
Point p0 = new Point((int) Math.round(corner[0].getX()),
(int) Math.round(corner[0].getY()));
Point p1 = new Point((int) Math.round(corner[2].getX()),
(int) Math.round(corner[2].getY()));
line = new Line(p0, p1, color[0], color[2]);
}
else
{
Point p0 = new Point((int) Math.round(corner[1].getX()),
(int) Math.round(corner[1].getY()));
Point p1 = new Point((int) Math.round(corner[2].getX()),
(int) Math.round(corner[2].getY()));
line = new Line(p0, p1, color[1], color[2]);
}
}
else
{
line = null;
}
v0 = edgeEquationValue(p[0], p[1], p[2]);
v1 = edgeEquationValue(p[1], p[2], p[0]);
v2 = edgeEquationValue(p[2], p[0], p[1]);
}
/**
* Calculate the degree value of a triangle.
*
* @param p 3 vertices coordinates
* @return number of unique points in the 3 vertices of a triangle, 3, 2 or
* 1
*/
private int calcDeg(Point2D[] p)
{
Set<Point> set = new HashSet<>();
for (Point2D itp : p)
{
Point np = new Point((int) Math.round(itp.getX() * 1000), (int) Math.round(itp.getY() * 1000));
set.add(np);
}
return set.size();
}
public int getDeg()
{
return degree;
}
/**
* get the boundary of a triangle.
*
* @return {xmin, xmax, ymin, ymax}
*/
public int[] getBoundary()
{
int[] boundary = new int[4];
int x0 = (int) Math.round(corner[0].getX());
int x1 = (int) Math.round(corner[1].getX());
int x2 = (int) Math.round(corner[2].getX());
int y0 = (int) Math.round(corner[0].getY());
int y1 = (int) Math.round(corner[1].getY());
int y2 = (int) Math.round(corner[2].getY());
boundary[0] = Math.min(Math.min(x0, x1), x2);
boundary[1] = Math.max(Math.max(x0, x1), x2);
boundary[2] = Math.min(Math.min(y0, y1), y2);
boundary[3] = Math.max(Math.max(y0, y1), y2);
return boundary;
}
/**
* Get the line of a triangle.
*
* @return points of the line, or null if this triangle isn't a line
*/
public Line getLine()
{
return line;
}
/**
* Whether a point is contained in this ShadedTriangle.
*
* @param p the target point
* @return false if p is outside of this triangle, otherwise true
*/
public boolean contains(Point2D p)
{
if (degree == 1)
{
return overlaps(corner[0], p) | overlaps(corner[1], p) | overlaps(corner[2], p);
}
else if (degree == 2)
{
Point tp = new Point((int) Math.round(p.getX()), (int) Math.round(p.getY()));
return line.linePoints.contains(tp);
}
/*
the following code judges whether a point is contained in a normal triangle,
taking the on edge case as contained
*/
double pv0 = edgeEquationValue(p, corner[1], corner[2]);
/*
if corner[0] and point p are on different sides of line from corner[1] to corner[2],
p is outside of the triangle
*/
if (pv0 * v0 < 0)
{
return false;
}
double pv1 = edgeEquationValue(p, corner[2], corner[0]);
/*
if vertex corner[1] and point p are on different sides of line from corner[2] to corner[0],
p is outside of the triangle
*/
if (pv1 * v1 < 0)
{
return false;
}
double pv2 = edgeEquationValue(p, corner[0], corner[1]);
/*
only left one case:
if corner[1] and point p are on different sides of line from corner[2] to corner[0],
p is outside of the triangle, otherwise p is contained in the triangle
*/
return pv2 * v2 >= 0; // !(pv2 * v2 < 0)
}
/*
check whether two points overlaps each other, as points' coordinates are
of type double, the coordinates' accuracy used here is 0.001
*/
private boolean overlaps(Point2D p0, Point2D p1)
{
return Math.abs(p0.getX() - p1.getX()) < 0.001 && Math.abs(p0.getY() - p1.getY()) < 0.001;
}
/*
two points can define a line equation, adjust the form of the equation to
let the rhs equals 0, calculate the lhs value by plugging the coordinate
of p in the lhs expression
*/
private double edgeEquationValue(Point2D p, Point2D p1, Point2D p2)
{
return (p2.getY() - p1.getY()) * (p.getX() - p1.getX())
- (p2.getX() - p1.getX()) * (p.getY() - p1.getY());
}
// calcuate the area of a triangle
private double getArea(Point2D a, Point2D b, Point2D c)
{
return Math.abs((c.getX() - b.getX()) * (c.getY() - a.getY())
- (c.getX() - a.getX()) * (c.getY() - b.getY())) / 2.0;
}
/**
* Calculate the color of a point.
*
* @param p the target point
* @return an array denotes the point's color
*/
public float[] calcColor(Point2D p)
{
int numberOfColorComponents = color[0].length;
float[] pCol = new float[numberOfColorComponents];
switch (degree)
{
case 1:
for (int i = 0; i < numberOfColorComponents; i++)
{
// average
pCol[i] = (color[0][i] + color[1][i] + color[2][i]) / 3.0f;
}
break;
case 2:
// linear interpolation
Point tp = new Point((int) Math.round(p.getX()), (int) Math.round(p.getY()));
return line.calcColor(tp);
default:
float aw = (float) (getArea(p, corner[1], corner[2]) / area);
float bw = (float) (getArea(p, corner[2], corner[0]) / area);
float cw = (float) (getArea(p, corner[0], corner[1]) / area);
for (int i = 0; i < numberOfColorComponents; i++)
{
// barycentric interpolation
pCol[i] = color[0][i] * aw + color[1][i] * bw + color[2][i] * cw;
}
break;
}
return pCol;
}
@Override
public String toString()
{
return corner[0] + " " + corner[1] + " " + corner[2];
}
}