804618d086
Introduced PathData in Java, which is effectively a thin layer around the native instance. PathData holds the verbs and points which is being used in path morphing/interpolation. The verbs and points can be interpreted into skia path commands, which is now done in native and therefore saves a handful of JNI calls during path creation. Removed the old PathDataNode mechanism and changed the PathEvaluator to use PathData instead. Also added tests and a microbench. Also ran CTS tests for VectorDrawable and AnimatedVectorDrawable, and passed all of the existing tests. Change-Id: Ia166f5172ff031fe18b154327967f911a62caec1
230 lines
7.3 KiB
C++
230 lines
7.3 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "PathParser.h"
|
|
|
|
#include "jni.h"
|
|
|
|
#include <errno.h>
|
|
#include <utils/Log.h>
|
|
#include <sstream>
|
|
#include <stdlib.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace android {
|
|
namespace uirenderer {
|
|
|
|
static size_t nextStart(const char* s, size_t length, size_t startIndex) {
|
|
size_t index = startIndex;
|
|
while (index < length) {
|
|
char c = s[index];
|
|
// Note that 'e' or 'E' are not valid path commands, but could be
|
|
// used for floating point numbers' scientific notation.
|
|
// Therefore, when searching for next command, we should ignore 'e'
|
|
// and 'E'.
|
|
if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
|
|
&& c != 'e' && c != 'E') {
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Calculate the position of the next comma or space or negative sign
|
|
* @param s the string to search
|
|
* @param start the position to start searching
|
|
* @param result the result of the extraction, including the position of the
|
|
* the starting position of next number, whether it is ending with a '-'.
|
|
*/
|
|
static void extract(int* outEndPosition, bool* outEndWithNegOrDot, const char* s, int start, int end) {
|
|
// Now looking for ' ', ',', '.' or '-' from the start.
|
|
int currentIndex = start;
|
|
bool foundSeparator = false;
|
|
*outEndWithNegOrDot = false;
|
|
bool secondDot = false;
|
|
bool isExponential = false;
|
|
for (; currentIndex < end; currentIndex++) {
|
|
bool isPrevExponential = isExponential;
|
|
isExponential = false;
|
|
char currentChar = s[currentIndex];
|
|
switch (currentChar) {
|
|
case ' ':
|
|
case ',':
|
|
foundSeparator = true;
|
|
break;
|
|
case '-':
|
|
// The negative sign following a 'e' or 'E' is not a separator.
|
|
if (currentIndex != start && !isPrevExponential) {
|
|
foundSeparator = true;
|
|
*outEndWithNegOrDot = true;
|
|
}
|
|
break;
|
|
case '.':
|
|
if (!secondDot) {
|
|
secondDot = true;
|
|
} else {
|
|
// This is the second dot, and it is considered as a separator.
|
|
foundSeparator = true;
|
|
*outEndWithNegOrDot = true;
|
|
}
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
isExponential = true;
|
|
break;
|
|
}
|
|
if (foundSeparator) {
|
|
break;
|
|
}
|
|
}
|
|
// In the case where nothing is found, we put the end position to the end of
|
|
// our extract range. Otherwise, end position will be where separator is found.
|
|
*outEndPosition = currentIndex;
|
|
}
|
|
|
|
static float parseFloat(PathParser::ParseResult* result, const char* startPtr, size_t expectedLength) {
|
|
char* endPtr = NULL;
|
|
float currentValue = strtof(startPtr, &endPtr);
|
|
if ((currentValue == HUGE_VALF || currentValue == -HUGE_VALF) && errno == ERANGE) {
|
|
result->failureOccurred = true;
|
|
result->failureMessage = "Float out of range: ";
|
|
result->failureMessage.append(startPtr, expectedLength);
|
|
}
|
|
if (currentValue == 0 && endPtr == startPtr) {
|
|
// No conversion is done.
|
|
result->failureOccurred = true;
|
|
result->failureMessage = "Float format error when parsing: ";
|
|
result->failureMessage.append(startPtr, expectedLength);
|
|
}
|
|
return currentValue;
|
|
}
|
|
|
|
/**
|
|
* Parse the floats in the string.
|
|
*
|
|
* @param s the string containing a command and list of floats
|
|
* @return true on success
|
|
*/
|
|
static void getFloats(std::vector<float>* outPoints, PathParser::ParseResult* result,
|
|
const char* pathStr, int start, int end) {
|
|
|
|
if (pathStr[start] == 'z' || pathStr[start] == 'Z') {
|
|
return;
|
|
}
|
|
int startPosition = start + 1;
|
|
int endPosition = start;
|
|
|
|
// The startPosition should always be the first character of the
|
|
// current number, and endPosition is the character after the current
|
|
// number.
|
|
while (startPosition < end) {
|
|
bool endWithNegOrDot;
|
|
extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end);
|
|
|
|
if (startPosition < endPosition) {
|
|
float currentValue = parseFloat(result, &pathStr[startPosition],
|
|
end - startPosition);
|
|
if (result->failureOccurred) {
|
|
return;
|
|
}
|
|
outPoints->push_back(currentValue);
|
|
}
|
|
|
|
if (endWithNegOrDot) {
|
|
// Keep the '-' or '.' sign with next number.
|
|
startPosition = endPosition;
|
|
} else {
|
|
startPosition = endPosition + 1;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void PathParser::getPathDataFromString(PathData* data, ParseResult* result,
|
|
const char* pathStr, size_t strLen) {
|
|
if (pathStr == NULL) {
|
|
result->failureOccurred = true;
|
|
result->failureMessage = "Path string cannot be NULL.";
|
|
return;
|
|
}
|
|
|
|
size_t start = 0;
|
|
size_t end = 1;
|
|
|
|
while (end < strLen) {
|
|
end = nextStart(pathStr, strLen, end);
|
|
std::vector<float> points;
|
|
getFloats(&points, result, pathStr, start, end);
|
|
if (result->failureOccurred) {
|
|
return;
|
|
}
|
|
data->verbs.push_back(pathStr[start]);
|
|
data->verbSizes.push_back(points.size());
|
|
data->points.insert(data->points.end(), points.begin(), points.end());
|
|
start = end;
|
|
end++;
|
|
}
|
|
|
|
if ((end - start) == 1 && start < strLen) {
|
|
data->verbs.push_back(pathStr[start]);
|
|
data->verbSizes.push_back(0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void PathParser::dump(const PathData& data) {
|
|
// Print out the path data.
|
|
size_t start = 0;
|
|
for (size_t i = 0; i < data.verbs.size(); i++) {
|
|
std::ostringstream os;
|
|
os << data.verbs[i];
|
|
os << ", verb size: " << data.verbSizes[i];
|
|
for (size_t j = 0; j < data.verbSizes[i]; j++) {
|
|
os << " " << data.points[start + j];
|
|
}
|
|
start += data.verbSizes[i];
|
|
ALOGD("%s", os.str().c_str());
|
|
}
|
|
|
|
std::ostringstream os;
|
|
for (size_t i = 0; i < data.points.size(); i++) {
|
|
os << data.points[i] << ", ";
|
|
}
|
|
ALOGD("points are : %s", os.str().c_str());
|
|
}
|
|
|
|
void PathParser::parseStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr, size_t strLen) {
|
|
PathData pathData;
|
|
getPathDataFromString(&pathData, result, pathStr, strLen);
|
|
if (result->failureOccurred) {
|
|
return;
|
|
}
|
|
// Check if there is valid data coming out of parsing the string.
|
|
if (pathData.verbs.size() == 0) {
|
|
result->failureOccurred = true;
|
|
result->failureMessage = "No verbs found in the string for pathData";
|
|
return;
|
|
}
|
|
VectorDrawableUtils::verbsToPath(skPath, pathData);
|
|
return;
|
|
}
|
|
|
|
}; // namespace uirenderer
|
|
}; //namespace android
|