svg_loader: preserveAspectRatio attrib handled according to the svg standard (#1249) 61/289761/1
authorMira Grudzinska <67589014+mgrudzinska@users.noreply.github.com>
Fri, 2 Sep 2022 02:59:49 +0000 (04:59 +0200)
committerMichal Szczecinski <mihashco89@gmail.com>
Tue, 14 Mar 2023 06:45:05 +0000 (07:45 +0100)
* svg_loader: preserveAspectRatio attrib handled according to the svg standard

* svg_loader: symbol fixed

The correct width/height values used in the _useBuildHelper function.
Bug was propageted while the preserveAspectRatio attrib was handled,
now fixed.

* svg_loader: refactoring code regarding the preserveAspectRatio attrib

To avoid copy/paste a new function is introduced to handle the proper
scaling.

* svg_loader: initialize the svg loader members

The 'align' and 'meetOrSlice' svg loader members were not initialized.

* svg_loader: resize function forces any transformation

The resize function is called after the svg image is read and scaled
taking into account the proper preserveAspectRatio value. While resizing
the preserveAspectRatio isn't checked any more and the image can be
freely transformed.

Change-Id: I5ec14b7676063f60c1fc3b12262cc06f85f64e97

src/lib/tvgLoadModule.h
src/loaders/svg/tvgSvgLoader.cpp
src/loaders/svg/tvgSvgLoader.h
src/loaders/svg/tvgSvgLoaderCommon.h
src/loaders/svg/tvgSvgSceneBuilder.cpp
src/loaders/svg/tvgSvgSceneBuilder.h

index bfcc165..0049831 100644 (file)
@@ -36,7 +36,6 @@ public:
     float vw = 0;
     float vh = 0;
     float w = 0, h = 0;         //default image size
-    bool preserveAspect = true; //keep aspect ratio by default.
 
     virtual ~LoadModule() {}
 
index 225ebe8..9a88853 100644 (file)
@@ -115,9 +115,54 @@ static bool _parseNumber(const char** content, float* number)
     if ((*content) == end) return false;
     //Skip comma if any
     *content = _skipComma(end);
+
+    return true;
+}
+
+
+static constexpr struct
+{
+    AspectRatioAlign align;
+    const char* tag;
+} alignTags[] = {
+    { AspectRatioAlign::XMinYMin, "xMinYMin" },
+    { AspectRatioAlign::XMidYMin, "xMidYMin" },
+    { AspectRatioAlign::XMaxYMin, "xMaxYMin" },
+    { AspectRatioAlign::XMinYMid, "xMinYMid" },
+    { AspectRatioAlign::XMidYMid, "xMidYMid" },
+    { AspectRatioAlign::XMaxYMid, "xMaxYMid" },
+    { AspectRatioAlign::XMinYMax, "xMinYMax" },
+    { AspectRatioAlign::XMidYMax, "xMidYMax" },
+    { AspectRatioAlign::XMaxYMax, "xMaxYMax" },
+};
+
+
+static bool _parseAspectRatio(const char** content, AspectRatioAlign* align, AspectRatioMeetOrSlice* meetOrSlice)
+{
+    if (!strcmp(*content, "none")) {
+        *align = AspectRatioAlign::None;
+        return true;
+    }
+
+    for (unsigned int i = 0; i < sizeof(alignTags) / sizeof(alignTags[0]); i++) {
+        if (!strncmp(*content, alignTags[i].tag, 8)) {
+            *align = alignTags[i].align;
+            *content += 8;
+            *content = _skipSpace(*content, nullptr);
+            break;
+        }
+    }
+
+    if (!strcmp(*content, "meet")) {
+        *meetOrSlice = AspectRatioMeetOrSlice::Meet;
+    } else if (!strcmp(*content, "slice")) {
+        *meetOrSlice = AspectRatioMeetOrSlice::Slice;
+    }
+
     return true;
 }
 
+
 /**
  * According to https://www.w3.org/TR/SVG/coords.html#Units
  */
