diff --git a/src/loaders/svg/tvgSvgLoader.cpp b/src/loaders/svg/tvgSvgLoader.cpp index 989c267e..f487c5eb 100644 --- a/src/loaders/svg/tvgSvgLoader.cpp +++ b/src/loaders/svg/tvgSvgLoader.cpp @@ -1088,6 +1088,17 @@ static void _handleMaskAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, con } +static void _handleFilterAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + int len = strlen(value); + if (len >= 3 && !strncmp(value, "url", 3)) { + if (style->filter.url) free(style->filter.url); + style->filter.url = _idFromUrl((const char*)(value + 3)); + } +} + + static void _handleMaskTypeAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) { node->node.mask.type = _toMaskType(value); @@ -1166,7 +1177,8 @@ static constexpr struct STYLE_DEF(mask, Mask, SvgStyleFlags::Mask), STYLE_DEF(mask-type, MaskType, SvgStyleFlags::MaskType), STYLE_DEF(display, Display, SvgStyleFlags::Display), - STYLE_DEF(paint-order, PaintOrder, SvgStyleFlags::PaintOrder) + STYLE_DEF(paint-order, PaintOrder, SvgStyleFlags::PaintOrder), + STYLE_DEF(filter, Filter, SvgStyleFlags::Filter) }; @@ -1240,6 +1252,8 @@ static bool _attrParseGNode(void* data, const char* key, const char* value) _handleClipPathAttr(loader, node, value); } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "filter")) { + _handleFilterAttr(loader, node, value); } else { return _parseStyleAttr(loader, key, value, false); } @@ -1343,6 +1357,108 @@ static bool _attrParseSymbolNode(void* data, const char* key, const char* value) } +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} boxTags[] = { + {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(Box, x)}, + {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(Box, y)}, + {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(Box, w)}, + {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(Box, h)} +}; + + +static bool _parseBox(const char* key, const char* value, Box* box, bool (&isPercentage)[4]) +{ + auto array = (unsigned char*)box; + int sz = strlen(key); + for (unsigned int i = 0; i < sizeof(boxTags) / sizeof(boxTags[0]); i++) { + if (boxTags[i].sz - 1 == sz && !strncmp(boxTags[i].tag, key, sz)) { + *(float*)(array + boxTags[i].offset) = _gradientToFloat(nullptr, value, isPercentage[i]); + return true; + } + } + return false; +} + + +static void _recalcBox(const SvgLoaderData* loader, Box* box, bool (&isPercentage)[4]) +{ + auto array = (unsigned char*)box; + for (unsigned int i = 0; i < sizeof(boxTags) / sizeof(boxTags[0]); i++) { + if (!isPercentage[i]) continue; + if (boxTags[i].type == SvgParserLengthType::Horizontal) *(float*)(array + boxTags[i].offset) *= loader->svgParse->global.w; + else *(float*)(array + boxTags[i].offset) *= loader->svgParse->global.h; + } +} + + +static bool _attrParseFilterNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgFilterNode* filter = &node->node.filter; + + _parseBox(key, value, &filter->box, filter->isPercentage); + + if (!strcmp(key, "id")) { + if (node->id && value) free(node->id); + node->id = _copyId(value); + } else if (!strcmp(key, "primitiveUnits")) { + if (!strcmp(value, "objectBoundingBox")) filter->primitiveUserSpace = false; + } else if (!strcmp(key, "filterUnits")) { + if (!strcmp(value, "userSpaceOnUse")) filter->filterUserSpace = true; + } + + return true; +} + + +static void _parseGaussianBlurStdDeviation(const char** content, float* x, float* y) +{ + auto str = *content; + char* end = nullptr; + float deviation[2] = {0, 0}; + int n = 0; + + while (*str && n < 2) { + str = _skipComma(str); + auto parsedValue = strToFloat(str, &end); + if (parsedValue < 0.0f) break; + deviation[n++] = parsedValue; + str = end; + } + + *x = deviation[0]; + *y = n == 1 ? deviation[0] : deviation[1]; +} + + +static bool _attrParseGaussianBlurNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgGaussianBlurNode* gaussianBlur = &node->node.gaussianBlur; + + if (_parseBox(key, value, &gaussianBlur->box, gaussianBlur->isPercentage)) gaussianBlur->hasBox = true; + + if (!strcmp(key, "id")) { + if (node->id && value) free(node->id); + node->id = _copyId(value); + } else if (!strcmp(key, "stdDeviation")) { + _parseGaussianBlurStdDeviation(&value, &gaussianBlur->stdDevX, &gaussianBlur->stdDevY); + } else if (!strcmp(key, "edgeMode")) { + if (!strcmp(value, "wrap")) gaussianBlur->edgeModeWrap = true; + } else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + static SvgNode* _createNode(SvgNode* parent, SvgNodeType type) { SvgNode* node = (SvgNode*)calloc(1, sizeof(SvgNode)); @@ -1504,6 +1620,48 @@ static SvgNode* _createSymbolNode(SvgLoaderData* loader, SvgNode* parent, const } +static SvgNode* _createGaussianBlurNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength, parseAttributes func) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::GaussianBlur); + if (!loader->svgParse->node) return nullptr; + SvgGaussianBlurNode& gaussianBlur = loader->svgParse->node->node.gaussianBlur; + + loader->svgParse->node->style->display = false; + + gaussianBlur.stdDevX = 0.0f; + gaussianBlur.stdDevY = 0.0f; + gaussianBlur.box = {0.0f, 0.0f, 1.0f, 1.0f}; + for (auto& p : gaussianBlur.isPercentage) p = false; + gaussianBlur.hasBox = false; + gaussianBlur.edgeModeWrap = false; + + func(buf, bufLength, _attrParseGaussianBlurNode, loader); + + return loader->svgParse->node; +} + + +static SvgNode* _createFilterNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength, parseAttributes func) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Filter); + if (!loader->svgParse->node) return nullptr; + SvgFilterNode& filter = loader->svgParse->node->node.filter; + + loader->svgParse->node->style->display = false; + + filter.box = {-0.1f, -0.1f, 1.2f, 1.2f}; + for (auto& p : filter.isPercentage) p = false; + filter.filterUserSpace = false; + filter.primitiveUserSpace = true; + + func(buf, bufLength, _attrParseFilterNode, loader); + + if (filter.filterUserSpace) _recalcBox(loader, &filter.box, filter.isPercentage); + + return loader->svgParse->node; +} + + static bool _attrParsePathNode(void* data, const char* key, const char* value) { SvgLoaderData* loader = (SvgLoaderData*)data; @@ -1520,6 +1678,8 @@ static bool _attrParsePathNode(void* data, const char* key, const char* value) _handleClipPathAttr(loader, node, value); } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "filter")) { + _handleFilterAttr(loader, node, value); } else if (!strcmp(key, "id")) { if (value) free(node->id); node->id = _copyId(value); @@ -1582,6 +1742,8 @@ static bool _attrParseCircleNode(void* data, const char* key, const char* value) _handleClipPathAttr(loader, node, value); } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "filter")) { + _handleFilterAttr(loader, node, value); } else if (!strcmp(key, "id")) { if (value) free(node->id); node->id = _copyId(value); @@ -1649,6 +1811,8 @@ static bool _attrParseEllipseNode(void* data, const char* key, const char* value _handleClipPathAttr(loader, node, value); } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "filter")) { + _handleFilterAttr(loader, node, value); } else { return _parseStyleAttr(loader, key, value, false); } @@ -1698,6 +1862,8 @@ static bool _attrParsePolygonNode(void* data, const char* key, const char* value _handleClipPathAttr(loader, node, value); } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "filter")) { + _handleFilterAttr(loader, node, value); } else if (!strcmp(key, "id")) { if (value) free(node->id); node->id = _copyId(value); @@ -1785,6 +1951,8 @@ static bool _attrParseRectNode(void* data, const char* key, const char* value) _handleClipPathAttr(loader, node, value); } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "filter")) { + _handleFilterAttr(loader, node, value); } else { ret = _parseStyleAttr(loader, key, value, false); } @@ -1850,6 +2018,8 @@ static bool _attrParseLineNode(void* data, const char* key, const char* value) _handleClipPathAttr(loader, node, value); } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "filter")) { + _handleFilterAttr(loader, node, value); } else { return _parseStyleAttr(loader, key, value, false); } @@ -1923,6 +2093,8 @@ static bool _attrParseImageNode(void* data, const char* key, const char* value) _handleClipPathAttr(loader, node, value); } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "filter")) { + _handleFilterAttr(loader, node, value); } else if (!strcmp(key, "transform")) { node->transform = _parseTransformationMatrix(value); } else { @@ -2103,6 +2275,8 @@ static bool _attrParseTextNode(void* data, const char* key, const char* value) _handleClipPathAttr(loader, node, value); } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "filter")) { + _handleFilterAttr(loader, node, value); } else if (!strcmp(key, "id")) { if (value) free(node->id); node->id = _copyId(value); @@ -2146,7 +2320,8 @@ static constexpr struct {"polyline", sizeof("polyline"), _createPolylineNode}, {"line", sizeof("line"), _createLineNode}, {"image", sizeof("image"), _createImageNode}, - {"text", sizeof("text"), _createTextNode} + {"text", sizeof("text"), _createTextNode}, + {"feGaussianBlur", sizeof("feGaussianBlur"), _createGaussianBlurNode} }; @@ -2162,7 +2337,8 @@ static constexpr struct {"mask", sizeof("mask"), _createMaskNode}, {"clipPath", sizeof("clipPath"), _createClipPathNode}, {"style", sizeof("style"), _createCssStyleNode}, - {"symbol", sizeof("symbol"), _createSymbolNode} + {"symbol", sizeof("symbol"), _createSymbolNode}, + {"filter", sizeof("filter"), _createFilterNode} }; @@ -3084,6 +3260,10 @@ static void _copyAttr(SvgNode* to, const SvgNode* from) free(to->style->mask.url); to->style->mask.url = strdup(from->style->mask.url); } + if (from->style->filter.url) { + if (to->style->filter.url) free(to->style->filter.url); + to->style->filter.url = strdup(from->style->filter.url); + } //Copy node attribute switch (from->type) { @@ -3554,13 +3734,29 @@ static void _updateComposite(SvgNode* node, SvgNode* root) } +static void _updateFilter(SvgNode* node, SvgNode* root) +{ + if (node->style->filter.url && !node->style->filter.node) { + SvgNode* findResult = _findNodeById(root, node->style->filter.url); + if (findResult) node->style->filter.node = findResult; + } + if (node->child.count > 0) { + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + _updateFilter(*child, root); + } + } +} + + static void _freeNodeStyle(SvgStyleProperty* style) { if (!style) return; - //style->clipPath.node and style->mask.node has only the addresses of node. Therefore, node is released from _freeNode. + //style->clipPath.node/mask.node/filter.node has only the addresses of node. Therefore, node is released from _freeNode. free(style->clipPath.url); free(style->mask.url); + free(style->filter.url); free(style->cssClass); if (style->fill.paint.gradient) { @@ -3761,6 +3957,9 @@ void SvgLoader::run(unsigned tid) _updateComposite(loaderData.doc, loaderData.doc); if (defs) _updateComposite(loaderData.doc, defs); + _updateFilter(loaderData.doc, loaderData.doc); + if (defs) _updateFilter(loaderData.doc, defs); + _updateStyle(loaderData.doc, nullptr); if (defs) _updateStyle(defs, nullptr); diff --git a/src/loaders/svg/tvgSvgLoaderCommon.h b/src/loaders/svg/tvgSvgLoaderCommon.h index 53c62d70..ec7614cf 100644 --- a/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/src/loaders/svg/tvgSvgLoaderCommon.h @@ -26,6 +26,27 @@ #include "tvgCommon.h" #include "tvgArray.h" +struct Box +{ + float x, y, w, h; + + void intersect(const Box& box) + { + auto x1 = x + w; + auto y1 = y + h; + auto x2 = box.x + box.w; + auto y2 = box.y + box.h; + + x = x > box.x ? x : box.x; + y = y > box.y ? y : box.y; + w = (x1 < x2 ? x1 : x2) - x; + h = (y1 < y2 ? y1 : y2) - y; + + if (w < 0.0f) w = 0.0f; + if (h < 0.0f) h = 0.0f; + } +}; + struct SvgNode; struct SvgStyleGradient; @@ -54,6 +75,8 @@ enum class SvgNodeType Mask, CssStyle, Symbol, + Filter, + GaussianBlur, Unknown }; @@ -142,6 +165,7 @@ enum class SvgStyleFlags PaintOrder = 0x10000, StrokeMiterlimit = 0x20000, StrokeDashOffset = 0x40000, + Filter = 0x80000 }; constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b) @@ -363,6 +387,23 @@ struct SvgTextNode float fontSize; }; +struct SvgGaussianBlurNode +{ + float stdDevX, stdDevY; + Box box; + bool isPercentage[4]; + bool hasBox; + bool edgeModeWrap; +}; + +struct SvgFilterNode +{ + Box box; + bool isPercentage[4]; + bool filterUserSpace; + bool primitiveUserSpace; +}; + struct SvgLinearGradient { float x1, y1, x2, y2; @@ -456,12 +497,19 @@ struct SvgStyleStroke SvgDash dash; }; +struct SvgFilter +{ + char *url; + SvgNode* node; +}; + struct SvgStyleProperty { SvgStyleFill fill; SvgStyleStroke stroke; SvgComposite clipPath; SvgComposite mask; + SvgFilter filter; int opacity; SvgColor color; char* cssClass; @@ -498,6 +546,8 @@ struct SvgNode SvgCssStyleNode cssStyle; SvgSymbolNode symbol; SvgTextNode text; + SvgFilterNode filter; + SvgGaussianBlurNode gaussianBlur; } node; ~SvgNode(); }; @@ -550,9 +600,4 @@ struct SvgLoaderData SvgNode* currentGraphicsNode = nullptr; }; -struct Box -{ - float x, y, w, h; -}; - #endif diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 2cf5b3b2..ae4894ff 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -41,7 +41,7 @@ static Scene* _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, static inline bool _isGroupType(SvgNodeType type) { - if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath || type == SvgNodeType::Symbol) return true; + if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath || type == SvgNodeType::Symbol || type == SvgNodeType::Filter) return true; return false; } @@ -288,6 +288,66 @@ static Paint* _applyComposition(SvgLoaderData& loaderData, Paint* paint, const S } +static Paint* _applyFilter(SvgLoaderData& loaderData, Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath) +{ + auto filterNode = node->style->filter.node; + if (!filterNode || filterNode->child.count == 0) return paint; + auto& filter = filterNode->node.filter; + + auto scene = Scene::gen(); + + Box bbox{}; + paint->bounds(&bbox.x, &bbox.y, &bbox.w, &bbox.h, false); + Box clipBox = filter.filterUserSpace ? filter.box : Box{bbox.x + filter.box.x * bbox.w, bbox.y + filter.box.y * bbox.h, filter.box.w * bbox.w, filter.box.h * bbox.h}; + auto primitiveUserSpace = filter.primitiveUserSpace; + auto sx = paint->transform().e11; + auto sy = paint->transform().e22; + + auto child = filterNode->child.data; + for (uint32_t i = 0; i < filterNode->child.count; ++i, ++child) { + if ((*child)->type == SvgNodeType::GaussianBlur) { + auto& gauss = (*child)->node.gaussianBlur; + + auto direction = gauss.stdDevX > 0.0f ? (gauss.stdDevY > 0.0f ? 0 : 1) : (gauss.stdDevY > 0.0f ? 2 : -1); + if (direction == -1) continue; + + auto stdDevX = gauss.stdDevX; + auto stdDevY = gauss.stdDevY; + if (gauss.hasBox) { + auto gaussBox = gauss.box; + auto isPercent = gauss.isPercentage; + if (primitiveUserSpace) { + if (isPercent[0]) gaussBox.x *= loaderData.svgParse->global.w; + if (isPercent[1]) gaussBox.y *= loaderData.svgParse->global.h; + if (isPercent[2]) gaussBox.w *= loaderData.svgParse->global.w; + if (isPercent[3]) gaussBox.h *= loaderData.svgParse->global.h; + } else { + stdDevX *= bbox.w; + stdDevY *= bbox.h; + if (isPercent[0]) gaussBox.x = bbox.x + gauss.box.x * bbox.w; + if (isPercent[1]) gaussBox.y = bbox.y + gauss.box.y * bbox.h; + if (isPercent[2]) gaussBox.w *= bbox.w; + if (isPercent[3]) gaussBox.h *= bbox.h; + } + clipBox.intersect(gaussBox); + } else if (!primitiveUserSpace) { + stdDevX *= bbox.w; + stdDevY *= bbox.h; + } + scene->push(SceneEffect::GaussianBlur, 1.25f * (direction == 2 ? stdDevY * sy : stdDevX * sx), direction, gauss.edgeModeWrap, 55); + } + } + + scene->push(paint); + + auto clip = Shape::gen(); + clip->appendRect(clipBox.x, clipBox.y, clipBox.w, clipBox.h); + clip->transform(paint->transform()); + scene->clip(clip); + + return scene; +} + static Paint* _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath, bool clip) { SvgStyleProperty* style = node->style; @@ -352,7 +412,8 @@ static Paint* _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg vg->strokeFill(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity); } - return _applyComposition(loaderData, vg, node, vBox, svgPath); + auto p = _applyFilter(loaderData, vg, node, vBox, svgPath); + return _applyComposition(loaderData, p, node, vBox, svgPath); } @@ -596,7 +657,8 @@ static Paint* _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const if (node->transform) m = *node->transform * m; picture->transform(m); - return _applyComposition(loaderData, picture, node, vBox, svgPath); + auto p = _applyFilter(loaderData, picture, node, vBox, svgPath); + return _applyComposition(loaderData, p, node, vBox, svgPath); } @@ -782,7 +844,8 @@ static Paint* _textBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, c _applyTextFill(node->style, text, vBox); - return _applyComposition(loaderData, text, node, vBox, svgPath); + auto p = _applyFilter(loaderData, text, node, vBox, svgPath); + return _applyComposition(loaderData, p, node, vBox, svgPath); } @@ -823,7 +886,9 @@ static Scene* _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, } } scene->opacity(node->style->opacity); - return static_cast(_applyComposition(loaderData, scene, node, vBox, svgPath)); + + auto p = _applyFilter(loaderData, scene, node, vBox, svgPath); + return static_cast(_applyComposition(loaderData, p, node, vBox, svgPath)); }