mirror of
https://github.com/thorvg/thorvg.git
synced 2025-07-23 14:48:24 +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 vw = 0;
|
||||||
float vh = 0;
|
float vh = 0;
|
||||||
float w = 0, h = 0; //default image size
|
float w = 0, h = 0; //default image size
|
||||||
bool preserveAspect = true; //keep aspect ratio by default.
|
|
||||||
|
|
||||||
virtual ~LoadModule() {}
|
virtual ~LoadModule() {}
|
||||||
|
|
||||||
|
|
|
@ -115,9 +115,54 @@ static bool _parseNumber(const char** content, float* number)
|
||||||
if ((*content) == end) return false;
|
if ((*content) == end) return false;
|
||||||
//Skip comma if any
|
//Skip comma if any
|
||||||
*content = _skipComma(end);
|
*content = _skipComma(end);
|
||||||
|
|
||||||
return true;
|
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
|
* 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;
|
loader->svgParse->global.x = (int)doc->vx;
|
||||||
} else if (!strcmp(key, "preserveAspectRatio")) {
|
} else if (!strcmp(key, "preserveAspectRatio")) {
|
||||||
if (!strcmp(value, "none")) doc->preserveAspect = false;
|
_parseAspectRatio(&value, &doc->align, &doc->meetOrSlice);
|
||||||
} else if (!strcmp(key, "style")) {
|
} else if (!strcmp(key, "style")) {
|
||||||
return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
|
return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
|
||||||
#ifdef THORVG_LOG_ENABLED
|
#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->h = _toFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
|
||||||
symbol->hasHeight = true;
|
symbol->hasHeight = true;
|
||||||
} else if (!strcmp(key, "preserveAspectRatio")) {
|
} else if (!strcmp(key, "preserveAspectRatio")) {
|
||||||
if (!strcmp(value, "none")) symbol->preserveAspect = false;
|
_parseAspectRatio(&value, &symbol->align, &symbol->meetOrSlice);
|
||||||
} else if (!strcmp(key, "overflow")) {
|
} else if (!strcmp(key, "overflow")) {
|
||||||
if (!strcmp(value, "visible")) symbol->overflowVisible = true;
|
if (!strcmp(value, "visible")) symbol->overflowVisible = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1249,7 +1294,8 @@ static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const cha
|
||||||
loader->svgParse->global.w = 0;
|
loader->svgParse->global.w = 0;
|
||||||
loader->svgParse->global.h = 0;
|
loader->svgParse->global.h = 0;
|
||||||
|
|
||||||
doc->preserveAspect = true;
|
doc->align = AspectRatioAlign::XMidYMid;
|
||||||
|
doc->meetOrSlice = AspectRatioMeetOrSlice::Meet;
|
||||||
func(buf, bufLength, _attrParseSvgNode, loader);
|
func(buf, bufLength, _attrParseSvgNode, loader);
|
||||||
|
|
||||||
if (loader->svgParse->global.w == 0) {
|
if (loader->svgParse->global.w == 0) {
|
||||||
|
@ -1310,7 +1356,8 @@ static SvgNode* _createSymbolNode(SvgLoaderData* loader, SvgNode* parent, const
|
||||||
if (!loader->svgParse->node) return nullptr;
|
if (!loader->svgParse->node) return nullptr;
|
||||||
|
|
||||||
loader->svgParse->node->display = false;
|
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.overflowVisible = false;
|
||||||
|
|
||||||
loader->svgParse->node->node.symbol.hasViewBox = false;
|
loader->svgParse->node->node.symbol.hasViewBox = false;
|
||||||
|
@ -3147,7 +3194,7 @@ void SvgLoader::run(unsigned tid)
|
||||||
|
|
||||||
_updateStyle(loaderData.doc, nullptr);
|
_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;
|
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 {
|
} else {
|
||||||
TVGLOG("SVG", "No SVG File. There is no <svg/>");
|
TVGLOG("SVG", "No SVG File. There is no <svg/>");
|
||||||
return false;
|
return false;
|
||||||
|
@ -3235,31 +3283,9 @@ bool SvgLoader::resize(Paint* paint, float w, float h)
|
||||||
|
|
||||||
auto sx = w / this->w;
|
auto sx = w / this->w;
|
||||||
auto sy = h / this->h;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,9 @@ public:
|
||||||
unique_ptr<Paint> paint() override;
|
unique_ptr<Paint> paint() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
AspectRatioAlign align = AspectRatioAlign::XMidYMid;
|
||||||
|
AspectRatioMeetOrSlice meetOrSlice = AspectRatioMeetOrSlice::Meet;
|
||||||
|
|
||||||
bool header();
|
bool header();
|
||||||
void clear();
|
void clear();
|
||||||
void run(unsigned tid) override;
|
void run(unsigned tid) override;
|
||||||
|
|
|
@ -145,6 +145,26 @@ enum class SvgParserLengthType
|
||||||
Other
|
Other
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class AspectRatioAlign
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
XMinYMin,
|
||||||
|
XMidYMin,
|
||||||
|
XMaxYMin,
|
||||||
|
XMinYMid,
|
||||||
|
XMidYMid,
|
||||||
|
XMaxYMid,
|
||||||
|
XMinYMax,
|
||||||
|
XMidYMax,
|
||||||
|
XMaxYMax
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AspectRatioMeetOrSlice
|
||||||
|
{
|
||||||
|
Meet,
|
||||||
|
Slice
|
||||||
|
};
|
||||||
|
|
||||||
struct SvgDocNode
|
struct SvgDocNode
|
||||||
{
|
{
|
||||||
float w;
|
float w;
|
||||||
|
@ -155,7 +175,8 @@ struct SvgDocNode
|
||||||
float vh;
|
float vh;
|
||||||
SvgNode* defs;
|
SvgNode* defs;
|
||||||
SvgNode* style;
|
SvgNode* style;
|
||||||
bool preserveAspect;
|
AspectRatioAlign align;
|
||||||
|
AspectRatioMeetOrSlice meetOrSlice;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SvgGNode
|
struct SvgGNode
|
||||||
|
@ -171,7 +192,8 @@ struct SvgSymbolNode
|
||||||
{
|
{
|
||||||
float w, h;
|
float w, h;
|
||||||
float vx, vy, vw, vh;
|
float vx, vy, vw, vh;
|
||||||
bool preserveAspect;
|
AspectRatioAlign align;
|
||||||
|
AspectRatioMeetOrSlice meetOrSlice;
|
||||||
bool overflowVisible;
|
bool overflowVisible;
|
||||||
bool hasViewBox;
|
bool hasViewBox;
|
||||||
bool hasWidth;
|
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)
|
static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite)
|
||||||
{
|
{
|
||||||
unique_ptr<Scene> finalScene;
|
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};
|
Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||||
if ((!mathEqual(width, vw) || !mathEqual(height, vh)) && vw > 0 && vh > 0) {
|
if ((!mathEqual(width, vw) || !mathEqual(height, vh)) && vw > 0 && vh > 0) {
|
||||||
auto sx = width / vw;
|
Box box = {symbol.vx, symbol.vy, vw, vh};
|
||||||
auto sy = height / vh;
|
mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box);
|
||||||
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};
|
|
||||||
} else if (!mathZero(symbol.vx) || !mathZero(symbol.vy)) {
|
} else if (!mathZero(symbol.vx) || !mathZero(symbol.vy)) {
|
||||||
mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1};
|
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 */
|
/* 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;
|
if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
|
||||||
|
|
||||||
Box vBox = {vx, vy, vw, vh};
|
Box vBox = {vx, vy, vw, vh};
|
||||||
auto docNode = _sceneBuildHelper(node, vBox, svgPath, false, 0);
|
auto docNode = _sceneBuildHelper(node, vBox, svgPath, false, 0);
|
||||||
|
|
||||||
if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
|
if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
|
||||||
auto sx = w / vw;
|
Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
|
||||||
auto sy = h / vh;
|
docNode->transform(m);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
} else if (!mathZero(vx) || !mathZero(vy)) {
|
} else if (!mathZero(vx) || !mathZero(vy)) {
|
||||||
docNode->translate(-vx, -vy);
|
docNode->translate(-vx, -vy);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,6 @@
|
||||||
|
|
||||||
#include "tvgCommon.h"
|
#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_
|
#endif //_TVG_SVG_SCENE_BUILDER_H_
|
||||||
|
|
Loading…
Add table
Reference in a new issue