/* * 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. */ /* $Id$ */ package org.apache.fop.afp.util; import java.awt.geom.Point2D; import java.awt.geom.Point2D.Double; /** * This class can be used to convert a cubic bezier curve within * a path into multiple quadratic bezier curves which will approximate * the original cubic curve. * The various techniques are described here: * http://www.timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm */ public final class CubicBezierApproximator { private CubicBezierApproximator() { } /** * This method will take in an array containing the x and y coordinates of the four control * points that describe the cubic bezier curve to be approximated using the fixed mid point * approximation. The curve will be approximated using four quadratic bezier curves the points * for which will be returned in a two dimensional array, with each array within that containing * the points for a single quadratic curve. The returned data will not include the start point * for any of the curves; the first point passed in to this method should already have been * set as the current position and will be the assumed start of the first curve. * * @param cubicControlPointCoords an array containing the x and y coordinates of the * four control points. * @return an array of arrays containing the x and y coordinates of the quadratic curves * that approximate the original supplied cubic bezier curve. */ public static double[][] fixedMidPointApproximation(double[] cubicControlPointCoords) { if (cubicControlPointCoords.length < 8) { throw new IllegalArgumentException("Must have at least 8 coordinates"); } //extract point objects from source array Point2D p0 = new Point2D.Double(cubicControlPointCoords[0], cubicControlPointCoords[1]); Point2D p1 = new Point2D.Double(cubicControlPointCoords[2], cubicControlPointCoords[3]); Point2D p2 = new Point2D.Double(cubicControlPointCoords[4], cubicControlPointCoords[5]); Point2D p3 = new Point2D.Double(cubicControlPointCoords[6], cubicControlPointCoords[7]); //calculates the useful base points Point2D pa = getPointOnSegment(p0, p1, 3.0 / 4.0); Point2D pb = getPointOnSegment(p3, p2, 3.0 / 4.0); //get 1/16 of the [P3, P0] segment double dx = (p3.getX() - p0.getX()) / 16.0; double dy = (p3.getY() - p0.getY()) / 16.0; //calculates control point 1 Point2D pc1 = getPointOnSegment(p0, p1, 3.0 / 8.0); //calculates control point 2 Point2D pc2 = getPointOnSegment(pa, pb, 3.0 / 8.0); pc2 = movePoint(pc2, -dx, -dy); //calculates control point 3 Point2D pc3 = getPointOnSegment(pb, pa, 3.0 / 8.0); pc3 = movePoint(pc3, dx, dy); //calculates control point 4 Point2D pc4 = getPointOnSegment(p3, p2, 3.0 / 8.0); //calculates the 3 anchor points Point2D pa1 = getMidPoint(pc1, pc2); Point2D pa2 = getMidPoint(pa, pb); Point2D pa3 = getMidPoint(pc3, pc4); //return the points for the four quadratic curves return new double[][] { {pc1.getX(), pc1.getY(), pa1.getX(), pa1.getY()}, {pc2.getX(), pc2.getY(), pa2.getX(), pa2.getY()}, {pc3.getX(), pc3.getY(), pa3.getX(), pa3.getY()}, {pc4.getX(), pc4.getY(), p3.getX(), p3.getY()}}; } private static Double movePoint(Point2D point, double dx, double dy) { return new Point2D.Double(point.getX() + dx, point.getY() + dy); } /** * This method will calculate the coordinates of a point half way along a segment [P0, P1] * * @param p0 - The point describing the start of the segment. * @param p1 - The point describing the end of the segment. * @return a Point object describing the coordinates of the calculated point on the segment. */ private static Point2D getMidPoint(Point2D p0, Point2D p1) { return getPointOnSegment(p0, p1, 0.5); } /** * This method will calculate the coordinates of a point on a segment [P0, P1] * whose distance along the segment [P0, P1] from P0, is the given ratio * of the length the [P0, P1] segment. * * @param p0 The point describing the start of the segment. * @param p1 The point describing the end of the segment. * @param ratio The distance of the point being calculated from P0 as a ratio of * the segment length. * @return a Point object describing the coordinates of the calculated point on the segment. */ private static Point2D getPointOnSegment(Point2D p0, Point2D p1, double ratio) { double x = p0.getX() + ((p1.getX() - p0.getX()) * ratio); double y = p0.getY() + ((p1.getY() - p0.getY()) * ratio); return new Point2D.Double(x, y); } }