1 /*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <gtest/gtest.h>
18
19 #include "PathParser.h"
20 #include "VectorDrawable.h"
21 #include "utils/MathUtils.h"
22 #include "utils/VectorDrawableUtils.h"
23
24 #include <SkBitmap.h>
25 #include <SkCanvas.h>
26 #include <SkPath.h>
27 #include <SkRefCnt.h>
28 #include <SkShader.h>
29
30 #include <functional>
31
32 namespace android {
33 namespace uirenderer {
34
35 struct TestData {
36 const char* pathString;
37 const PathData pathData;
38 const std::function<void(SkPath*)> skPathLamda;
39 };
40
41 const static TestData sTestDataSet[] = {
42 // TestData with scientific notation -2e3 etc.
43 {// Path
44 "M2.000000,22.000000l20.000000,0.000000 1e0-2e3z",
45 {
46 // Verbs
47 {'M', 'l', 'z'},
48 // Verb sizes
49 {2, 4, 0},
50 // Points
51 {2, 22, 20, 0, 1, -2000},
52 },
__anon6c41bec80102() 53 [](SkPath* outPath) {
54 outPath->moveTo(2, 22);
55 outPath->rLineTo(20, 0);
56 outPath->rLineTo(1, -2000);
57 outPath->close();
58 outPath->moveTo(2, 22);
59 }},
60
61 // Comprehensive data, containing all the verbs possible.
62 {// Path
63 "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 "
64 "8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10",
65 {// Verbs
66 {'M', 'm', 'l', 'L', 'H', 'h', 'V', 'v', 'Q', 'q', 't', 'T', 'C', 'c', 'S', 's', 'A',
67 'a'},
68 // VerbSizes
69 {2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, 6, 6, 4, 4, 7, 7},
70 // Points
71 {
72 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 6.0,
73 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0,
74 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 10.0,
75 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0,
76 }},
__anon6c41bec80202() 77 [](SkPath* outPath) {
78 outPath->moveTo(1.0, 1.0);
79 outPath->rMoveTo(2.0, 2.0);
80 outPath->rLineTo(3.0, 3.0);
81 outPath->lineTo(3.0, 3.0);
82 outPath->lineTo(4.0, 3.0);
83 outPath->rLineTo(4.0, 0);
84 outPath->lineTo(8.0, 5.0);
85 outPath->rLineTo(0, 5.0);
86 outPath->quadTo(6.0, 6.0, 6.0, 6.0);
87 outPath->rQuadTo(6.0, 6.0, 6.0, 6.0);
88 outPath->rQuadTo(0.0, 0.0, 7.0, 7.0);
89 outPath->quadTo(26.0, 26.0, 7.0, 7.0);
90 outPath->cubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
91 outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
92 outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0);
93 outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0);
94 outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPathDirection::kCW, 10.0,
95 10.0);
96 outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPathDirection::kCW, 20.0,
97 20.0);
98 }},
99
100 // Check box VectorDrawable path data
101 {// Path
102 "M 0.0,-1.0 l 0.0,0.0 c 0.5522847498,0.0 1.0,0.4477152502 1.0,1.0 l 0.0,0.0 c "
103 "0.0,0.5522847498 -0.4477152502,1.0 -1.0,1.0 l 0.0,0.0 c -0.5522847498,0.0 "
104 "-1.0,-0.4477152502 -1.0,-1.0 l 0.0,0.0 c 0.0,-0.5522847498 0.4477152502,-1.0 1.0,-1.0 Z "
105 "M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 "
106 "c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 "
107 "14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 "
108 "0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z",
109 {
110 {'M', 'l', 'c', 'l', 'c', 'l', 'c', 'l', 'c', 'Z', 'M',
111 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'Z'},
112 {2, 2, 6, 2, 6, 2, 6, 2, 6, 0, 2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0},
113 {0.0, -1.0, 0.0, 0.0, 0.5522848, 0.0, 1.0,
114 0.44771525, 1.0, 1.0, 0.0, 0.0, 0.0, 0.5522848,
115 -0.44771525, 1.0, -1.0, 1.0, 0.0, 0.0, -0.5522848,
116 0.0, -1.0, -0.44771525, -1.0, -1.0, 0.0, 0.0,
117 0.0, -0.5522848, 0.44771525, -1.0, 1.0, -1.0, 7.0,
118 -9.0, 0.0, 0.0, -14.0, 0.0, -14.0, 0.0,
119 -1.1044922, 0.0, -2.0, 0.8955078, -2.0, 2.0, 0.0,
120 0.0, 0.0, 14.0, 0.0, 14.0, 0.0, 1.1044922,
121 0.8955078, 2.0, 2.0, 2.0, 0.0, 0.0, 14.0,
122 0.0, 14.0, 0.0, 1.1044922, 0.0, 2.0, -0.8955078,
123 2.0, -2.0, 0.0, 0.0, 0.0, -14.0, 0.0,
124 -14.0, 0.0, -1.1044922, -0.8955078, -2.0, -2.0, -2.0,
125 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
126 },
__anon6c41bec80302() 127 [](SkPath* outPath) {
128 outPath->moveTo(0.0, -1.0);
129 outPath->rLineTo(0.0, 0.0);
130 outPath->rCubicTo(0.5522848, 0.0, 1.0, 0.44771525, 1.0, 1.0);
131 outPath->rLineTo(0.0, 0.0);
132 outPath->rCubicTo(0.0, 0.5522848, -0.44771525, 1.0, -1.0, 1.0);
133 outPath->rLineTo(0.0, 0.0);
134 outPath->rCubicTo(-0.5522848, 0.0, -1.0, -0.44771525, -1.0, -1.0);
135 outPath->rLineTo(0.0, 0.0);
136 outPath->rCubicTo(0.0, -0.5522848, 0.44771525, -1.0, 1.0, -1.0);
137 outPath->close();
138 outPath->moveTo(0.0, -1.0);
139 outPath->moveTo(7.0, -9.0);
140 outPath->rCubicTo(0.0, 0.0, -14.0, 0.0, -14.0, 0.0);
141 outPath->rCubicTo(-1.1044922, 0.0, -2.0, 0.8955078, -2.0, 2.0);
142 outPath->rCubicTo(0.0, 0.0, 0.0, 14.0, 0.0, 14.0);
143 outPath->rCubicTo(0.0, 1.1044922, 0.8955078, 2.0, 2.0, 2.0);
144 outPath->rCubicTo(0.0, 0.0, 14.0, 0.0, 14.0, 0.0);
145 outPath->rCubicTo(1.1044922, 0.0, 2.0, -0.8955078, 2.0, -2.0);
146 outPath->rCubicTo(0.0, 0.0, 0.0, -14.0, 0.0, -14.0);
147 outPath->rCubicTo(0.0, -1.1044922, -0.8955078, -2.0, -2.0, -2.0);
148 outPath->rCubicTo(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
149 outPath->close();
150 outPath->moveTo(7.0, -9.0);
151 }},
152
153 // pie1 in progress bar
154 {"M300,70 a230,230 0 1,0 1,0 z",
155 {
156 {
157 'M', 'a', 'z',
158 },
159 {
160 2, 7, 0,
161 },
162 {
163 300.0, 70.0, 230.0, 230.0, 0.0, 1.0, 0.0, 1.0, 0.0,
164 },
165 },
__anon6c41bec80402() 166 [](SkPath* outPath) {
167 outPath->moveTo(300.0, 70.0);
168 outPath->arcTo(230.0, 230.0, 0.0, SkPath::kLarge_ArcSize, SkPathDirection::kCCW,
169 301.0, 70.0);
170 outPath->close();
171 outPath->moveTo(300.0, 70.0);
172 }},
173
174 // Random long data
175 {// Path
176 "M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 "
177 "4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 "
178 "-0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 "
179 "-12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z",
180 {
181 // Verbs
182 {'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'C', 'z'},
183 // Verb sizes
184 {2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0},
185 // Points
186 {5.3, 13.2, -0.1, 0, -0.3, 0, -0.4, -0.1, -0.3, -0.2, -0.4, -0.7, -0.2, -1,
187 1.3, -1.9, 2.9, -3.4, 4.9, -4.5, 4.1, -2.2, 9.3, -2.2, 13.4, 0, 1.9, 1.1,
188 3.6, 2.5, 4.9, 4.4, 0.2, 0.3, 0.1, 0.8, -0.2, 1, -0.3, 0.2, -0.8, 0.1,
189 -1, -0.2, -1.2, -1.7, -2.6, -3, -4.3, -4, -3.7, -2, -8.3, -2, -12, 0,
190 -1.7, 0.9, -3.2, 2.3, -4.3, 4, 5.7, 13.1, 5.5, 13.2, 5.3, 13.2},
191 },
__anon6c41bec80502() 192 [](SkPath* outPath) {
193 outPath->moveTo(5.3, 13.2);
194 outPath->rCubicTo(-0.1, 0.0, -0.3, 0.0, -0.4, -0.1);
195 outPath->rCubicTo(-0.3, -0.2, -0.4, -0.7, -0.2, -1.0);
196 outPath->rCubicTo(1.3, -1.9, 2.9, -3.4, 4.9, -4.5);
197 outPath->rCubicTo(4.1, -2.2, 9.3, -2.2, 13.4, 0.0);
198 outPath->rCubicTo(1.9, 1.1, 3.6, 2.5, 4.9, 4.4);
199 outPath->rCubicTo(0.2, 0.3, 0.1, 0.8, -0.2, 1.0);
200 outPath->rCubicTo(-0.3, 0.2, -0.8, 0.1, -1.0, -0.2);
201 outPath->rCubicTo(-1.2, -1.7, -2.6, -3.0, -4.3, -4.0);
202 outPath->rCubicTo(-3.7, -2.0, -8.3, -2.0, -12.0, 0.0);
203 outPath->rCubicTo(-1.7, 0.9, -3.2, 2.3, -4.3, 4.0);
204 outPath->cubicTo(5.7, 13.1, 5.5, 13.2, 5.3, 13.2);
205 outPath->close();
206 outPath->moveTo(5.3, 13.2);
207 }},
208
209 // Extreme case with numbers and decimal points crunched together
210 {// Path
211 "l0.0.0.5.0.0.5-0.5.0.0-.5z",
212 {
213 // Verbs
214 {'l', 'z'},
215 // Verb sizes
216 {10, 0},
217 // Points
218 {0, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5},
219 },
__anon6c41bec80602() 220 [](SkPath* outPath) {
221 outPath->rLineTo(0.0, 0.0);
222 outPath->rLineTo(0.5, 0.0);
223 outPath->rLineTo(0.0, 0.5);
224 outPath->rLineTo(-0.5, 0.0);
225 outPath->rLineTo(0.0, -0.5);
226 outPath->close();
227 outPath->moveTo(0.0, 0.0);
228 }},
229
230 // Empty test data
231 {"",
232 {
233 // Verbs
234 {},
235 {},
236 {},
237 },
__anon6c41bec80702() 238 [](SkPath* outPath) {}}
239
240 };
241
242 struct StringPath {
243 const char* stringPath;
244 bool isValid;
245 };
246
247 const StringPath sStringPaths[] = {
248 {"3e...3", false}, // Not starting with a verb and ill-formatted float
249 {"L.M.F.A.O", false}, // No floats following verbs
250 {"m 1 1", true}, // Valid path data
251 {"\n \t z", true}, // Valid path data with leading spaces
252 {"1-2e34567", false}, // Not starting with a verb and ill-formatted float
253 {"f 4 5", false}, // Invalid verb
254 {"\r ", false}, // Empty string
255 {"L1,0 L1,1 L0,1 z M1000", false} // Not enough floats following verb M.
256 };
257
hasSameVerbs(const PathData & from,const PathData & to)258 static bool hasSameVerbs(const PathData& from, const PathData& to) {
259 return from.verbs == to.verbs && from.verbSizes == to.verbSizes;
260 }
261
TEST(PathParser,parseStringForData)262 TEST(PathParser, parseStringForData) {
263 for (const TestData& testData : sTestDataSet) {
264 PathParser::ParseResult result;
265 // Test generated path data against the given data.
266 PathData pathData;
267 size_t length = strlen(testData.pathString);
268 PathParser::getPathDataFromAsciiString(&pathData, &result, testData.pathString, length);
269 EXPECT_EQ(testData.pathData, pathData);
270 }
271
272 for (StringPath stringPath : sStringPaths) {
273 PathParser::ParseResult result;
274 PathData pathData;
275 SkPath skPath;
276 PathParser::getPathDataFromAsciiString(&pathData, &result, stringPath.stringPath,
277 strlen(stringPath.stringPath));
278 EXPECT_EQ(stringPath.isValid, !result.failureOccurred);
279 }
280 }
281
TEST(VectorDrawableUtils,createSkPathFromPathData)282 TEST(VectorDrawableUtils, createSkPathFromPathData) {
283 for (const TestData& testData : sTestDataSet) {
284 SkPath expectedPath;
285 testData.skPathLamda(&expectedPath);
286 SkPath actualPath;
287 VectorDrawableUtils::verbsToPath(&actualPath, testData.pathData);
288 EXPECT_EQ(expectedPath, actualPath);
289 }
290 }
291
TEST(PathParser,parseAsciiStringForSkPath)292 TEST(PathParser, parseAsciiStringForSkPath) {
293 for (const TestData& testData : sTestDataSet) {
294 PathParser::ParseResult result;
295 size_t length = strlen(testData.pathString);
296 // Check the return value as well as the SkPath generated.
297 SkPath actualPath;
298 PathParser::parseAsciiStringForSkPath(&actualPath, &result, testData.pathString, length);
299 bool hasValidData = !result.failureOccurred;
300 EXPECT_EQ(hasValidData, testData.pathData.verbs.size() > 0);
301 SkPath expectedPath;
302 testData.skPathLamda(&expectedPath);
303 EXPECT_EQ(expectedPath, actualPath);
304 }
305
306 for (StringPath stringPath : sStringPaths) {
307 PathParser::ParseResult result;
308 SkPath skPath;
309 PathParser::parseAsciiStringForSkPath(&skPath, &result, stringPath.stringPath,
310 strlen(stringPath.stringPath));
311 EXPECT_EQ(stringPath.isValid, !result.failureOccurred);
312 }
313 }
314
TEST(VectorDrawableUtils,morphPathData)315 TEST(VectorDrawableUtils, morphPathData) {
316 for (const TestData& fromData : sTestDataSet) {
317 for (const TestData& toData : sTestDataSet) {
318 bool canMorph = VectorDrawableUtils::canMorph(fromData.pathData, toData.pathData);
319 if (fromData.pathData == toData.pathData) {
320 EXPECT_TRUE(canMorph);
321 } else {
322 bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
323 EXPECT_EQ(expectedToMorph, canMorph);
324 }
325 }
326 }
327 }
328
TEST(VectorDrawableUtils,interpolatePathData)329 TEST(VectorDrawableUtils, interpolatePathData) {
330 // Interpolate path data with itself and every other path data
331 for (const TestData& fromData : sTestDataSet) {
332 for (const TestData& toData : sTestDataSet) {
333 PathData outData;
334 bool success = VectorDrawableUtils::interpolatePathData(&outData, fromData.pathData,
335 toData.pathData, 0.5);
336 bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
337 EXPECT_EQ(expectedToMorph, success);
338 }
339 }
340
341 float fractions[] = {0, 0.00001, 0.28, 0.5, 0.7777, 0.9999999, 1};
342 // Now try to interpolate with a slightly modified version of self and expect success
343 for (const TestData& fromData : sTestDataSet) {
344 PathData toPathData = fromData.pathData;
345 for (size_t i = 0; i < toPathData.points.size(); i++) {
346 toPathData.points[i]++;
347 }
348 const PathData& fromPathData = fromData.pathData;
349 PathData outData;
350 // Interpolate the two path data with different fractions
351 for (float fraction : fractions) {
352 bool success = VectorDrawableUtils::interpolatePathData(&outData, fromPathData,
353 toPathData, fraction);
354 EXPECT_TRUE(success);
355 for (size_t i = 0; i < outData.points.size(); i++) {
356 float expectedResult =
357 fromPathData.points[i] * (1.0 - fraction) + toPathData.points[i] * fraction;
358 EXPECT_TRUE(MathUtils::areEqual(expectedResult, outData.points[i]));
359 }
360 }
361 }
362 }
363
TEST(VectorDrawable,groupProperties)364 TEST(VectorDrawable, groupProperties) {
365 // TODO: Also need to test property sync and dirty flag when properties change.
366 VectorDrawable::Group group;
367 VectorDrawable::Group::GroupProperties* properties = group.mutateProperties();
368 // Test default values, change values through setters and verify the change through getters.
369 EXPECT_EQ(0.0f, properties->getTranslateX());
370 properties->setTranslateX(1.0f);
371 EXPECT_EQ(1.0f, properties->getTranslateX());
372
373 EXPECT_EQ(0.0f, properties->getTranslateY());
374 properties->setTranslateY(1.0f);
375 EXPECT_EQ(1.0f, properties->getTranslateY());
376
377 EXPECT_EQ(0.0f, properties->getRotation());
378 properties->setRotation(1.0f);
379 EXPECT_EQ(1.0f, properties->getRotation());
380
381 EXPECT_EQ(1.0f, properties->getScaleX());
382 properties->setScaleX(0.0f);
383 EXPECT_EQ(0.0f, properties->getScaleX());
384
385 EXPECT_EQ(1.0f, properties->getScaleY());
386 properties->setScaleY(0.0f);
387 EXPECT_EQ(0.0f, properties->getScaleY());
388
389 EXPECT_EQ(0.0f, properties->getPivotX());
390 properties->setPivotX(1.0f);
391 EXPECT_EQ(1.0f, properties->getPivotX());
392
393 EXPECT_EQ(0.0f, properties->getPivotY());
394 properties->setPivotY(1.0f);
395 EXPECT_EQ(1.0f, properties->getPivotY());
396 }
397
TEST(VectorDrawable,drawPathWithoutIncrementingShaderRefCount)398 TEST(VectorDrawable, drawPathWithoutIncrementingShaderRefCount) {
399 VectorDrawable::FullPath path("m1 1", 4);
400 SkBitmap bitmap;
401 bitmap.allocN32Pixels(5, 5, false);
402 SkCanvas canvas(bitmap);
403
404 sk_sp<SkShader> shader = SkShaders::Color(SK_ColorBLACK);
405 // Initial ref count is 1
406 EXPECT_TRUE(shader->unique());
407
408 // Setting the fill gradient increments the ref count of the shader by 1
409 path.mutateStagingProperties()->setFillGradient(shader.get());
410 EXPECT_FALSE(shader->unique());
411 path.draw(&canvas, true);
412 // Resetting the fill gradient decrements the ref count of the shader by 1
413 path.mutateStagingProperties()->setFillGradient(nullptr);
414
415 EXPECT_TRUE(shader->unique());
416 }
417
418 } // namespace uirenderer
419 } // namespace android
420