@@ -803,7 +848,7 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value)
         }
         loader->svgParse->global.x = (int)doc->vx;
     } else if (!strcmp(key, "preserveAspectRatio")) {
-        if (!strcmp(value, "none")) doc->preserveAspect = false;
+        _parseAspectRatio(&value, &doc->align, &doc->meetOrSlice);
     } else if (!strcmp(key, "style")) {
         return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
 #ifdef THORVG_LOG_ENABLED
@@ -1157,7 +1202,7 @@ static bool _attrParseSymbolNode(void* data, const char* key, const char* value)
         symbol->h = _toFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
         symbol->hasHeight = true;
     } else if (!strcmp(key, "preserveAspectRatio")) {
-        if (!strcmp(value, "none")) symbol->preserveAspect = false;
+        _parseAspectRatio(&value, &symbol->align, &symbol->meetOrSlice);
     } else if (!strcmp(key, "overflow")) {
         if (!strcmp(value, "visible")) symbol->overflowVisible = true;
     } else {
@@ -1249,7 +1294,8 @@ static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const cha
     loader->svgParse->global.w = 0;
     loader->svgParse->global.h = 0;
 
-    doc->preserveAspect = true;
+    doc->align = AspectRatioAlign::XMidYMid;
+    doc->meetOrSlice = AspectRatioMeetOrSlice::Meet;
     func(buf, bufLength, _attrParseSvgNode, loader);
 
     if (loader->svgParse->global.w == 0) {
@@ -1310,7 +1356,8 @@ static SvgNode* _createSymbolNode(SvgLoaderData* loader, SvgNode* parent, const
     if (!loader->svgParse->node) return nullptr;
 
     loader->svgParse->node->display = false;
-    loader->svgParse->node->node.symbol.preserveAspect = true;
+    loader->svgParse->node->node.symbol.align = AspectRatioAlign::XMidYMid;
+    loader->svgParse->node->node.symbol.meetOrSlice = AspectRatioMeetOrSlice::Meet;
     loader->svgParse->node->node.symbol.overflowVisible = false;
 
     loader->svgParse->node->node.symbol.hasViewBox = false;
@@ -3147,7 +3194,7 @@ void SvgLoader::run(unsigned tid)
 
         _updateStyle(loaderData.doc, nullptr);
     }
-    root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, preserveAspect, svgPath);
+    root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, align, meetOrSlice, svgPath);
 }
 
 
@@ -3180,7 +3227,8 @@ bool SvgLoader::header()
             if (vh < FLT_EPSILON) vh = h;
         }
 
-        preserveAspect = loaderData.doc->node.doc.preserveAspect;
+        align = loaderData.doc->node.doc.align;
+        meetOrSlice = loaderData.doc->node.doc.meetOrSlice;
     } else {
         TVGLOG("SVG", "No SVG File. There is no <svg/>");
         return false;
@@ -3235,31 +3283,9 @@ bool SvgLoader::resize(Paint* paint, float w, float h)
 
     auto sx = w / this->w;
     auto sy = h / this->h;
+    Matrix m = {sx, 0, 0, 0, sy, 0, 0, 0, 1};
+    paint->transform(m);
 
-    if (preserveAspect) {
-        //Scale
-        auto scale = sx < sy ? sx : sy;
-        paint->scale(scale);
-        //Align
-        auto tx = 0.0f;
-        auto ty = 0.0f;
-        auto tw = this->w * scale;
-        auto th = this->h * scale;
-        if (tw > th) ty -= (h - th) * 0.5f;
-        else tx -= (w - tw) * 0.5f;
-        paint->translate(-tx, -ty);
-    } else {
-        //Align
-        auto tx = 0.0f;
-        auto ty = 0.0f;
-        auto tw = this->w * sx;
-        auto th = this->h * sy;
-        if (tw > th) ty -= (h - th) * 0.5f;
-        else tx -= (w - tw) * 0.5f;
-
-        Matrix m = {sx, 0, -tx, 0, sy, -ty, 0, 0, 1};
-        paint->transform(m);
-    }
     return true;
 }
 
index 093fb67..f224d1a 100644 (file)
@@ -50,6 +50,9 @@ public:
     unique_ptr<Paint> paint() override;
 
 private:
+    AspectRatioAlign align = AspectRatioAlign::XMidYMid;
+    AspectRatioMeetOrSlice meetOrSlice = AspectRatioMeetOrSlice::Meet;
+
     bool header();
     void clear();
     void run(unsigned tid) override;
index dc9ed55..c657c0e 100644 (file)
@@ -145,6 +145,26 @@ enum class SvgParserLengthType
     Other
 };
 
+enum class AspectRatioAlign
+{
+    None,
+    XMinYMin,
+    XMidYMin,
+    XMaxYMin,
+    XMinYMid,
+    XMidYMid,
+    XMaxYMid,
+    XMinYMax,
+    XMidYMax,
+    XMaxYMax
+};
+
+enum class AspectRatioMeetOrSlice
+{
+    Meet,
+    Slice
+};
+
 struct SvgDocNode
 {
     float w;
@@ -155,7 +175,8 @@ struct SvgDocNode
     float vh;
     SvgNode* defs;
     SvgNode* style;
-    bool preserveAspect;
+    AspectRatioAlign align;
+    AspectRatioMeetOrSlice meetOrSlice;
 };
 
 struct SvgGNode
