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