svg_loader: preserveAspectRatio attrib handled according to the svg standard (#1249)

* 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.
This commit is contained in:
Mira Grudzinska 2022-09-02 04:59:49 +02:00 committed by GitHub
parent c93c2d3100
commit 3939b61770
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 165 additions and 71 deletions

View 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() {}

View 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;
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};
Matrix m = {sx, 0, 0, 0, sy, 0, 0, 0, 1};
paint->transform(m);
}
return true;
}

View 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;

View 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;

View 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};
Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
docNode->transform(m);
}
} else if (!mathZero(vx) || !mathZero(vy)) {
docNode->translate(-vx, -vy);
}

View 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_