mvn install should do the job. It will install the library in your local repository. You can also retrieve the jar file under the target directory (after the maven build).
16 |
17 | The library is published on maven central to ease developers' life. So to use the library in a maven build:
18 | <dependency>
19 | <groupId>com.orange.dgil.trail</groupId>
20 | <artifactId>trail-core-lib</artifactId>
21 | <version>1.2.3</version>
22 | <scope>compile</scope>
23 | </dependency>
24 |
25 |
26 | In a gradle build:
27 | dependencies {
28 | compile "com.orange.dgil.trail:trail-core-lib:1.2.3"
29 | }
30 |
31 |
32 | # Demo applications
33 | Can be found in the "demo" directory: android and javafx apps. Basically the javafx application is used to test and debug algorithms.
34 | Above gestures where produced with the android "DroidTest" demo, whose prebuilt apk is here (click on "Raw" to save file): [Demo apk](https://github.com/Orange-OpenSource/trail-drawing/blob/master/demo/DroidDrawingTest.apk)
35 |
36 | Both demo apps use gradle for the build.
37 |
38 | For "DroidTest", ./gradlew assembleDebug will build the apk.
39 |
40 | For "JavaFxTest", ./gradlew run will build and launch the application. Be sure to use oracle java 8 jvm here.
41 |
42 | Happy testing!
43 |
44 | # License
45 | Copyright (C) 2014 Orange
46 |
47 | The trail drawing library is distributed under the terms and conditions of the Mozilla Public License v2.0, https://www.mozilla.org/MPL/2.0
48 |
49 | # Authors
50 | The library is born in 2014 at the Orange Labs in Meylan (french Alps). If Christophe Maldivi and Eric Petit practice cliff climbing together, they also sometimes write software together :)
51 |
--------------------------------------------------------------------------------
/src/main/java/com/orange/dgil/trail/android/animation/AnimRunnable.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Trail drawing library
3 | * Copyright (C) 2014 Orange
4 | * Authors: christophe.maldivi@orange.com, eric.petit@orange.com
5 | *
6 | * This Source Code Form is subject to the terms of the Mozilla Public
7 | * License, v. 2.0. If a copy of the MPL was not distributed with this
8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 | */
10 | package com.orange.dgil.trail.android.animation;
11 |
12 | import android.view.View;
13 | import android.view.animation.AnimationUtils;
14 |
15 | import lombok.Getter;
16 |
17 | class AnimRunnable implements Runnable {
18 |
19 | private final IAnimDrawer drawer;
20 | private final AnimParameters animParameters;
21 |
22 | @Getter
23 | private boolean running;
24 | @Getter
25 | private float factor;
26 |
27 | private long startTime;
28 |
29 |
30 | AnimRunnable(IAnimDrawer drawer, AnimParameters animParameters) {
31 | this.drawer = drawer;
32 | this.animParameters = animParameters;
33 | }
34 |
35 | void start() {
36 | reset();
37 | setStartTime(animParameters.getPreAnimDelay());
38 | running = true;
39 | drawer.invalidatePath();
40 | updateAfter(animParameters.getPreAnimDelay());
41 | }
42 |
43 | void updateAfter(int delayMillis) {
44 | View v = drawer.getView();
45 | if (delayMillis == 0) {
46 | v.post(this);
47 | } else {
48 | v.postDelayed(this, delayMillis);
49 | }
50 | }
51 |
52 | void reset() {
53 | factor = 1f;
54 | running = false;
55 | drawer.getView().removeCallbacks(this);
56 | }
57 |
58 | void setStartTime(int preAnimDelay) {
59 | startTime = getCurrentTime() + preAnimDelay;
60 | }
61 |
62 | long getRemainingDuration() {
63 | long remainingDuration = startTime + animParameters.getAnimDuration() - getCurrentTime();
64 | return Math.max(remainingDuration, 0);
65 | }
66 |
67 | // not tested, android stuff
68 | private long getCurrentTime() {
69 | return AnimationUtils.currentAnimationTimeMillis();
70 | }
71 |
72 |
73 | @Override
74 | public void run() {
75 | if (running) {
76 | doRun();
77 | }
78 | drawer.invalidatePath();
79 | }
80 |
81 | void doRun() {
82 | long remainingDuration = getRemainingDuration();
83 | if (remainingDuration > 0) {
84 | update(remainingDuration);
85 | } else {
86 | end();
87 | }
88 | }
89 |
90 | void end() {
91 | reset();
92 | drawer.animationFinished();
93 | }
94 |
95 | void update(long remainingDuration) {
96 | factor = getTimeFactor(remainingDuration);
97 | updateAfter(0);
98 | }
99 |
100 | float getTimeFactor(long remainingDuration) {
101 | return remainingDuration / (float) animParameters.getAnimDuration();
102 | }
103 | }
--------------------------------------------------------------------------------
/src/test/java/com/orange/dgil/trail/core/common/TrailPointTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Trail drawing library
3 | * Copyright (C) 2014 Orange
4 | * Authors: christophe.maldivi@orange.com, eric.petit@orange.com
5 | *
6 | * This Source Code Form is subject to the terms of the Mozilla Public
7 | * License, v. 2.0. If a copy of the MPL was not distributed with this
8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 | */
10 | package com.orange.dgil.trail.core.common;
11 |
12 | import org.junit.Assert;
13 | import org.junit.Test;
14 |
15 | public class TrailPointTest {
16 |
17 | @Test
18 | public void shouldInstantiateWithDefaultValues() {
19 | // given
20 | // do
21 | TrailPoint p = new TrailPoint();
22 | Assert.assertEquals(-1, p.getX());
23 | Assert.assertEquals(-1, p.getY());
24 | }
25 |
26 | @Test
27 | public void shouldInstantiateWithGivenValues() {
28 | // given
29 | int x = 12;
30 | int y = 34;
31 | // do
32 | TrailPoint p = new TrailPoint(x, y);
33 | Assert.assertEquals(x, p.getX());
34 | Assert.assertEquals(y, p.getY());
35 | }
36 |
37 | @Test
38 | public void shouldSetAndGet() {
39 | // given
40 | int x = 12;
41 | int y = 34;
42 | TrailPoint p = new TrailPoint();
43 | // do
44 | p.set(x, y);
45 | // then
46 | Assert.assertEquals(x, p.getX());
47 | Assert.assertEquals(y, p.getY());
48 | }
49 |
50 | @Test
51 | public void shouldHandleEqualPoints() {
52 | // given
53 | int x = 12;
54 | int y = 34;
55 | TrailPoint p1 = new TrailPoint();
56 | TrailPoint p2 = new TrailPoint();
57 | // do
58 | p1.set(x, y);
59 | p2.set(x, y);
60 | // then
61 | Assert.assertTrue(p1.isSameAs(p2));
62 | }
63 |
64 | @Test
65 | public void shouldHandleNotEqualPointsX() {
66 | // given
67 | int x = 12;
68 | int y = 34;
69 | TrailPoint p1 = new TrailPoint();
70 | TrailPoint p2 = new TrailPoint();
71 | // do
72 | p1.set(x, y);
73 | p2.set(x+1, y);
74 | // then
75 | Assert.assertFalse(p1.isSameAs(p2));
76 | }
77 |
78 | @Test
79 | public void shouldHandleNotEqualPointsY() {
80 | // given
81 | int x = 12;
82 | int y = 34;
83 | TrailPoint p1 = new TrailPoint();
84 | TrailPoint p2 = new TrailPoint();
85 | // do
86 | p1.set(x, y);
87 | p2.set(x, y+1);
88 | // then
89 | Assert.assertFalse(p1.isSameAs(p2));
90 | }
91 |
92 | @Test
93 | public void shouldDeepCopy() {
94 | // given
95 | int x = 12;
96 | int y = 34;
97 | TrailPoint p1 = new TrailPoint();
98 | p1.set(x, y);
99 | TrailPoint p2 = new TrailPoint();
100 | // do
101 | p2.deepCopy(p1);
102 | // then
103 | Assert.assertEquals(x, p2.getX());
104 | Assert.assertEquals(y, p2.getY());
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/test/java/com/orange/dgil/trail/core/vecto/linearwindowfilter/LinearWindowFilterGetMeanXYTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Trail drawing library
3 | * Copyright (C) 2014 Orange
4 | * Authors: christophe.maldivi@orange.com, eric.petit@orange.com
5 | *
6 | * This Source Code Form is subject to the terms of the Mozilla Public
7 | * License, v. 2.0. If a copy of the MPL was not distributed with this
8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 | */
10 | package com.orange.dgil.trail.core.vecto.linearwindowfilter;
11 |
12 | import com.orange.dgil.trail.TestTools;
13 | import com.orange.dgil.trail.core.common.TrailPoint;
14 | import com.orange.dgil.trail.core.vecto.SlidingWindow;
15 |
16 | import org.junit.Assert;
17 | import org.junit.Before;
18 | import org.junit.Test;
19 | import org.mockito.Mockito;
20 |
21 | public class LinearWindowFilterGetMeanXYTest {
22 |
23 | private LinearWindowFilter filter;
24 |
25 | @Before
26 | public void setUp() {
27 | filter = Mockito.mock(LinearWindowFilter.class);
28 | }
29 |
30 | @Test
31 | public void shouldGetMeanX() throws IllegalAccessException {
32 | // given
33 | int[] windowWeights = {1, 2, 1};
34 | TestTools.setObj("windowWeights", LinearWindowFilter.class, filter, windowWeights);
35 | TestTools.setObj("weightsSum", LinearWindowFilter.class, filter, 4);
36 |
37 | SlidingWindow slidingWindow = new SlidingWindow(windowWeights.length);
38 | TestTools.setObj("slidingWindow", LinearWindowFilter.class, filter, slidingWindow);
39 |
40 | TrailPoint p0 = new TrailPoint();
41 | TrailPoint p1 = new TrailPoint();
42 | TrailPoint p2 = new TrailPoint();
43 | p0.set(2, 10);
44 | p1.set(5, 20);
45 | p2.set(4, 18);
46 | slidingWindow.add(p0);
47 | slidingWindow.add(p1);
48 | slidingWindow.add(p2);
49 |
50 | Mockito.doCallRealMethod().when(filter).getMeanX();
51 | // do
52 | int res = filter.getMeanX();
53 | // then
54 | Assert.assertEquals(4, res);
55 | }
56 |
57 | @Test
58 | public void shouldGetMeanY() throws IllegalAccessException {
59 | // given
60 | int[] windowWeights = {1, 2, 1};
61 | TestTools.setObj("windowWeights", LinearWindowFilter.class, filter, windowWeights);
62 | TestTools.setObj("weightsSum", LinearWindowFilter.class, filter, 4);
63 |
64 | SlidingWindow slidingWindow = new SlidingWindow(windowWeights.length);
65 | TestTools.setObj("slidingWindow", LinearWindowFilter.class, filter, slidingWindow);
66 |
67 | TrailPoint p0 = new TrailPoint();
68 | TrailPoint p1 = new TrailPoint();
69 | TrailPoint p2 = new TrailPoint();
70 | p0.set(2, 10);
71 | p1.set(5, 20);
72 | p2.set(4, 18);
73 | slidingWindow.add(p0);
74 | slidingWindow.add(p1);
75 | slidingWindow.add(p2);
76 |
77 | Mockito.doCallRealMethod().when(filter).getMeanY();
78 | // do
79 | int res = filter.getMeanY();
80 | // then
81 | Assert.assertEquals(17, res);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/orange/dgil/trail/core/vecto/linearwindowfilter/LinearWindowFilter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Trail drawing library
3 | * Copyright (C) 2014 Orange
4 | * Authors: christophe.maldivi@orange.com, eric.petit@orange.com
5 | *
6 | * This Source Code Form is subject to the terms of the Mozilla Public
7 | * License, v. 2.0. If a copy of the MPL was not distributed with this
8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 | */
10 | package com.orange.dgil.trail.core.vecto.linearwindowfilter;
11 |
12 | import com.orange.dgil.trail.core.common.TrailPoint;
13 | import com.orange.dgil.trail.core.vecto.SlidingWindow;
14 |
15 | public class LinearWindowFilter {
16 |
17 | /** window elements weight */
18 | private final int[] windowWeights = {1, 2, 1};
19 |
20 | /** window elements */
21 | private final SlidingWindow slidingWindow = new SlidingWindow(windowWeights.length);
22 |
23 | private final LinearWindowFilterListener linearWindowFilterListener;
24 |
25 | private int weightsSum;
26 |
27 |
28 | public LinearWindowFilter(LinearWindowFilterListener linearWindowFilterListener) {
29 | this.linearWindowFilterListener = linearWindowFilterListener;
30 | initProperties();
31 | }
32 |
33 | void initProperties() {
34 | weightsSum = getWeightsSum();
35 | }
36 |
37 | int getWeightsSum() {
38 | int sum = 0;
39 | for (int weight : windowWeights) {
40 | sum+= weight;
41 | }
42 | return sum;
43 | }
44 |
45 |
46 | public void reset() {
47 | slidingWindow.reset();
48 | }
49 |
50 | public TrailPoint getLastPoint() {
51 | return slidingWindow.getLastElement();
52 | }
53 |
54 | public void addPoint(TrailPoint point) {
55 | if (!slidingWindow.isSameAsLast(point)) {
56 | doAddPoint(point);
57 | }
58 | }
59 |
60 | void doAddPoint(TrailPoint point) {
61 | slidingWindow.add(point);
62 | if (isNewPointAvailable()) {
63 | computeNewPointAndNotifyListener();
64 | }
65 | }
66 |
67 | boolean isNewPointAvailable() {
68 | return slidingWindow.getAddedElementsNumber() == 1 || slidingWindow.isFull();
69 | }
70 |
71 | void computeNewPointAndNotifyListener() {
72 | int x;
73 | int y;
74 | if (slidingWindow.getAddedElementsNumber() == 1) {
75 | x = slidingWindow.getX(0);
76 | y = slidingWindow.getY(0);
77 | } else {
78 | x = getMeanX();
79 | y = getMeanY();
80 | }
81 | linearWindowFilterListener.onNewFilteredPointAvailable(x, y);
82 | }
83 |
84 | int getMeanX() {
85 | int sumX = 0;
86 | for (int i = 0; i < windowWeights.length; i++) {
87 | sumX += slidingWindow.getX(i) * windowWeights[i];
88 | }
89 | return (sumX + weightsSum/2) / weightsSum;
90 | }
91 | int getMeanY() {
92 | int sumY = 0;
93 | for (int i = 0; i < windowWeights.length; i++) {
94 | sumY += slidingWindow.getY(i) * windowWeights[i];
95 | }
96 | return (sumY + weightsSum/2) / weightsSum;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/demo/JavaFxTest/src/main/java/com/orange/dgil/trail/app/fxdrawertest/FxDrawer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Trail drawing library
3 | * Copyright (C) 2014 Orange
4 | * Authors: christophe.maldivi@orange.com, eric.petit@orange.com
5 | *
6 | * This Source Code Form is subject to the terms of the Mozilla Public
7 | * License, v. 2.0. If a copy of the MPL was not distributed with this
8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 | */
10 | package com.orange.dgil.trail.app.fxdrawertest;
11 |
12 | import java.io.IOException;
13 |
14 | import javafx.application.Application;
15 | import javafx.event.EventHandler;
16 | import javafx.fxml.FXMLLoader;
17 | import javafx.scene.Parent;
18 | import javafx.scene.Scene;
19 | import javafx.scene.canvas.Canvas;
20 | import javafx.scene.canvas.GraphicsContext;
21 | import javafx.scene.input.MouseEvent;
22 | import javafx.scene.paint.Color;
23 | import javafx.stage.Stage;
24 |
25 | public class FxDrawer extends Application {
26 |
27 | @Override
28 | public void start(Stage primaryStage) throws IOException {
29 |
30 | Canvas canvas = new Canvas(400, 400);
31 | final GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
32 | initDraw(graphicsContext);
33 |
34 | canvas.addEventHandler(MouseEvent.MOUSE_PRESSED,
35 | new EventHandler6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | package com.orange.dgil.trail.core.quad; 11 | 12 | import com.orange.dgil.trail.core.common.TrailPoint; 13 | import com.orange.dgil.trail.core.vecto.SlidingWindow; 14 | import lombok.AccessLevel; 15 | import lombok.Getter; 16 | import lombok.Setter; 17 | 18 | /** 19 | * Polynomial approximation of degree 2, for the polygon P0 -- P1 -- P2 20 | *
21 | * Description: 22 | * x = A + B*t + C*t^2 23 | * y = D + E*t + F*t^2 with t in [O, 1] 24 | * t=0 -> p0 ; t=1 -> p2 25 | * 26 | * at p0, the curve is tangent with P0P1 27 | * at p2, the curve is tangent with P2P1 28 | */ 29 | class QuadInterpolator { 30 | private static final int QUAD_INTERPOLATION_DENSITY = 1; 31 | private static final int NB_POINTS_FOR_INTERP = 3; 32 | 33 | @Getter(AccessLevel.PACKAGE) 34 | private final QuadDat quadDat = new QuadDat(); 35 | @Getter(AccessLevel.PACKAGE) 36 | private final QuadCurveArray quadCurveArray; 37 | 38 | private final SlidingWindow slidingWindow = new SlidingWindow(NB_POINTS_FOR_INTERP); 39 | 40 | @Setter 41 | private int density = QUAD_INTERPOLATION_DENSITY; 42 | 43 | 44 | QuadInterpolator(boolean longCurve) { 45 | quadCurveArray = new QuadCurveArray(longCurve); 46 | } 47 | 48 | 49 | void reset() { 50 | slidingWindow.reset(); 51 | quadCurveArray.reset(); 52 | quadDat.reset(); 53 | } 54 | 55 | void newPointAdded(int x, int y) { 56 | slidingWindow.add(x, y); 57 | if (hasSufficientPoints()) { 58 | doInterpolation(); 59 | } 60 | } 61 | 62 | void lastPointAdded(int x, int y) { 63 | quadDat.setCurveEnd(true); 64 | newPointAdded(x, y); 65 | } 66 | 67 | void lastPointAddedPreviously() { 68 | quadDat.setCurveEnd(true); 69 | if (hasSufficientPoints()) { 70 | doInterpolation(); 71 | } 72 | } 73 | 74 | private boolean hasSufficientPoints() { 75 | return slidingWindow.isFull(); 76 | } 77 | 78 | private void doInterpolation() { 79 | quadDat.setPoint0(slidingWindow.getElementAt(0)); 80 | quadDat.setPoint1(slidingWindow.getElementAt(1)); 81 | quadDat.setPoint2(slidingWindow.getElementAt(2)); 82 | interpolate(); 83 | quadDat.setCurveStart(false); 84 | } 85 | 86 | 87 | void interpolate() { 88 | updateInterpStartAndEndPoints(); 89 | rawInterpolate(); 90 | handleCurveEnd(); 91 | } 92 | 93 | private void updateInterpStartAndEndPoints() { 94 | updateInterpStartEndWithMiddles(); 95 | if (quadDat.isCurveStart()) { 96 | quadDat.getInterpStartPoint().deepCopy(quadDat.getPoint0()); 97 | } 98 | if (quadDat.isCurveEnd()) { 99 | quadDat.getInterpEndPoint().deepCopy(quadDat.getPoint2()); 100 | } 101 | } 102 | 103 | private void updateInterpStartEndWithMiddles() { 104 | updateWithMiddle(quadDat.getInterpStartPoint(), quadDat.getPoint0(), quadDat.getPoint1()); 105 | updateWithMiddle(quadDat.getInterpEndPoint(), quadDat.getPoint1(), quadDat.getPoint2()); 106 | } 107 | 108 | private void updateWithMiddle(TrailPoint destPoint, TrailPoint point0, TrailPoint point1) { 109 | destPoint.set((point0.getX() + point1.getX() + 1) / 2, (point0.getY() + point1.getY() + 1) / 2); 110 | } 111 | 112 | private void handleCurveEnd() { 113 | if (quadDat.isCurveEnd()) { 114 | TrailPoint lastPoint = quadDat.getPoint2(); 115 | quadCurveArray.add(lastPoint.getX(), lastPoint.getY()); 116 | } 117 | } 118 | 119 | 120 | void rawInterpolate() { 121 | initInterpolation(); 122 | doInterpolate(); 123 | } 124 | 125 | private void doInterpolate() { 126 | for (int t = 0; t < quadDat.getInterpNbPoints(); t++) { 127 | doInterpolateAtT(t); 128 | } 129 | } 130 | 131 | private void doInterpolateAtT(int t) { 132 | int x = getInterp(t, quadDat.getCoefA(), quadDat.getCoefB(), quadDat.getCoefC(), quadDat.getInterpNbPoints()); 133 | int y = getInterp(t, quadDat.getCoefD(), quadDat.getCoefE(), quadDat.getCoefF(), quadDat.getInterpNbPoints()); 134 | quadCurveArray.add(x, y); 135 | } 136 | 137 | private int getInterp(int t, long coef1, long coef2, long coef3, int nbPoints) { 138 | long mod1 = ((coef3 * t) << 12) / nbPoints + (coef2 << 12); 139 | long mod2 = (mod1 * t) / nbPoints + (coef1 << 12); 140 | return (int) ((mod2 + 2 ^ 11) >> 12); 141 | } 142 | 143 | private void initInterpolation() { 144 | initMiddlePoint(); 145 | initInterpNbPoints(); 146 | initCoefficients(); 147 | } 148 | 149 | private void initCoefficients() { 150 | TrailPoint p0 = quadDat.getInterpStartPoint(); 151 | TrailPoint p1 = quadDat.getPoint1(); 152 | TrailPoint p2 = quadDat.getInterpEndPoint(); 153 | 154 | quadDat.setCoefA(p0.getX()); 155 | quadDat.setCoefB((p1.getX() - p0.getX()) << 1); 156 | quadDat.setCoefC(p2.getX() + p0.getX() - (p1.getX() << 1)); 157 | 158 | quadDat.setCoefD(p0.getY()); 159 | quadDat.setCoefE((p1.getY() - p0.getY()) << 1); 160 | quadDat.setCoefF(p2.getY() + p0.getY() - (p1.getY() << 1)); 161 | } 162 | 163 | /** Found middle point, at t = 0.5 */ 164 | private void initMiddlePoint() { 165 | TrailPoint p0 = quadDat.getInterpStartPoint(); 166 | TrailPoint p1 = quadDat.getPoint1(); 167 | TrailPoint p2 = quadDat.getInterpEndPoint(); 168 | 169 | int x = getMiddleApproximation(p0.getX(), p1.getX(), p2.getX()); 170 | int y = getMiddleApproximation(p0.getY(), p1.getY(), p2.getY()); 171 | 172 | quadDat.getMiddlePoint().set(x, y); 173 | } 174 | 175 | private int getMiddleApproximation(int coord0, int coord1, int coord2) { 176 | return (((coord0 + coord1 + 1) >> 1) + ((coord1 + coord2 + 1) >> 1) + 1) >> 1; 177 | } 178 | 179 | 180 | private void initInterpNbPoints() { 181 | int curveLength = getCurveLengthApproximation(); 182 | quadDat.setInterpNbPoints(curveLength / density + 1); 183 | } 184 | 185 | private int getCurveLengthApproximation() { 186 | TrailPoint p0 = quadDat.getInterpStartPoint(); 187 | TrailPoint p2 = quadDat.getInterpEndPoint(); 188 | TrailPoint middlePoint = quadDat.getMiddlePoint(); 189 | return (int) (p0.getDistanceTo(middlePoint) + middlePoint.getDistanceTo(p2) + 0.5f); 190 | } 191 | } 192 | --------------------------------------------------------------------------------