@@ -171,7 +192,8 @@ struct SvgSymbolNode
 {
     float w, h;
     float vx, vy, vw, vh;
-    bool preserveAspect;
+    AspectRatioAlign align;
+    AspectRatioMeetOrSlice meetOrSlice;
     bool overflowVisible;
     bool hasViewBox;
     bool hasWidth;
index b4695a6..4cb4ffd 100644 (file)
@@ -560,6 +560,80 @@ static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, con
 }
 
 
+static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, float width, float height, const Box& box)
+{
+    auto sx = width / box.w;
+    auto sy = height / box.h;
+    auto tvx = box.x * sx;
+    auto tvy = box.y * sy;
+
+    if (align == AspectRatioAlign::None)
+        return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
+
+    //Scale
+    if (meetOrSlice == AspectRatioMeetOrSlice::Meet) {
+        if (sx < sy) sy = sx;
+        else sx = sy;
+    } else {
+        if (sx < sy) sx = sy;
+        else sy = sx;
+    }
+
+    //Align
+    tvx = box.x * sx;
+    tvy = box.y * sy;
+    auto tvw = box.w * sx;
+    auto tvh = box.h * sy;
+
+    switch (align) {
+        case AspectRatioAlign::XMinYMin: {
+            break;
+        }
+        case AspectRatioAlign::XMidYMin: {
+            tvx -= (width - tvw) * 0.5f;
+            break;
+        }
+        case AspectRatioAlign::XMaxYMin: {
+            tvx -= width - tvw;
+            break;
+        }
+        case AspectRatioAlign::XMinYMid: {
+            tvy -= (height - tvh) * 0.5f;
+            break;
+        }
+        case AspectRatioAlign::XMidYMid: {
+            tvx -= (width - tvw) * 0.5f;
+            tvy -= (height - tvh) * 0.5f;
+            break;
+        }
+        case AspectRatioAlign::XMaxYMid: {
+            tvx -= width - tvw;
+            tvy -= (height - tvh) * 0.5f;
+            break;
+        }
+        case AspectRatioAlign::XMinYMax: {
+            tvy -= height - tvh;
+            break;
+        }
+        case AspectRatioAlign::XMidYMax: {
+            tvx -= (width - tvw) * 0.5f;
+            tvy -= height - tvh;
+            break;
+        }
+        case AspectRatioAlign::XMaxYMax: {
+            tvx -= width - tvw;
+            tvy -= height - tvh;
+            break;
+        }
+        default: {
+            break;
+        }
+    }
+
+    return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
+}
+
+
 static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite)
 {
     unique_ptr<Scene> finalScene;
@@ -585,20 +659,8 @@ static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, c
 
         Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1};
         if ((!mathEqual(width, vw) || !mathEqual(height, vh)) && vw > 0 && vh > 0) {
-            auto sx = width / vw;
-            auto sy = height / vh;
-            if (symbol.preserveAspect) {
-                if (sx < sy) sy = sx;
-                else sx = sy;
-            }
-
-            auto tvx = symbol.vx * sx;
-            auto tvy = symbol.vy * sy;
-            auto tvw = vw * sx;
-            auto tvh = vh * sy;
-            tvy -= (symbol.h - tvh) * 0.5f;
-            tvx -= (symbol.w - tvw) * 0.5f;
-            mViewBox = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
+            Box box = {symbol.vx, symbol.vy, vw, vh};
+            mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box);
         } else if (!mathZero(symbol.vx) || !mathZero(symbol.vy)) {
             mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1};
         }
@@ -698,36 +760,18 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox,
 /* External Class Implementation                                        */
 /************************************************************************/
 
-unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath)
+unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath)
 {
+    //TODO: aspect ratio is valid only if viewBox was set
+
     if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
 
     Box vBox = {vx, vy, vw, vh};
     auto docNode = _sceneBuildHelper(node, vBox, svgPath, false, 0);
 
     if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
-        auto sx = w / vw;
-        auto sy = h / vh;
-
-        if (preserveAspect) {
-            //Scale
-            auto scale = sx < sy ? sx : sy;
-            docNode->scale(scale);
-            //Align
-            auto tvx = vx * scale;
-            auto tvy = vy * scale;
-            auto tvw = vw * scale;
-            auto tvh = vh * scale;
-            tvx -= (w - tvw) * 0.5f;
-            tvy -= (h - tvh) * 0.5f;
-            docNode->translate(-tvx, -tvy);
-        } else {
-            //Align
-            auto tvx = vx * sx;
-            auto tvy = vy * sy;
-            Matrix m = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
-            docNode->transform(m);
-        }
+        Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
+        docNode->transform(m);
     } else if (!mathZero(vx) || !mathZero(vy)) {
         docNode->translate(-vx, -vy);
     }
index cecbbf0..311f3c8 100644 (file)
@@ -25,6 +25,6 @@
 
 #include "tvgCommon.h"
 
-unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath);
+unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath);
 
 #endif //_TVG_SVG_SCENE_BUILDER_H_