# TensorPatch.java

    /*
     * 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.geom.Point2D;
    import java.util.List;

    /**
     * This class is used to describe a patch for type 7 shading. This was done as
     * part of GSoC2014, Tilman Hausherr is the mentor.
     *
     * @author Shaola Ren
     */
    class TensorPatch extends Patch
    {
        /**
         * Constructor of a patch for type 7 shading.
         *
         * @param tcp 16 control points
         * @param color 4 corner colors
         */
        protected TensorPatch(Point2D[] tcp, float[][] color)
        {
            super(color);
            controlPoints = reshapeControlPoints(tcp);
            level = calcLevel();
            listOfTriangles = getTriangles();
        }

        /*
         order the 16 1d points to a square matrix which is as the one described
         in p.199 of PDF3200_2008.pdf rotated 90 degrees clockwise
         */
        private Point2D[][] reshapeControlPoints(Point2D[] tcp)
        {
            Point2D[][] square = new Point2D[4][4];
            for (int i = 0; i <= 3; i++)
            {
                square[0][i] = tcp[i];
                square[3][i] = tcp[9 - i];
            }
            for (int i = 1; i <= 2; i++)
            {
                square[i][0] = tcp[12 - i];
                square[i][2] = tcp[12 + i];
                square[i][3] = tcp[3 + i];
            }
            square[1][1] = tcp[12];
            square[2][1] = tcp[15];
            return square;
        }

        // calculate the dividing level from the control points
        private int[] calcLevel()
        {
            int[] l =
            {
                4, 4
            };

            Point2D[] ctlC1 = new Point2D[4];
            Point2D[] ctlC2 = new Point2D[4];
            for (int j = 0; j < 4; j++)
            {
                ctlC1[j] = controlPoints[j][0];
                ctlC2[j] = controlPoints[j][3];
            }
            // if two opposite edges are both lines, there is a possibility to reduce the dividing level
            if (isEdgeALine(ctlC1) && isEdgeALine(ctlC2))
            {
                /*
                 if any of the 4 inner control points is out of the patch formed by the 4 edges,
                 keep the high dividing level,
                 otherwise, determine the dividing level by the lengths of edges
                 */
                if (isOnSameSideCC(controlPoints[1][1]) || isOnSameSideCC(controlPoints[1][2])
                        || isOnSameSideCC(controlPoints[2][1]) || isOnSameSideCC(controlPoints[2][2]))
                {
                    // keep the high dividing level
                }
                else
                {
                    // length's unit is one pixel in device space
                    double lc1 = getLen(ctlC1[0], ctlC1[3]), lc2 = getLen(ctlC2[0], ctlC2[3]);
                    if (lc1 > 800 || lc2 > 800)
                    {
                        // keeps init value 4
                    }
                    else if (lc1 > 400 || lc2 > 400)
                    {
                        l[0] = 3;
                    }
                    else if (lc1 > 200 || lc2 > 200)
                    {
                        l[0] = 2;
                    }
                    else
                    {
                        l[0] = 1;
                    }
                }
            }

            // the other two opposite edges
            if (isEdgeALine(controlPoints[0]) && isEdgeALine(controlPoints[3]))
            {
                if (isOnSameSideDD(controlPoints[1][1]) || isOnSameSideDD(controlPoints[1][2])
                        || isOnSameSideDD(controlPoints[2][1]) || isOnSameSideDD(controlPoints[2][2]))
                {
                    // keep the high dividing level
                }
                else
                {
                    double ld1 = getLen(controlPoints[0][0], controlPoints[0][3]);
                    double ld2 = getLen(controlPoints[3][0], controlPoints[3][3]);
                    if (ld1 > 800 || ld2 > 800)
                    {
                        // keeps init value 4
                    }
                    else if (ld1 > 400 || ld2 > 400)
                    {
                        l[1] = 3;
                    }
                    else if (ld1 > 200 || ld2 > 200)
                    {
                        l[1] = 2;
                    }
                    else
                    {
                        l[1] = 1;
                    }
                }
            }
            return l;
        }

        // whether a point is on the same side of edge C1 and edge C2
        private boolean isOnSameSideCC(Point2D p)
        {
            double cc = edgeEquationValue(p, controlPoints[0][0], controlPoints[3][0])
                    * edgeEquationValue(p, controlPoints[0][3], controlPoints[3][3]);
            return cc > 0;
        }

        // whether a point is on the same side of edge D1 and edge D2
        private boolean isOnSameSideDD(Point2D p)
        {
            double dd = edgeEquationValue(p, controlPoints[0][0], controlPoints[0][3])
                    * edgeEquationValue(p, controlPoints[3][0], controlPoints[3][3]);
            return dd > 0;
        }

        // get a list of triangles which compose this tensor patch
        private List<ShadedTriangle> getTriangles()
        {
            CoordinateColorPair[][] patchCC = getPatchCoordinatesColor();
            return getShadedTriangles(patchCC);
        }

        @Override
        protected Point2D[] getFlag1Edge()
        {
            Point2D[] implicitEdge = new Point2D[4];
            for (int i = 0; i < 4; i++)
            {
                implicitEdge[i] = controlPoints[i][3];
            }
            return implicitEdge;
        }

        @Override
        protected Point2D[] getFlag2Edge()
        {
            Point2D[] implicitEdge = new Point2D[4];
            for (int i = 0; i < 4; i++)
            {
                implicitEdge[i] = controlPoints[3][3 - i];
            }
            return implicitEdge;
        }

        @Override
        protected Point2D[] getFlag3Edge()
        {
            Point2D[] implicitEdge = new Point2D[4];
            for (int i = 0; i < 4; i++)
            {
                implicitEdge[i] = controlPoints[3 - i][0];
            }
            return implicitEdge;
        }

        /*
         dividing a patch into a grid according to level, then calculate the coordinate and color of
         each crossing point in the grid, the rule to calculate the coordinate is tensor-product which
         is defined in page 119 of PDF32000_2008.pdf, the method to calculate the corresponding color is
         bilinear interpolation
         */
        private CoordinateColorPair[][] getPatchCoordinatesColor()
        {
            int numberOfColorComponents = cornerColor[0].length;
            double[][] bernsteinPolyU = getBernsteinPolynomials(level[0]);
            int szU = bernsteinPolyU[0].length;
            double[][] bernsteinPolyV = getBernsteinPolynomials(level[1]);
            int szV = bernsteinPolyV[0].length;
            CoordinateColorPair[][] patchCC = new CoordinateColorPair[szV][szU];

            double stepU = 1.0 / (szU - 1);
            double stepV = 1.0 / (szV - 1);
            double v = -stepV;
            for (int k = 0; k < szV; k++)
            {
                // v and u are the assistant parameters
                v += stepV;
                double u = -stepU;
                for (int l = 0; l < szU; l++)
                {
                    double tmpx = 0.0;
                    double tmpy = 0.0;
                    // these two "for" loops are for the equation to define the patch surface (coordinates)
                    for (int i = 0; i < 4; i++)
                    {
                        for (int j = 0; j < 4; j++)
                        {
                            tmpx += controlPoints[i][j].getX() * bernsteinPolyU[i][l] * bernsteinPolyV[j][k];
                            tmpy += controlPoints[i][j].getY() * bernsteinPolyU[i][l] * bernsteinPolyV[j][k];
                        }
                    }
                    Point2D tmpC = new Point2D.Double(tmpx, tmpy);

                    u += stepU;
                    float[] paramSC = new float[numberOfColorComponents];
                    for (int ci = 0; ci < numberOfColorComponents; ci++)
                    {
                        paramSC[ci] = (float) ((1 - v) * ((1 - u) * cornerColor[0][ci] + u * cornerColor[3][ci])
                                + v * ((1 - u) * cornerColor[1][ci] + u * cornerColor[2][ci])); // bilinear interpolation
                    }
                    patchCC[k][l] = new CoordinateColorPair(tmpC, paramSC);
                }
            }
            return patchCC;
        }

        // Bernstein polynomials which are defined in page 119 of PDF32000_2008.pdf
        private double[][] getBernsteinPolynomials(int lvl)
        {
            int sz = (1 << lvl) + 1;
            double[][] poly = new double[4][sz];
            double step = 1.0 / (sz - 1);
            double t = -step;
            for (int i = 0; i < sz; i++)
            {
                t += step;
                poly[0][i] = (1 - t) * (1 - t) * (1 - t);
                poly[1][i] = 3 * t * (1 - t) * (1 - t);
                poly[2][i] = 3 * t * t * (1 - t);
                poly[3][i] = t * t * t;
            }
            return poly;
        }
    }