问题描述
我有一个很长的项目:一个在浏览器中运行并使用 SVG 和 Javascript 的基本矢量图形工具(也许你已经在别处看到过这样的东西).该工具只有非常有限的一组功能,因为受众受到限制并且目的非常具体,实际上除了明确允许的功能(你知道)之外,不允许有其他功能.一个遗漏的特征是侵蚀(也称为插入或细化)和扩张(开始、加厚、加粗)多边形和其他图形元素.
我多次使用 Adobe Illustrator 的偏移路径效果,有了它,我可以轻松地复制变薄或变厚的图形对象,而不会影响原始对象,因此几乎可以是程序支持的任何对象.
我试图获得与 SVG 相同的功能,但没有成功.
我尝试了以下方法:
- 扩张和侵蚀过滤器,但结果不令人满意(,它可用于从选定对象开始指定距离(或类似距离).SVG:s erode 和 dilate 滤镜与 Illustrator 的 Offset Path Effect 有相似之处,但质量作为矢量操作(相对于位图)高.
当前状态下的 SVG 格式,缺乏对类似 Illustrator 的偏移路径的支持,但可以使用可变宽度的笔触和蒙版获得相同的功能,如上所述
(至少要测试改变笔画宽度!)
(对不起,我的英语不好,让母语者笑到死,但请记住,我属于 94% 的人类,母语不是英语.但幸运的是我们有谷歌翻译.)
I have a long time project: a basic vector graphic tool which runs in browser and uses SVG and Javascript (maybe you have seen somekind of these elsewhere). The tool has only very limited set of functions, because the audience is restricted and the purpose is very specific and in fact there are not allowed to be other functionality than what is explicitly allowed (you know). One missed feature is eroding (also known as inset or thin) and dilating (outset, thicken, bolden) polygons and other graphical elements.
I have used Adobe Illustrator's Offset Path Effect many times and with it I can easily make copies of graphical objects that are thinned or thickened, without affecting the original object, which therefore can be nearly whatever supported by the program.
I have tried to get the same functionality to function in SVG, but without success.
I have tried the following:
- dilate and erode filters, but with not satisfying results (please see the image here)
- Server-side Python's Shapely library, but this workaround is too slow and allows to inset or outset only the basic polygons (description here)
- to find javascript library / code / function, which could alter the path data of graphical elements, but found nothing for javascript
So is there any meaningful way to implement this like Offset Path Effect and how?
This is an "Answer your own question – share your knowledge, Q&A-style" -style answer, but if you have some better answer, please freely use your keyboard.
I have used SO only a few days, so please don't downvote me to the gap. I got an interesting workaround idea to this issue, which is based on variable-width strokes and masks.
But let's start at your (or my) first idea. When we are going to erode (thin) graphical objects in SVG, the obvious first thought is to use erode filter:
But because erode filter (and dilate as well) uses pixel data (the rasterized path) the result is not good looking in all cases. In fact I have never seen a good-looking erode when used to filter vector objects. See the hat and mouth:
The dilate filter has similar problems (the nose is not nice and the baseball cap is scrappy and some other inconsistencies):
All users of Adobe Illustrator know the nice path effects, which can be used to apply various path operations to shapes (objects). These effects doesn't change the original path data, they only create a modified copy of object. One of the most usable is Offset Path Effect, which can be used to set off from the selected object by a specified distance (or something similar). SVG:s erode and dilate filters have similarities with Illustrator's Offset Path Effect, but the quality is as a vector operation (versus bitmap) high.
SVG format in it's current state, lacks support for Illustrator-like Offset Path, but it's possible to get the same functionality using variable-width strokes and masks as noted here.
Let's dive into the world of SVG masks. The dilate (or outset path or thicken) is possible to achieve by simply increasing a stroke width, but erode (or inset path or thinning) needs something more, for example masks. In SVG, any graphics object or 'g' element can be used as an alpha mask for compositing the current object into the background (W3C SVG 1.1 Recommendation).
The above means that not only object's fill can be used as a mask, but also a stroke. And adjusting the width of the stroke of the path that is used as a mask, we can control how much of the current object (into which the mask is applied using mask attribute) is masked out.
Let's get an example of using mask. First we define a path in SVG:s defs element:
<defs>
<path id="head_path" d="M133.833,139.777c1 ...clip... 139.777z"/>
</defs>
When we define a path in defs element, it eliminates the need for repeating the same data in other parts of document. The path's id attribute is used to refer to the path from some point(s) of the document.
Now we can use this path data in mask:
<defs>
...
<mask id="myMask" maskUnits="userSpaceOnUse">
<use xlink:href="#head_path" fill="#FFFFFF" stroke="#000000"
stroke-width="18" stroke-linecap="round" stroke-linejoin="round"/>
</mask>
...
</defs>
The 'use' element references to the 'path' element, whose id is 'head_path' and indicates that the graphical content (in this case only the path data) of 'head_path' element is included at this mask. The stroke-width that is defined on the above 'use' element will be the amount of offset (erode) effect. This amount is masked out of the element in case, which we are going to draw next.
Okay, let's draw first the 'head' without masking to see how beautiful it is:
...
</defs>
<use x="5" y="5" xlink:href="#head_path" fill="#4477FF" stroke="black"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
This produces the following shape:
Now test, what we can achieve using mask:
...
</defs>
<use x="5" y="5" xlink:href="#head_path" fill="#22EE22" stroke="black"
stroke-width="21" stroke-linecap="round" stroke-linejoin="round"
mask="url(#myMask)"/>
The above 'use' element is instructed to use 'myMask' as a mask and 'head_path' as a graphical content. The mask effect is applied to the 'use' element and the following shape is drawn:
If we stack both on top of each, we can compare the original head to the masked head:
Not bad at all? Let's compare the first attempt using SVG erode filtered version to the masked version:
The left one is erode-filtered and the right one is masked to imitate Illustrator-like Offset Path Effect. No odd artifacts in the hat and mouth!
How about dilate then? Is there a way to remove the path unfidelity on nose and scrappiness of the baseball cap? Sure. And the method is really simple but sort of hack. Fortunately there is no need to use masks. Instead we can adjust stroke-width to achieve the desired effect. And because the stroke is already used for boldening, to get a black stroke around boldened shape (if ever wanted), we have to add an additional copy of element with a little wider stroke and lay it out below the boldened shape:
<!-- To get the black stroke -->
<use x="220" y="5" xlink:href="#head_path" fill="red" stroke="black"
stroke-width="24" stroke-linecap="round" stroke-linejoin="round"/>
<!-- To get the boldened shape -->
<use x="220" y="5" xlink:href="#head_path" fill="red" stroke="red"
stroke-width="21" stroke-linecap="round" stroke-linejoin="round"/>
This produces the following shape:
Here both the original shape and the one with our custom Offset Path Effect applied:
How our custom boldening compares to dilate filter:
The left one (above) is dilated using SVG:s dilate filter, the right one is boldened using our custom Offset Path Effect. Pretty nice, I like. Path follows faithfully the original path at the given distance and no signs of scrappiness on baseball cap.
And finally let's pull all the wires together:
The left one (above) uses dilate/erode filter of SVG and the right one uses Illustrator-imitated Offset Path Effect, which is achieved using SVG mask and thicker strokes. Which one you would choose?
Conclusion: We are not forced to use Javascript or other scripts to thicken or thin graphical elements in SVG. Erode and Dilate filters of SVG may have some using purposes, but they are not well suitable for high-quality path "modifications". Masks are a little complicated to use, but after few experiments you get familiar with them. I really hope that SVG in the future would support Offset Path Effect natively, without using this like hacks.
I jsfiddled the shapes used in these examples for you to play with filters and masks: http://jsfiddle.net/7Y4am/
(Test at least to change stroke widths!)
(Sorry my bad English, which get native speakers to laugh until die, but please remember, I belong to the 94% of humanity, who does not speak English natively. But fortunately we have Google Translate.)
这篇关于如何在不使用 Javascript 或膨胀/腐蚀过滤器的情况下在 SVG 中实现偏移路径效果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!