mirror of
https://github.com/thorvg/thorvg.git
synced 2025-07-23 06:38:43 +00:00
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:
parent
c93c2d3100
commit
3939b61770
6 changed files with 165 additions and 71 deletions
|
@ -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() {}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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_
|
||||
|
|
Loading…
Add table
Reference in a new issue