Home | History | Annotate | Download | only in utils
      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 "VectorDrawableUtils.h"
     18 
     19 #include "PathParser.h"
     20 
     21 #include <math.h>
     22 #include <utils/Log.h>
     23 
     24 namespace android {
     25 namespace uirenderer {
     26 
     27 class PathResolver {
     28 public:
     29     float currentX = 0;
     30     float currentY = 0;
     31     float ctrlPointX = 0;
     32     float ctrlPointY = 0;
     33     float currentSegmentStartX = 0;
     34     float currentSegmentStartY = 0;
     35     void addCommand(SkPath* outPath, char previousCmd, char cmd, const std::vector<float>* points,
     36                     size_t start, size_t end);
     37 };
     38 
     39 bool VectorDrawableUtils::canMorph(const PathData& morphFrom, const PathData& morphTo) {
     40     if (morphFrom.verbs.size() != morphTo.verbs.size()) {
     41         return false;
     42     }
     43 
     44     for (unsigned int i = 0; i < morphFrom.verbs.size(); i++) {
     45         if (morphFrom.verbs[i] != morphTo.verbs[i] ||
     46             morphFrom.verbSizes[i] != morphTo.verbSizes[i]) {
     47             return false;
     48         }
     49     }
     50     return true;
     51 }
     52 
     53 bool VectorDrawableUtils::interpolatePathData(PathData* outData, const PathData& morphFrom,
     54                                               const PathData& morphTo, float fraction) {
     55     if (!canMorph(morphFrom, morphTo)) {
     56         return false;
     57     }
     58     interpolatePaths(outData, morphFrom, morphTo, fraction);
     59     return true;
     60 }
     61 
     62 /**
     63 * Convert an array of PathVerb to Path.
     64 */
     65 void VectorDrawableUtils::verbsToPath(SkPath* outPath, const PathData& data) {
     66     PathResolver resolver;
     67     char previousCommand = 'm';
     68     size_t start = 0;
     69     outPath->reset();
     70     for (unsigned int i = 0; i < data.verbs.size(); i++) {
     71         size_t verbSize = data.verbSizes[i];
     72         resolver.addCommand(outPath, previousCommand, data.verbs[i], &data.points, start,
     73                             start + verbSize);
     74         previousCommand = data.verbs[i];
     75         start += verbSize;
     76     }
     77 }
     78 
     79 /**
     80  * The current PathVerb will be interpolated between the
     81  * <code>nodeFrom</code> and <code>nodeTo</code> according to the
     82  * <code>fraction</code>.
     83  *
     84  * @param nodeFrom The start value as a PathVerb.
     85  * @param nodeTo The end value as a PathVerb
     86  * @param fraction The fraction to interpolate.
     87  */
     88 void VectorDrawableUtils::interpolatePaths(PathData* outData, const PathData& from,
     89                                            const PathData& to, float fraction) {
     90     outData->points.resize(from.points.size());
     91     outData->verbSizes = from.verbSizes;
     92     outData->verbs = from.verbs;
     93 
     94     for (size_t i = 0; i < from.points.size(); i++) {
     95         outData->points[i] = from.points[i] * (1 - fraction) + to.points[i] * fraction;
     96     }
     97 }
     98 
     99 // Use the given verb, and points in the range [start, end) to insert a command into the SkPath.
    100 void PathResolver::addCommand(SkPath* outPath, char previousCmd, char cmd,
    101                               const std::vector<float>* points, size_t start, size_t end) {
    102     int incr = 2;
    103     float reflectiveCtrlPointX;
    104     float reflectiveCtrlPointY;
    105 
    106     switch (cmd) {
    107         case 'z':
    108         case 'Z':
    109             outPath->close();
    110             // Path is closed here, but we need to move the pen to the
    111             // closed position. So we cache the segment's starting position,
    112             // and restore it here.
    113             currentX = currentSegmentStartX;
    114             currentY = currentSegmentStartY;
    115             ctrlPointX = currentSegmentStartX;
    116             ctrlPointY = currentSegmentStartY;
    117             outPath->moveTo(currentX, currentY);
    118             break;
    119         case 'm':
    120         case 'M':
    121         case 'l':
    122         case 'L':
    123         case 't':
    124         case 'T':
    125             incr = 2;
    126             break;
    127         case 'h':
    128         case 'H':
    129         case 'v':
    130         case 'V':
    131             incr = 1;
    132             break;
    133         case 'c':
    134         case 'C':
    135             incr = 6;
    136             break;
    137         case 's':
    138         case 'S':
    139         case 'q':
    140         case 'Q':
    141             incr = 4;
    142             break;
    143         case 'a':
    144         case 'A':
    145             incr = 7;
    146             break;
    147     }
    148 
    149     for (unsigned int k = start; k < end; k += incr) {
    150         switch (cmd) {
    151             case 'm':  // moveto - Start a new sub-path (relative)
    152                 currentX += points->at(k + 0);
    153                 currentY += points->at(k + 1);
    154                 if (k > start) {
    155                     // According to the spec, if a moveto is followed by multiple
    156                     // pairs of coordinates, the subsequent pairs are treated as
    157                     // implicit lineto commands.
    158                     outPath->rLineTo(points->at(k + 0), points->at(k + 1));
    159                 } else {
    160                     outPath->rMoveTo(points->at(k + 0), points->at(k + 1));
    161                     currentSegmentStartX = currentX;
    162                     currentSegmentStartY = currentY;
    163                 }
    164                 break;
    165             case 'M':  // moveto - Start a new sub-path
    166                 currentX = points->at(k + 0);
    167                 currentY = points->at(k + 1);
    168                 if (k > start) {
    169                     // According to the spec, if a moveto is followed by multiple
    170                     // pairs of coordinates, the subsequent pairs are treated as
    171                     // implicit lineto commands.
    172                     outPath->lineTo(points->at(k + 0), points->at(k + 1));
    173                 } else {
    174                     outPath->moveTo(points->at(k + 0), points->at(k + 1));
    175                     currentSegmentStartX = currentX;
    176                     currentSegmentStartY = currentY;
    177                 }
    178                 break;
    179             case 'l':  // lineto - Draw a line from the current point (relative)
    180                 outPath->rLineTo(points->at(k + 0), points->at(k + 1));
    181                 currentX += points->at(k + 0);
    182                 currentY += points->at(k + 1);
    183                 break;
    184             case 'L':  // lineto - Draw a line from the current point
    185                 outPath->lineTo(points->at(k + 0), points->at(k + 1));
    186                 currentX = points->at(k + 0);
    187                 currentY = points->at(k + 1);
    188                 break;
    189             case 'h':  // horizontal lineto - Draws a horizontal line (relative)
    190                 outPath->rLineTo(points->at(k + 0), 0);
    191                 currentX += points->at(k + 0);
    192                 break;
    193             case 'H':  // horizontal lineto - Draws a horizontal line
    194                 outPath->lineTo(points->at(k + 0), currentY);
    195                 currentX = points->at(k + 0);
    196                 break;
    197             case 'v':  // vertical lineto - Draws a vertical line from the current point (r)
    198                 outPath->rLineTo(0, points->at(k + 0));
    199                 currentY += points->at(k + 0);
    200                 break;
    201             case 'V':  // vertical lineto - Draws a vertical line from the current point
    202                 outPath->lineTo(currentX, points->at(k + 0));
    203                 currentY = points->at(k + 0);
    204                 break;
    205             case 'c':  // curveto - Draws a cubic Bzier curve (relative)
    206                 outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2),
    207                                   points->at(k + 3), points->at(k + 4), points->at(k + 5));
    208 
    209                 ctrlPointX = currentX + points->at(k + 2);
    210                 ctrlPointY = currentY + points->at(k + 3);
    211                 currentX += points->at(k + 4);
    212                 currentY += points->at(k + 5);
    213 
    214                 break;
    215             case 'C':  // curveto - Draws a cubic Bzier curve
    216                 outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2),
    217                                  points->at(k + 3), points->at(k + 4), points->at(k + 5));
    218                 currentX = points->at(k + 4);
    219                 currentY = points->at(k + 5);
    220                 ctrlPointX = points->at(k + 2);
    221                 ctrlPointY = points->at(k + 3);
    222                 break;
    223             case 's':  // smooth curveto - Draws a cubic Bzier curve (reflective cp)
    224                 reflectiveCtrlPointX = 0;
    225                 reflectiveCtrlPointY = 0;
    226                 if (previousCmd == 'c' || previousCmd == 's' || previousCmd == 'C' ||
    227                     previousCmd == 'S') {
    228                     reflectiveCtrlPointX = currentX - ctrlPointX;
    229                     reflectiveCtrlPointY = currentY - ctrlPointY;
    230                 }
    231                 outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, points->at(k + 0),
    232                                   points->at(k + 1), points->at(k + 2), points->at(k + 3));
    233                 ctrlPointX = currentX + points->at(k + 0);
    234                 ctrlPointY = currentY + points->at(k + 1);
    235                 currentX += points->at(k + 2);
    236                 currentY += points->at(k + 3);
    237                 break;
    238             case 'S':  // shorthand/smooth curveto Draws a cubic Bzier curve(reflective cp)
    239                 reflectiveCtrlPointX = currentX;
    240                 reflectiveCtrlPointY = currentY;
    241                 if (previousCmd == 'c' || previousCmd == 's' || previousCmd == 'C' ||
    242                     previousCmd == 'S') {
    243                     reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
    244                     reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
    245                 }
    246                 outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, points->at(k + 0),
    247                                  points->at(k + 1), points->at(k + 2), points->at(k + 3));
    248                 ctrlPointX = points->at(k + 0);
    249                 ctrlPointY = points->at(k + 1);
    250                 currentX = points->at(k + 2);
    251                 currentY = points->at(k + 3);
    252                 break;
    253             case 'q':  // Draws a quadratic Bzier (relative)
    254                 outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2),
    255                                  points->at(k + 3));
    256                 ctrlPointX = currentX + points->at(k + 0);
    257                 ctrlPointY = currentY + points->at(k + 1);
    258                 currentX += points->at(k + 2);
    259                 currentY += points->at(k + 3);
    260                 break;
    261             case 'Q':  // Draws a quadratic Bzier
    262                 outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2),
    263                                 points->at(k + 3));
    264                 ctrlPointX = points->at(k + 0);
    265                 ctrlPointY = points->at(k + 1);
    266                 currentX = points->at(k + 2);
    267                 currentY = points->at(k + 3);
    268                 break;
    269             case 't':  // Draws a quadratic Bzier curve(reflective control point)(relative)
    270                 reflectiveCtrlPointX = 0;
    271                 reflectiveCtrlPointY = 0;
    272                 if (previousCmd == 'q' || previousCmd == 't' || previousCmd == 'Q' ||
    273                     previousCmd == 'T') {
    274                     reflectiveCtrlPointX = currentX - ctrlPointX;
    275                     reflectiveCtrlPointY = currentY - ctrlPointY;
    276                 }
    277                 outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, points->at(k + 0),
    278                                  points->at(k + 1));
    279                 ctrlPointX = currentX + reflectiveCtrlPointX;
    280                 ctrlPointY = currentY + reflectiveCtrlPointY;
    281                 currentX += points->at(k + 0);
    282                 currentY += points->at(k + 1);
    283                 break;
    284             case 'T':  // Draws a quadratic Bzier curve (reflective control point)
    285                 reflectiveCtrlPointX = currentX;
    286                 reflectiveCtrlPointY = currentY;
    287                 if (previousCmd == 'q' || previousCmd == 't' || previousCmd == 'Q' ||
    288                     previousCmd == 'T') {
    289                     reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
    290                     reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
    291                 }
    292                 outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, points->at(k + 0),
    293                                 points->at(k + 1));
    294                 ctrlPointX = reflectiveCtrlPointX;
    295                 ctrlPointY = reflectiveCtrlPointY;
    296                 currentX = points->at(k + 0);
    297                 currentY = points->at(k + 1);
    298                 break;
    299             case 'a':  // Draws an elliptical arc
    300                 // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
    301                 outPath->arcTo(points->at(k + 0), points->at(k + 1), points->at(k + 2),
    302                                (SkPath::ArcSize) (points->at(k + 3) != 0),
    303                                (SkPath::Direction) (points->at(k + 4) == 0),
    304                                points->at(k + 5) + currentX, points->at(k + 6) + currentY);
    305                 currentX += points->at(k + 5);
    306                 currentY += points->at(k + 6);
    307                 ctrlPointX = currentX;
    308                 ctrlPointY = currentY;
    309                 break;
    310             case 'A':  // Draws an elliptical arc
    311                 outPath->arcTo(points->at(k + 0), points->at(k + 1), points->at(k + 2),
    312                                (SkPath::ArcSize) (points->at(k + 3) != 0),
    313                                (SkPath::Direction) (points->at(k + 4) == 0),
    314                                points->at(k + 5), points->at(k + 6));
    315                 currentX = points->at(k + 5);
    316                 currentY = points->at(k + 6);
    317                 ctrlPointX = currentX;
    318                 ctrlPointY = currentY;
    319                 break;
    320             default:
    321                 LOG_ALWAYS_FATAL("Unsupported command: %c", cmd);
    322                 break;
    323         }
    324         previousCmd = cmd;
    325     }
    326 }
    327 
    328 }  // namespace uirenderer
    329 }  // namespace android
    330