Android examples for Graphics:Canvas
Draws a circular arc on the given Canvas .
/**// www . j av a 2 s . c o m * ArcUtils.java * * Copyright (c) 2014 BioWink GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. **/ //package com.java2s; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import static java.lang.Math.abs; import static java.lang.Math.ceil; import static java.lang.Math.cos; import static java.lang.Math.floor; import static java.lang.Math.sin; import static java.lang.Math.sqrt; import static java.lang.Math.toRadians; public class Main { private static final double FULL_CIRCLE_RADIANS = toRadians(360d); /** * Draws a circular arc on the given {@code Canvas}. * * @param canvas The canvas to draw into. * @param circleCenter The center of the circle on which to draw the arc. * @param circleRadius The radius of the circle on which to draw the arc. * @param startAngle Starting angle (in degrees) where the arc begins. * @param sweepAngle Sweep angle (in degrees) measured clockwise. * @param paint The paint to use then drawing the arc. * * @see #drawArc(Canvas, PointF, float, float, float, Paint, int, boolean) */ public static void drawArc(@NonNull Canvas canvas, PointF circleCenter, float circleRadius, float startAngle, float sweepAngle, @NonNull Paint paint) { drawArc(canvas, circleCenter, circleRadius, startAngle, sweepAngle, paint, 8, false); } /** * Draws a circular arc on the given {@code Canvas}. * * @param canvas The canvas to draw into. * @param circleCenter The center of the circle on which to draw the arc. * @param circleRadius The radius of the circle on which to draw the arc. * @param startAngle Starting angle (in degrees) where the arc begins. * @param sweepAngle Sweep angle (in degrees) measured clockwise. * @param paint The paint to use then drawing the arc. * @param arcsPointsOnCircle See {@link #createBezierArcDegrees(PointF, float, float, float, int, boolean, Path)}. * @param arcsOverlayPoints See {@link #createBezierArcDegrees(PointF, float, float, float, int, boolean, Path)}. * * @see #drawArc(Canvas, PointF, float, float, float, Paint) */ public static void drawArc(@NonNull Canvas canvas, PointF circleCenter, float circleRadius, float startAngle, float sweepAngle, @NonNull Paint paint, int arcsPointsOnCircle, boolean arcsOverlayPoints) { if (sweepAngle == 0f) { final PointF p = pointFromAngleDegrees(circleCenter, circleRadius, startAngle); canvas.drawPoint(p.x, p.y, paint); } else { canvas.drawPath( createBezierArcDegrees(circleCenter, circleRadius, startAngle, sweepAngle, arcsPointsOnCircle, arcsOverlayPoints, null), paint); } } /** * Returns the point of a given angle (in degrees) on a circle. * * @param center The center of the circle. * @param radius The radius of the circle. * @param angleDegrees The angle (in degrees). * * @return The point of the given angle on the specified circle. * * @see #pointFromAngleRadians(PointF, float, double) */ @NonNull public static PointF pointFromAngleDegrees(@NonNull PointF center, float radius, float angleDegrees) { return pointFromAngleRadians(center, radius, toRadians(angleDegrees)); } /** * Adds a circular arc to the given path by approximating it through a cubic B?er curve, splitting it if * necessary. The precision of the approximation can be adjusted through {@code pointsOnCircle} and * {@code overlapPoints} parameters. * <p> * <strong>Example:</strong> imagine an arc starting from 0? and sweeping 100? with a value of * {@code pointsOnCircle} equal to 12 (threshold -> 360? / 12 = 30?): * <ul> * <li>if {@code overlapPoints} is {@code true}, it will be split as following: * <ul> * <li>from 0? to 30? (sweep 30?)</li> * <li>from 30? to 60? (sweep 30?)</li> * <li>from 60? to 90? (sweep 30?)</li> * <li>from 90? to 100? (sweep 10?)</li> * </ul> * </li> * <li>if {@code overlapPoints} is {@code false}, it will be split into 4 equal arcs: * <ul> * <li>from 0? to 25? (sweep 25?)</li> * <li>from 25? to 50? (sweep 25?)</li> * <li>from 50? to 75? (sweep 25?)</li> * <li>from 75? to 100? (sweep 25?)</li> * </ul> * </li> * </ul> * </p> * <p/> * For a technical explanation: * <a href="http://hansmuller-flex.blogspot.de/2011/10/more-about-approximating-circular-arcs.html"> * http://hansmuller-flex.blogspot.de/2011/10/more-about-approximating-circular-arcs.html * </a> * * @param center The center of the circle. * @param radius The radius of the circle. * @param startAngleDegrees The starting angle on the circle (in degrees). * @param sweepAngleDegrees How long to make the total arc (in degrees). * @param pointsOnCircle Defines a <i>threshold</i> (360? /{@code pointsOnCircle}) to split the B?er arc to * better approximate a circular arc, depending also on the value of {@code overlapPoints}. * The suggested number to have a reasonable approximation of a circle is at least 4 (90?). * Less than 1 will ignored (the arc will not be split). * @param overlapPoints Given the <i>threshold</i> defined through {@code pointsOnCircle}: * <ul> * <li>if {@code true}, split the arc on every angle which is a multiple of the * <i>threshold</i> (yields better results if drawing precision is required, * especially when stacking multiple arcs, but can potentially use more points)</li> * <li>if {@code false}, split the arc equally so that each part is shorter than * the <i>threshold</i></li> * </ul> * @param addToPath An existing path where to add the arc to, or {@code null} to create a new path. * * @return {@code addToPath} if it's not {@code null}, otherwise a new path. * * @see #createBezierArcRadians(PointF, float, double, double, int, boolean, Path) */ @NonNull public static Path createBezierArcDegrees(@NonNull PointF center, float radius, float startAngleDegrees, float sweepAngleDegrees, int pointsOnCircle, boolean overlapPoints, @Nullable Path addToPath) { return createBezierArcRadians(center, radius, toRadians(startAngleDegrees), toRadians(sweepAngleDegrees), pointsOnCircle, overlapPoints, addToPath); } /** * Returns the point of a given angle (in radians) on a circle. * * @param center The center of the circle. * @param radius The radius of the circle. * @param angleRadians The angle (in radians). * * @return The point of the given angle on the specified circle. * * @see #pointFromAngleDegrees(PointF, float, float) */ @NonNull public static PointF pointFromAngleRadians(@NonNull PointF center, float radius, double angleRadians) { return new PointF((float) (center.x + radius * cos(angleRadians)), (float) (center.y + radius * sin(angleRadians))); } /** * Adds a circular arc to the given path by approximating it through a cubic B?er curve, splitting it if * necessary. The precision of the approximation can be adjusted through {@code pointsOnCircle} and * {@code overlapPoints} parameters. * <p> * <strong>Example:</strong> imagine an arc starting from 0? and sweeping 100? with a value of * {@code pointsOnCircle} equal to 12 (threshold -> 360? / 12 = 30?): * <ul> * <li>if {@code overlapPoints} is {@code true}, it will be split as following: * <ul> * <li>from 0? to 30? (sweep 30?)</li> * <li>from 30? to 60? (sweep 30?)</li> * <li>from 60? to 90? (sweep 30?)</li> * <li>from 90? to 100? (sweep 10?)</li> * </ul> * </li> * <li>if {@code overlapPoints} is {@code false}, it will be split into 4 equal arcs: * <ul> * <li>from 0? to 25? (sweep 25?)</li> * <li>from 25? to 50? (sweep 25?)</li> * <li>from 50? to 75? (sweep 25?)</li> * <li>from 75? to 100? (sweep 25?)</li> * </ul> * </li> * </ul> * </p> * <p/> * For a technical explanation: * <a href="http://hansmuller-flex.blogspot.de/2011/10/more-about-approximating-circular-arcs.html"> * http://hansmuller-flex.blogspot.de/2011/10/more-about-approximating-circular-arcs.html * </a> * * @param center The center of the circle. * @param radius The radius of the circle. * @param startAngleRadians The starting angle on the circle (in radians). * @param sweepAngleRadians How long to make the total arc (in radians). * @param pointsOnCircle Defines a <i>threshold</i> (360? /{@code pointsOnCircle}) to split the B?er arc to * better approximate a circular arc, depending also on the value of {@code overlapPoints}. * The suggested number to have a reasonable approximation of a circle is at least 4 (90?). * Less than 1 will be ignored (the arc will not be split). * @param overlapPoints Given the <i>threshold</i> defined through {@code pointsOnCircle}: * <ul> * <li>if {@code true}, split the arc on every angle which is a multiple of the * <i>threshold</i> (yields better results if drawing precision is required, * especially when stacking multiple arcs, but can potentially use more points)</li> * <li>if {@code false}, split the arc equally so that each part is shorter than * the <i>threshold</i></li> * </ul> * @param addToPath An existing path where to add the arc to, or {@code null} to create a new path. * * @return {@code addToPath} if it's not {@code null}, otherwise a new path. * * @see #createBezierArcDegrees(PointF, float, float, float, int, boolean, Path) */ @NonNull public static Path createBezierArcRadians(@NonNull PointF center, float radius, double startAngleRadians, double sweepAngleRadians, int pointsOnCircle, boolean overlapPoints, @Nullable Path addToPath) { final Path path = addToPath != null ? addToPath : new Path(); if (sweepAngleRadians == 0d) { return path; } if (pointsOnCircle >= 1) { final double threshold = FULL_CIRCLE_RADIANS / pointsOnCircle; if (abs(sweepAngleRadians) > threshold) { double angle = normalizeRadians(startAngleRadians); PointF end, start = pointFromAngleRadians(center, radius, angle); path.moveTo(start.x, start.y); if (overlapPoints) { final boolean cw = sweepAngleRadians > 0; // clockwise? final double angleEnd = angle + sweepAngleRadians; while (true) { double next = (cw ? ceil(angle / threshold) : floor(angle / threshold)) * threshold; if (angle == next) { next += threshold * (cw ? 1d : -1d); } final boolean isEnd = cw ? angleEnd <= next : angleEnd >= next; end = pointFromAngleRadians(center, radius, isEnd ? angleEnd : next); addBezierArcToPath(path, center, start, end, false); if (isEnd) { break; } angle = next; start = end; } } else { final int n = abs((int) ceil(sweepAngleRadians / threshold)); final double sweep = sweepAngleRadians / n; for (int i = 0; i < n; i++, start = end) { angle += sweep; end = pointFromAngleRadians(center, radius, angle); addBezierArcToPath(path, center, start, end, false); } } return path; } } final PointF start = pointFromAngleRadians(center, radius, startAngleRadians); final PointF end = pointFromAngleRadians(center, radius, startAngleRadians + sweepAngleRadians); addBezierArcToPath(path, center, start, end, true); return path; } /** * Normalize the input radians in the range 360? > x >= 0?. * * @param radians The angle to normalize (in radians). * * @return The angle normalized in the range 360? > x >= 0?. */ public static double normalizeRadians(double radians) { radians %= FULL_CIRCLE_RADIANS; if (radians < 0d) { radians += FULL_CIRCLE_RADIANS; } if (radians == FULL_CIRCLE_RADIANS) { radians = 0d; } return radians; } /** * Adds a circular arc to the given path by approximating it through a cubic B?er curve. * <p/> * <p> * Note that this <strong>does not</strong> split the arc to better approximate it, for that see either: * <ul> * <li>{@link #createBezierArcDegrees(PointF, float, float, float, int, boolean, * Path)}</li> * <li>{@link #createBezierArcRadians(PointF, float, double, double, int, boolean, * Path)}</li> * </ul> * </p> * <p/> * For a technical explanation: * <a href="http://hansmuller-flex.blogspot.de/2011/10/more-about-approximating-circular-arcs.html"> * http://hansmuller-flex.blogspot.de/2011/10/more-about-approximating-circular-arcs.html * </a> * * @param path The path to add the arc to. * @param center The center of the circle. * @param start The starting point of the arc on the circle. * @param end The ending point of the arc on the circle. * @param moveToStart If {@code true}, move to the starting point of the arc * (see: {@link Path#moveTo(float, float)}). * * @see #createBezierArcDegrees(PointF, float, float, float, int, boolean, Path) * @see #createBezierArcRadians(PointF, float, double, double, int, boolean, Path) */ public static void addBezierArcToPath(@NonNull Path path, @NonNull PointF center, @NonNull PointF start, @NonNull PointF end, boolean moveToStart) { if (moveToStart) { path.moveTo(start.x, start.y); } if (start.equals(end)) { return; } final double ax = start.x - center.x; final double ay = start.y - center.y; final double bx = end.x - center.x; final double by = end.y - center.y; final double q1 = ax * ax + ay * ay; final double q2 = q1 + ax * bx + ay * by; final double k2 = 4d / 3d * (sqrt(2d * q1 * q2) - q2) / (ax * by - ay * bx); final float x2 = (float) (center.x + ax - k2 * ay); final float y2 = (float) (center.y + ay + k2 * ax); final float x3 = (float) (center.x + bx + k2 * by); final float y3 = (float) (center.y + by - k2 * bx); path.cubicTo(x2, y2, x3, y3, end.x, end.y); } }