├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── modules.xml ├── scopes │ └── scope_settings.xml └── vcs.xml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── build.gradle ├── example ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── me │ │ └── mattlogan │ │ └── artiste │ │ └── example │ │ ├── AssortedShapeView.java │ │ └── MainActivity.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ └── activity_main.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── github_assets └── artiste_shapes.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── mattlogan │ │ └── artiste │ │ └── PathsTest.java │ └── main │ ├── AndroidManifest.xml │ └── java │ └── me │ └── mattlogan │ └── artiste │ ├── MathUtils.java │ └── Paths.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | 8 | /example/example.iml 9 | /library/library.iml 10 | 11 | /Artiste.iml 12 | 13 | /.idea/misc.xml 14 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Artiste -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | Version 4.0.0 *(2015-9-15)* 5 | -------------------------- 6 | 7 | * Renamed Artiste class to Paths in accordance with Effective Java, Item 1 8 | * Removed telescoping methods with different parameters for Path creation 9 | * Moved majority of implementation into Paths class, with exception of small MathUtils 10 | * Upgraded Android dependencies 11 | * Enforced no instances and no subclasses for Paths and MathUtils 12 | 13 | Version 3.0.0 *(2015-2-22)* 14 | -------------------------- 15 | 16 | * Changed API to collection of static methods for creating Path objects 17 | 18 | Version 2.0.1 *(2015-1-20)* 19 | -------------------------- 20 | 21 | * Stripped AndroidManifest.xml to fix manifest merging conflicts 22 | * Added example module 23 | * Upgraded build tools to 2.1.2 24 | 25 | Version 2.0.0 *(2014-12-7)* 26 | -------------------------- 27 | 28 | * Big API change -- let Canvas do drawing, Shape only responsible for Path 29 | 30 | Version 1.1.4 *(2014-12-3)* 31 | -------------------------- 32 | 33 | * Prepare for jCenter release with fixes 34 | 35 | Version 1.1.4 *(2014-12-3)* 36 | -------------------------- 37 | 38 | * Prepare for jCenter release 39 | 40 | Version 1.1.3 *(2014-12-2)* 41 | -------------------------- 42 | 43 | * Improved intersection find algorithm in `RegularStarPolygon` 44 | 45 | Version 1.1.2 *(2014-12-1)* 46 | -------------------------- 47 | 48 | * Changed setRotation() parameter to float from int 49 | * Added offsets to path calculation for when `Rect` isn't at origin 50 | 51 | Version 1.1.1 *(2014-12-1)* 52 | -------------------------- 53 | 54 | * Marked some methods as final 55 | * Added `RegularConvexPolygonTest` and `RegularStarPolygonTest` 56 | * Added test in `ArtisteTest` 57 | 58 | Version 1.1.0 *(2014-12-1)* 59 | -------------------------- 60 | 61 | * Added `setRotation(int rotationDegrees)` to `Shape` 62 | * Added `setOutlined(boolean outlined)` to `RegularStarPolygon` 63 | 64 | Version 1.0.5 *(2014-11-30)* 65 | -------------------------- 66 | 67 | * Updated testOnCanvas() test in ArtisteTest 68 | 69 | Version 1.0.4 *(2014-11-30)* 70 | -------------------------- 71 | 72 | * Moved drawing responsibility into Shape with draw(Canvas canvas, Paint paint) 73 | * Added Circle to Shapes class 74 | 75 | Version 1.0.3 *(2014-11-30)* 76 | -------------------------- 77 | 78 | * Added Change Log 79 | 80 | Version 1.0.2 *(2014-11-29)* 81 | -------------------------- 82 | 83 | * Added `RegularStarPolygon` and `FivePointedStar` 84 | 85 | Version 1.0.1 *(2014-11-29)* 86 | -------------------------- 87 | 88 | * Changed API to use `setBounds(Rect rect)` instead of `inRect(Rect rect)` 89 | * Added unit tests for `Artiste` class 90 | 91 | 92 | Version 1.0.0 *(2014-11-29)* 93 | ---------------------------- 94 | 95 | * Initial release 96 | 97 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Matthew Logan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Artiste 2 | ======= 3 | 4 | Artiste is a collection of static methods for creating `Path` objects initialized with different shapes. 5 | 6 | 7 | 8 | ## Add as a dependency 9 | 10 | Artiste is on jCenter. 11 | 12 | ```groovy 13 | repositories { 14 | jcenter() 15 | } 16 | ``` 17 | 18 | ```groovy 19 | dependencies { 20 | compile 'me.mattlogan.artiste:artiste:4.0.0' 21 | } 22 | ``` 23 | 24 | ## Overview 25 | 26 | The `Paths` class contains the entirety of the public API for this library. It contains three static factory methods, including: 27 | 28 | 1. One method for creating a regular convex polygon. 29 | 2. One method for creating a regular star polygon. 30 | 3. One method for creating a circle. 31 | 32 | ## The API 33 | 34 | ```public static Path regularConvexPolygon(int left, int top, int right, int bottom, int numSides, float rotationDegrees)``` 35 | 36 | Creates a regular convex polygon Path. 37 | 38 | * **Parameters:** 39 | * `left` — Left bound 40 | * `top` — Top bound 41 | * `right` — Right bound 42 | * `bottom` — Bottom bound 43 | * `numSides` — Number of sides 44 | * `rotationDegrees` — Degrees to rotate polygon 45 | * **Returns:** A Path corresponding to a regular convex polygon. 46 | 47 | ```public static Path regularStarPolygon(int left, int top, int right, int bottom, int numPoints, int density, float rotationDegrees, boolean outline)``` 48 | 49 | Creates a regular star polygon Path. 50 | 51 | * **Parameters:** 52 | * `left` — Left bound 53 | * `top` — Top bound 54 | * `right` — Right bound 55 | * `bottom` — Bottom bound 56 | * `numPoints` — Number of points on star 57 | * `density` — Density of the star polygon (the number of vertices, or points, to skip when drawing a line connecting two vertices.) 58 | * `rotationDegrees` — Number of degrees to rotate star polygon 59 | * `outline` — True if only the star's outline should be drawn. If false, complete lines will be drawn connecting the star's vertices. 60 | * **Returns:** A Path corresponding to a regular star polygon. 61 | 62 | ```public static Path circle(int left, int top, int right, int bottom)``` 63 | 64 | Creates a circle Path. 65 | 66 | * **Parameters:** 67 | * `left` — Left bound 68 | * `top` — Top bound 69 | * `right` — Right bound 70 | * `bottom` — Bottom bound 71 | * **Returns:** A Path corresponding to a circle. 72 | 73 | ## Tests 74 | 75 | This library contains instrumentation tests in the directory `/library/src/androidTest/`. 76 | 77 | It's hard to be 100% sure, even with these tests, that the static factory methods in `Paths` are returning `Path` objects initialized with the correct shape. Some of these tests are a bit more implicit -- for example, making sure a pentagon has a larger perimeter than a square. Others just check for known values -- for example, that the perimeter of a square inscribed inside a circle with diameter 100 is 282.84. Finally, some tests check for exceptions that should be thrown for invalid arguments. 78 | 79 | In addition to the `library` module, there's also an `example` module with a small demo app. This is a quick way to verify that the library works as described for at least a few of the more common shapes you might encounter. The picture at the top of this readme is a cropped screenshot from this example app. 80 | 81 | ## License 82 | 83 | ``` 84 | The MIT License (MIT) 85 | 86 | Copyright (c) 2014 Matthew Logan 87 | 88 | Permission is hereby granted, free of charge, to any person obtaining a copy 89 | of this software and associated documentation files (the "Software"), to deal 90 | in the Software without restriction, including without limitation the rights 91 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 92 | copies of the Software, and to permit persons to whom the Software is 93 | furnished to do so, subject to the following conditions: 94 | 95 | The above copyright notice and this permission notice shall be included in all 96 | copies or substantial portions of the Software. 97 | 98 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 99 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 100 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 101 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 102 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 103 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 104 | SOFTWARE. 105 | ``` 106 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | applicationId "me.mattlogan.artiste" 9 | minSdkVersion 10 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | } 15 | 16 | dependencies { 17 | compile 'com.android.support:appcompat-v7:23.0.1' 18 | compile project(':library') 19 | } 20 | -------------------------------------------------------------------------------- /example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/src/main/java/me/mattlogan/artiste/example/AssortedShapeView.java: -------------------------------------------------------------------------------- 1 | package me.mattlogan.artiste.example; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import me.mattlogan.artiste.Paths; 11 | 12 | public class AssortedShapeView extends View { 13 | 14 | private Path hexagonPath; 15 | private Path fivePointedStarPath; 16 | private Path decagramPath; 17 | private Path circlePath; 18 | private Path octagramPath; 19 | private Path pentagonPath; 20 | 21 | private Paint hexagonPaint; 22 | private Paint fivePointedStarPaint; 23 | private Paint decagramPaint; 24 | private Paint circlePaint; 25 | private Paint octagramPaint; 26 | private Paint pentagonPaint; 27 | 28 | public AssortedShapeView(Context context) { 29 | super(context); 30 | initPaint(); 31 | } 32 | 33 | public AssortedShapeView(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | initPaint(); 36 | } 37 | 38 | public AssortedShapeView(Context context, AttributeSet attrs, int defStyleAttr) { 39 | super(context, attrs, defStyleAttr); 40 | initPaint(); 41 | } 42 | 43 | private void initPaint() { 44 | hexagonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 45 | hexagonPaint.setColor(0xFF0263ED); 46 | hexagonPaint.setStrokeWidth(3); 47 | hexagonPaint.setStyle(Paint.Style.STROKE); 48 | 49 | fivePointedStarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 50 | fivePointedStarPaint.setColor(0xFFD73F22); 51 | fivePointedStarPaint.setStrokeWidth(4); 52 | fivePointedStarPaint.setStyle(Paint.Style.STROKE); 53 | 54 | decagramPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 55 | decagramPaint.setColor(0xFFFFB900); 56 | decagramPaint.setStyle(Paint.Style.FILL); 57 | 58 | circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 59 | circlePaint.setColor(0xFF0263ED); 60 | circlePaint.setStyle(Paint.Style.FILL); 61 | 62 | octagramPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 63 | octagramPaint.setColor(0xFF009C55); 64 | octagramPaint.setStyle(Paint.Style.STROKE); 65 | octagramPaint.setStrokeWidth(4); 66 | 67 | pentagonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 68 | pentagonPaint.setColor(0xFFD73F22); 69 | pentagonPaint.setStyle(Paint.Style.FILL); 70 | } 71 | 72 | @Override 73 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 74 | super.onSizeChanged(w, h, oldw, oldh); 75 | hexagonPath = Paths.regularConvexPolygon(0, 0, h, h, 6, 25); 76 | fivePointedStarPath = Paths.regularStarPolygon(h, 0, 2 * h, h, 5, 2, 12, true); 77 | decagramPath = Paths.regularStarPolygon(2 * h, 0, 3 * h, h, 10, 3, 18, false); 78 | circlePath = Paths.circle(3 * h, 0, 4 * h, h); 79 | octagramPath = Paths.regularStarPolygon(4 * h, 0, 5 * h, h, 8, 3, 22.5f, false); 80 | pentagonPath = Paths.regularConvexPolygon(5 * h, 0, 6 * h, h, 5, 50); 81 | } 82 | 83 | @Override 84 | protected void onDraw(Canvas canvas) { 85 | super.onDraw(canvas); 86 | canvas.drawPath(hexagonPath, hexagonPaint); 87 | canvas.drawPath(fivePointedStarPath, fivePointedStarPaint); 88 | canvas.drawPath(decagramPath, decagramPaint); 89 | canvas.drawPath(circlePath, circlePaint); 90 | canvas.drawPath(octagramPath, octagramPaint); 91 | canvas.drawPath(pentagonPath, pentagonPaint); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /example/src/main/java/me/mattlogan/artiste/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.mattlogan.artiste.example; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattlogan/Artiste/eb0912a5ec403b3716a43c7d9579fe25c46eb68e/example/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattlogan/Artiste/eb0912a5ec403b3716a43c7d9579fe25c46eb68e/example/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattlogan/Artiste/eb0912a5ec403b3716a43c7d9579fe25c46eb68e/example/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattlogan/Artiste/eb0912a5ec403b3716a43c7d9579fe25c46eb68e/example/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /example/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /example/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artiste 5 | 6 | -------------------------------------------------------------------------------- /example/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /github_assets/artiste_shapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattlogan/Artiste/eb0912a5ec403b3716a43c7d9579fe25c46eb68e/github_assets/artiste_shapes.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattlogan/Artiste/eb0912a5ec403b3716a43c7d9579fe25c46eb68e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Dec 07 09:44:10 PST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | ext { 4 | PUBLISH_GROUP_ID = 'me.mattlogan.artiste' 5 | PUBLISH_ARTIFACT_ID = 'artiste' 6 | PUBLISH_VERSION = '4.0.0' 7 | } 8 | 9 | android { 10 | compileSdkVersion 23 11 | buildToolsVersion "23.0.1" 12 | 13 | defaultConfig { 14 | minSdkVersion 10 15 | targetSdkVersion 23 16 | versionCode 4 17 | versionName "4.0.0" 18 | 19 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 20 | } 21 | } 22 | 23 | dependencies { 24 | androidTestCompile 'com.android.support.test:runner:0.4' 25 | } 26 | 27 | apply from: 'https://raw.githubusercontent.com/mattlogan/release-android-library/master/android-release-aar.gradle' 28 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/mlogan/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/androidTest/java/me/mattlogan/artiste/PathsTest.java: -------------------------------------------------------------------------------- 1 | package me.mattlogan.artiste; 2 | 3 | import android.graphics.Path; 4 | import android.graphics.PathMeasure; 5 | import android.support.test.runner.AndroidJUnit4; 6 | import android.test.suitebuilder.annotation.SmallTest; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | /** 15 | * It's hard to be 100% sure, even with these tests, that the static factory methods in Paths 16 | * are returning Paths with the correct shape. Some of these tests are a bit more implicit -- for 17 | * example, making sure a pentagon has a larger perimeter than a square. Others just check for 18 | * known values -- for example, that the perimeter of a square inscribed inside a circle with 19 | * diameter 100 is 282.84. Finally, some tests check for exceptions that should be thrown for 20 | * invalid arguments. 21 | */ 22 | @RunWith(AndroidJUnit4.class) 23 | @SmallTest 24 | public class PathsTest { 25 | 26 | /* 27 | This is an easy one. In a square of side lengths 100, the diameter of the inscribed circle 28 | is 100. So the perimeter of our circle should be roughly 100 * 3.14 = 314. 29 | */ 30 | @Test 31 | public void testCircle() { 32 | Path path = Paths.circle(0, 0, 100, 100); 33 | PathMeasure pm = new PathMeasure(path, false); 34 | assertEquals(314, pm.getLength(), 1); 35 | } 36 | 37 | /* 38 | Non-square bounds should throw. 39 | */ 40 | @Test(expected = IllegalArgumentException.class) 41 | public void testCircleThrowsWithNonSquareBounds() { 42 | Paths.circle(0, 0, 100, 99); 43 | } 44 | 45 | /* 46 | Check the perimeter of a regular convex polygon. 47 | */ 48 | @Test 49 | public void testRegularConvexPolygonPerimeter() { 50 | Path path = Paths.regularConvexPolygon(0, 0, 100, 100, 4, 0); 51 | PathMeasure pm = new PathMeasure(path, false); 52 | 53 | // Perimeter of square inscribed in circle with diameter 100 54 | float expectedPerimeter = 282.84f; 55 | 56 | assertEquals(expectedPerimeter, pm.getLength(), 0.01f); 57 | } 58 | 59 | /* 60 | Make sure that rotation has no effect on the perimeter of a regular convex polygon. 61 | */ 62 | @Test 63 | public void testRegularConvexPolygonPerimeterIsRotationIndependent() { 64 | Path path1 = Paths.regularConvexPolygon(0, 0, 100, 100, 5, 0); 65 | PathMeasure pm1 = new PathMeasure(path1, false); 66 | 67 | Path path2 = Paths.regularConvexPolygon(0, 0, 100, 100, 5, 60); 68 | PathMeasure pm2 = new PathMeasure(path2, false); 69 | 70 | assertEquals(pm1.getLength(), pm2.getLength(), 0); 71 | } 72 | 73 | /* 74 | Check relative magnitudes of regular convex polygon perimeters. For example, a pentagon should 75 | have a slightly larger perimeter than a square if both are inscribed in the same circle. 76 | */ 77 | @Test 78 | public void testRelativePerimetersOfRegularConvexPolygons() { 79 | Path path1 = Paths.regularConvexPolygon(0, 0, 100, 100, 6, 0); 80 | PathMeasure pm1 = new PathMeasure(path1, false); 81 | 82 | Path path2 = Paths.regularConvexPolygon(0, 0, 100, 100, 7, 0); 83 | PathMeasure pm2 = new PathMeasure(path2, false); 84 | 85 | assertTrue(pm2.getLength() > pm1.getLength()); 86 | } 87 | 88 | /* 89 | Non-square bounds should throw. 90 | */ 91 | @Test(expected = IllegalArgumentException.class) 92 | public void testRegularConvexPolygonThrowsWithNonSquareBounds() { 93 | Paths.regularConvexPolygon(0, 0, 100, 99, 4, 0); 94 | } 95 | 96 | /* 97 | Num sides less than 3 should throw. 98 | */ 99 | @Test(expected = IllegalArgumentException.class) 100 | public void testRegularConvexPolygonThrowsWithLessThanThreeSides() { 101 | Paths.regularConvexPolygon(0, 0, 100, 99, 2, 0); 102 | } 103 | 104 | /* 105 | Check the perimeter of an outlined regular convex polygon. 106 | */ 107 | @Test 108 | public void testRegularStarPolygonOutlinePerimeter() { 109 | Path path = Paths.regularStarPolygon(0, 0, 100, 100, 5, 2, 0, true); 110 | PathMeasure pm = new PathMeasure(path, false); 111 | 112 | // Perimeter of outlined five pointed star inscribed in circle with diameter 100 113 | float expectedPerimeter = 363.27f; 114 | 115 | assertEquals(expectedPerimeter, pm.getLength(), 0.01f); 116 | } 117 | 118 | /* 119 | Check the perimeter of a regular convex polygon without outline. 120 | */ 121 | @Test 122 | public void testRegularStarPolygonNoOutlinePerimeter() { 123 | Path path = Paths.regularStarPolygon(0, 0, 100, 100, 5, 2, 0, false); 124 | PathMeasure pm = new PathMeasure(path, false); 125 | 126 | // Perimeter of non-outlined five pointed star inscribed in circle with diameter 100 127 | float expectedPerimeter = 475.53f; 128 | 129 | assertEquals(expectedPerimeter, pm.getLength(), 0.01f); 130 | } 131 | 132 | /* 133 | Make sure that rotation has no effect on the perimeter of a regular star polygon. 134 | */ 135 | @Test 136 | public void testRegularStarPolygonOutlinePerimeterIsRotationIndependent() { 137 | Path path1 = Paths.regularStarPolygon(0, 0, 100, 100, 5, 2, 0, true); 138 | PathMeasure pm1 = new PathMeasure(path1, false); 139 | 140 | Path path2 = Paths.regularStarPolygon(0, 0, 100, 100, 5, 2, 60, true); 141 | PathMeasure pm2 = new PathMeasure(path2, false); 142 | 143 | assertEquals(pm1.getLength(), pm2.getLength(), 0); 144 | } 145 | 146 | /* 147 | Make sure that rotation has no effect on the perimeter of a regular star polygon. 148 | */ 149 | @Test 150 | public void testRegularStarPolygonNoOutlinePerimeterIsRotationIndependent() { 151 | Path path1 = Paths.regularStarPolygon(0, 0, 100, 100, 5, 2, 0, false); 152 | PathMeasure pm1 = new PathMeasure(path1, false); 153 | 154 | Path path2 = Paths.regularStarPolygon(0, 0, 100, 100, 5, 2, 60, false); 155 | PathMeasure pm2 = new PathMeasure(path2, false); 156 | 157 | assertEquals(pm1.getLength(), pm2.getLength(), 0); 158 | } 159 | 160 | /* 161 | Check relative magnitudes of regular star polygon perimeters. For example, an eight pointed 162 | star should have a larger perimeter than a five pointed star if both are inscribed in the same 163 | circle. 164 | */ 165 | @Test 166 | public void testRelativePerimetersOfRegularStarPolygons() { 167 | Path path1 = Paths.regularStarPolygon(0, 0, 100, 100, 5, 2, 0, true); 168 | PathMeasure pm1 = new PathMeasure(path1, false); 169 | 170 | Path path2 = Paths.regularStarPolygon(0, 0, 100, 100, 8, 3, 0, true); 171 | PathMeasure pm2 = new PathMeasure(path2, false); 172 | 173 | assertTrue(pm2.getLength() > pm1.getLength()); 174 | } 175 | 176 | /* 177 | Non-square bounds should throw. 178 | */ 179 | @Test(expected = IllegalArgumentException.class) 180 | public void testRegularStarPolygonThrowsWithNonSquareBounds() { 181 | Paths.regularStarPolygon(0, 0, 100, 99, 5, 2, 0, true); 182 | } 183 | 184 | /* 185 | Num points less than 5 should throw. 186 | */ 187 | @Test(expected = IllegalArgumentException.class) 188 | public void testRegularStarPolygonThrowsWithLessThanThreePoints() { 189 | Paths.regularStarPolygon(0, 0, 100, 100, 4, 2, 0, true); 190 | } 191 | 192 | /* 193 | Density less than 2 should throw. 194 | */ 195 | @Test(expected = IllegalArgumentException.class) 196 | public void testRegularStarPolygonThrowsWithDensityLessThanTwo() { 197 | Paths.regularStarPolygon(0, 0, 100, 100, 5, 1, 0, true); 198 | } 199 | 200 | /* 201 | While a six pointed star can exist, it can't be drawn with one continuous line, so it's 202 | outside the scope of this project (for now). 203 | */ 204 | @Test(expected = IllegalStateException.class) 205 | public void testRegularStarPolygonThrowsForInvalidShape() { 206 | Paths.regularStarPolygon(0, 0, 100, 100, 6, 2, 0, true); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /library/src/main/java/me/mattlogan/artiste/MathUtils.java: -------------------------------------------------------------------------------- 1 | package me.mattlogan.artiste; 2 | 3 | import static java.lang.Math.pow; 4 | import static java.lang.Math.sqrt; 5 | 6 | final class MathUtils { 7 | 8 | private MathUtils() { 9 | throw new AssertionError("No instances allowed"); 10 | } 11 | 12 | static float slope(float[] point1, float[] point2) { 13 | return (point2[1] - point1[1]) / (point2[0] - point1[0]); 14 | } 15 | 16 | static float distance(float x1, float y1, float x2, float y2) { 17 | return (float) sqrt(pow(y2 - y1, 2) + pow(x2 - x1, 2)); 18 | } 19 | 20 | static float yIntercept(float[] point, float slope) { 21 | return point[1] - slope * point[0]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /library/src/main/java/me/mattlogan/artiste/Paths.java: -------------------------------------------------------------------------------- 1 | package me.mattlogan.artiste; 2 | 3 | import android.graphics.Path; 4 | import android.graphics.RectF; 5 | 6 | import static java.lang.Math.cos; 7 | import static java.lang.Math.max; 8 | import static java.lang.Math.min; 9 | import static java.lang.Math.sin; 10 | import static java.lang.Math.toRadians; 11 | import static me.mattlogan.artiste.MathUtils.*; 12 | 13 | public final class Paths { 14 | 15 | private Paths() { 16 | throw new AssertionError("No instances allowed"); 17 | } 18 | 19 | /** 20 | * Creates a regular convex polygon {@link android.graphics.Path}. 21 | * 22 | * @param left Left bound 23 | * @param top Top bound 24 | * @param right Right bound 25 | * @param bottom Bottom bound 26 | * @param numSides Number of sides 27 | * @param rotationDegrees Degrees to rotate polygon 28 | * @return A {@link android.graphics.Path} corresponding to a regular convex polygon. 29 | */ 30 | public static Path regularConvexPolygon(int left, int top, int right, int bottom, 31 | int numSides, float rotationDegrees) { 32 | 33 | if (right - left != bottom - top) { 34 | throw new IllegalArgumentException("Provided bounds (" + left + ", " + top + ", " + 35 | right + ", " + bottom + ") must be square."); 36 | } 37 | if (numSides < 3) { 38 | throw new IllegalArgumentException("Number of sides must be at least 3."); 39 | } 40 | 41 | float radius = (right - left) / 2f; 42 | 43 | float degreesBetweenPoints = 360f / numSides; 44 | 45 | // Add 90 so first point is top 46 | float baseRotation = 90 + rotationDegrees; 47 | 48 | // Assume we want a point of the polygon at the top unless otherwise set 49 | float startDegrees = numSides % 2 != 0 ? 50 | baseRotation : baseRotation + degreesBetweenPoints / 2f; 51 | 52 | Path path = new Path(); 53 | 54 | for (int i = 0; i <= numSides; i++) { 55 | double theta = toRadians(startDegrees + i * degreesBetweenPoints); 56 | 57 | float x = (float) (left + radius + (radius * cos(theta))); 58 | float y = (float) (top + radius - (radius * sin(theta))); 59 | 60 | if (i == 0) { 61 | path.moveTo(x, y); 62 | } else { 63 | path.lineTo(x, y); 64 | } 65 | } 66 | 67 | return path; 68 | } 69 | 70 | /** 71 | * Creates a regular star polygon {@link android.graphics.Path}. 72 | * 73 | * @param left Left bound 74 | * @param top Top bound 75 | * @param right Right bound 76 | * @param bottom Bottom bound 77 | * @param numPoints Number of points on star 78 | * @param density Density of the star polygon (the number of vertices, or points, to 79 | * skip when drawing a line connecting two vertices.) 80 | * @param rotationDegrees Number of degrees to rotate star polygon 81 | * @param outline True if only the star's outline should be drawn. If false, complete 82 | * lines will be drawn connecting the star's vertices. 83 | * @return A {@link android.graphics.Path} corresponding to a regular star polygon. 84 | */ 85 | public static Path regularStarPolygon(int left, int top, int right, int bottom, 86 | int numPoints, int density, float rotationDegrees, 87 | boolean outline) { 88 | 89 | if (right - left != bottom - top) { 90 | throw new IllegalArgumentException("Provided bounds (" + left + ", " + top + ", " + 91 | right + ", " + bottom + ") must be square."); 92 | } 93 | if (numPoints < 5) { 94 | throw new IllegalArgumentException("Number of points must be at least 5"); 95 | } 96 | if (density < 2) { 97 | throw new IllegalArgumentException("Density must be at least 2"); 98 | } 99 | 100 | float outerRadius = (right - left) / 2f; 101 | 102 | // Add 90 so first point is top 103 | float startDegrees = 90 + rotationDegrees; 104 | 105 | float degreesBetweenPoints = 360f / numPoints; 106 | 107 | float[][] outerPointsArray = new float[numPoints][2]; 108 | for (int i = 0; i < numPoints; i++) { 109 | double theta = toRadians(startDegrees + density * i * degreesBetweenPoints); 110 | outerPointsArray[i][0] = (float) (outerRadius + (outerRadius * cos(theta))); 111 | outerPointsArray[i][1] = (float) (outerRadius - (outerRadius * sin(theta))); 112 | } 113 | 114 | float[][] pointsForPath; 115 | 116 | if (outline) { 117 | // Find the first intersection point created by drawing each line in the star 118 | float[] firstIntersection = null; 119 | 120 | float[] firstPt1 = outerPointsArray[0]; 121 | float[] firstPt2 = outerPointsArray[1]; 122 | 123 | float firstSlope = slope(firstPt1, firstPt2); 124 | float firstYInt = yIntercept(firstPt1, firstSlope); 125 | 126 | // Ranges for first line. We'll use these later to check if the intersection we find 127 | // is in the valid range. 128 | float firstLowX = min(firstPt1[0], firstPt2[0]); 129 | float firstHighX = max(firstPt1[0], firstPt2[0]); 130 | float firstLowY = min(firstPt1[0], firstPt2[1]); 131 | float firstHighY = max(firstPt1[1], firstPt2[1]); 132 | 133 | // The second line and the last line can't intersect the first line. Skip them. 134 | for (int i = 2; i < outerPointsArray.length - 1; i++) { 135 | float[] curPt1 = outerPointsArray[i]; 136 | float[] curPt2 = outerPointsArray[i + 1]; 137 | 138 | float curSlope = slope(curPt1, curPt2); 139 | float curYInt = curPt1[1] - curSlope * curPt1[0]; 140 | 141 | // System of equations. Two equations, two unknowns. 142 | // y = firstSlope * x + firstYInt 143 | // y = curSlope * x + curYInt 144 | 145 | // Solve for x and y in terms of known quantities. 146 | // firstSlope * x + firstYInt = curSlope * x + curYInt 147 | // firstSlope * x - curSlope * x = curYInt - firstYInt 148 | // x * (firstSlope - curSlope) = (curYInt - firstYInt) 149 | // x = (curYInt - firstYInt) / (firstSlope - curSlope) 150 | // y = firstSlope * x + firstYInt 151 | 152 | if (firstSlope == curSlope) { 153 | // lines can't intersect if they are parallel 154 | continue; 155 | } 156 | 157 | float intersectionX = (curYInt - firstYInt) / (firstSlope - curSlope); 158 | float intersectionY = firstSlope * intersectionX + firstYInt; 159 | 160 | // Ranges for current line. 161 | float curLowX = min(curPt1[0], curPt2[0]); 162 | float curHighX = max(curPt1[0], curPt2[0]); 163 | float curLowY = min(curPt1[0], curPt2[1]); 164 | float curHighY = max(curPt1[1], curPt2[1]); 165 | 166 | // Range where intersection has to be. 167 | float startX = max(firstLowX, curLowX); 168 | float endX = min(firstHighX, curHighX); 169 | float startY = max(firstLowY, curLowY); 170 | float endY = min(firstHighY, curHighY); 171 | 172 | if (intersectionX > startX && intersectionX < endX && 173 | intersectionY > startY && intersectionY < endY) { 174 | // Found intersection. 175 | firstIntersection = new float[]{intersectionX, intersectionY}; 176 | } 177 | } 178 | 179 | if (firstIntersection == null) { 180 | // If there are no intersections, it's not a star polygon. 181 | throw new IllegalStateException("Failed to calculate path." + 182 | "Are the number of points and density valid?"); 183 | } 184 | 185 | // Use the first intersection point to find the radius of the inner circle of the star 186 | float innerRadius = distance(outerRadius, outerRadius, firstIntersection[0], 187 | firstIntersection[1]); 188 | 189 | // There are now twice as many points to "line to" in our path 190 | numPoints *= 2; 191 | 192 | // Recalculate degrees between points 193 | degreesBetweenPoints = 360f / numPoints; 194 | 195 | pointsForPath = new float[numPoints][2]; 196 | 197 | for (int i = 0; i < numPoints; i++) { 198 | double theta = toRadians(startDegrees + i * degreesBetweenPoints); 199 | float radius = i % 2 == 0 ? outerRadius : innerRadius; 200 | 201 | pointsForPath[i][0] = (float) (outerRadius + (radius * cos(theta))); 202 | pointsForPath[i][1] = (float) (outerRadius - (radius * sin(theta))); 203 | } 204 | } else { 205 | pointsForPath = outerPointsArray; 206 | } 207 | 208 | // Make the Path from whatever points array we're using -- outline or not 209 | Path path = new Path(); 210 | for (int i = 0; i < pointsForPath.length; i++) { 211 | float x = left + pointsForPath[i][0]; 212 | float y = top + pointsForPath[i][1]; 213 | 214 | if (i == 0) { 215 | path.moveTo(x, y); 216 | } else { 217 | path.lineTo(x, y); 218 | } 219 | } 220 | path.lineTo(left + pointsForPath[0][0], top + pointsForPath[0][1]); 221 | 222 | return path; 223 | } 224 | 225 | /** 226 | * Creates a circle {@link android.graphics.Path}. 227 | * 228 | * @param left Left bound 229 | * @param top Top bound 230 | * @param right Right bound 231 | * @param bottom Bottom bound 232 | * @return A {@link android.graphics.Path} corresponding to a circle. 233 | */ 234 | public static Path circle(int left, int top, int right, int bottom) { 235 | if (right - left != bottom - top) { 236 | throw new IllegalArgumentException("Provided bounds (" + left + ", " + top + ", " + 237 | right + ", " + bottom + ") must be square."); 238 | } 239 | 240 | Path path = new Path(); 241 | // sweep angle is mod 360, so we can't actually use 360. 242 | path.arcTo(new RectF(left, top, right, bottom), 0, 359.9999f); 243 | return path; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library', ':example' 2 | --------------------------------------------------------------------------------