I saw the following svg shape when i was trying to understand fill-rule in SVG
<div class="contain-demo">
<svg width="250px" height="250px" viewBox="0 0 250 250">
<desc>Yellow star with intersecting paths to demonstrate evenodd value.</desc>
<polygon fill="#F9F38C" fill-rule="evenodd" stroke="#E5D50C" stroke-width="5" stroke-linejoin="round" points="47.773,241.534 123.868,8.466 200.427,241.534 7.784,98.208 242.216,98.208 " />
</svg>
</div>
Please note the following:
The SVG has just one path.
The SVG has intersecting points.
if i change fill-rule="nonzero" the entire SVG get the fill.
Currently with fill-rule="evenodd" applied the SVG's central area does't get the fill.
Why is it that with fill-rule="evenodd" the central portion of the star SVG is not filled ?
I did read the spec for fill-rule="evenodd"
This value determines the "insideness" of a point in the shape by
drawing a ray from that point to infinity in any direction and
counting the number of path segments from the given shape that the ray
crosses. If this number is odd, the point is inside; if even, the
point is outside.
But i still don't understand why when i apply fill-rule="evenodd", the middle of the star is not filled. Can somebody please explain this ?
This is how the mechanism works. Pick a point in the center, draw lines to infinity in any direction - they always cross an even number of path segments -which means that they are "outside" and their areas don't get filled.
Pick a point in the filled triangles - if you draw lines to infinity in any direction - they always cross an odd number of path segments and thus, they are "inside" and their area should be filled.
Related
I have a couple of SVG images that I want to paste together to make a big graphic.
First, let me present you with my problem:
This is one of the symbols, placed in a grid. The grid is, for convenience, with unit-less 100 distance from line to line.
If I render it to pdf, it looks like this instead:
Those symbols have a completely wrong size for the grid (They are much larger, mostly) and they are badly positioned if I use them raw.
So my treatment is, I scale them and position them correctly in relation to the grid, then I make a rectangle around them that encompasses them completely and makes the symbol-handling easier.
That rectangle is perfectly fitting to my grid. In my case, for this symbol, it is a rectangle encompassing the six squares around the symbol. I did this, because the symbol can be rotated by the user and the rotation is done from symbol point of view; any transform after the rotation is from the rotated point of view. So I made an attempt to de-couple the transformations by wrapping them.
Finally, I move the rectangle to a user-defined place and rotate it as the user wants to have it. So far, so well, it was extensively tested in google chrome and works reliably. In Google Chrome.
Now I wanted to translate it to pdf for printing. And after conversion, the symbol is placed in the wrong position.
I am guessing (as I made several tests) that the error is somewhere in my header.
Could someone of you please check the headers that I add and tell me if and where I did something wrong? For example there is a view box starting at -100, otherwise the symbol would be cut and wrongly placed. Is there another way to "rectangularize" any arbitrary symbol? Or is it generally the wrong way to do these kinds of things?
Here are the changes that I added around the svg symbol code. Innermost changes are applied first.
<!-- move the rectangle to the right place and rotate it as the user wishes-->
<g transform="translate(300.0, 400.0) rotate(0,150.0, 50.0) ">
<!-- a rectangle around the symbol, perfectly fitting to the grid-->
<svg x="0" y="0" width="300.0" height="200.0" version = '2.0'
xmlns="http://www.w3.org/2000/svg" viewBox="-100.0 0 300.0 200.0">
<!-- scaled to correct size, placed inside the grid as it should be -->
<g transform = "translate(-10.000, 73.614) scale(0.229,0.229)" >
<!-- original symbol, wrong size -->
<svg width="523" height="230" version = '2.0' xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 523 230">
.... lots of svg paths here ...
</svg>
</g>
</svg>
</g>
I try to understand how SVG programs (like browsers) draw a shape by the given paths. I struggle to understand how a path is drawn and one clips part of a shape.
For example, consider the letter Å, and A with a top ring. The SVG code is like
<svg viewBox="0 0 1800 1800" xmlns="http://www.w3.org/2000/svg">
<path
d="
M484 0l-42 137h-245l-47 -137h-147l237 664h161l234 -664h-151z
M426 822q0 -47 -24 -71.5t-80 -24.5q-104 0 -104 96q0 46 25.5 71t78.5 25q56 0 80 -25t24 -71z
M319 515h-1l-85 -264h169z
M374 822q0 51 -52 51q-50 0 -50 -51q0 -49 50 -49q52 0 52 49z
" />
</svg>
JSFIDDLE
First line: draws the body of an A.
Second line: draws a top circle.
Third line: clips a triangle from the first line.
Fourth line: clips a small circle from the second line.
My question is: how do SVG programs understand to draw a shape by the second line, but clip a shape from an existing shape?
Obviously the answer is: if the path is within another path, it clips otherwise it draws.
There are two reasons that I think this not the whole picture:
It needs huge calculations to find if a path is within another path.
The order of lines is not important (the clipping path does not necessarily come after the drawing path).
This is, of course, not limited to SVG, as other vector formats such as EPS does the same.
To add a pragmatic perspective, please read the question as: how can we parse (in any programming language) the above d element to find out which path is drawing (black) and which is clipping (white) out of the four paths given in the above SVG?
Broadly speaking, you don't parse the paths at all.
Instead you 'scan convert' each path to a series of rectangles, at the current resolution. Each reactangle may be as little as one pixel high, and there may be more than one rectangle at a given y value.
Do this for both the path to be filled or stroked and the path to apply as a clip, then intersect the series of rectangles. This is, obviously, a much simpler task. Then fill the rectangles.
This does, of course, produce a result which is only correct at a given resolution. Change the resolution and you get a different series of rectangles. However it produces the correct output at a decent speed. Intersecting two arbitrary paths to produce a new arbitrary path is obviously a much more complex task, and for the purpose of drawing the result, not one we need to perform.
In the next examples I'm using the path for the letter A in your example.
In the first svg element the letter A is drawn from right to left while the hole of is drawn in the opposite dirrection: you get the "clipping".
In the second example I've reversed the part that is drawing the hole. Now this part is drawn in the same direction as the main part of the letter A. Now you won't get the "clipping"
In the third example I'm using the reversed path as before but I'm adding fill-rule="evenodd" Now the hole is clipped since the "fill-rule attribute is a presentation attribute defining the algorithm to use to determine the inside part of a shape".
svg{width:30%;border:solid}
<svg viewBox="-40 -40 900 900" xmlns="http://www.w3.org/2000/svg">
<path
d="
M484 0l-42 137h-245l-47 -137h-147l237 664h161l234 -664h-151z
M319 515h-1l-85 -264h169z
" />
</svg>
<svg viewBox="-40 -40 900 900" xmlns="http://www.w3.org/2000/svg">
<path
d="
M484 0l-42 137h-245l-47 -137h-147l237 664h161l234 -664h-151z
M319,515L402,251L233,251L318,515L319,515z
" />
</svg>
<svg viewBox="-40 -40 900 900" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="
M484 0l-42 137h-245l-47 -137h-147l237 664h161l234 -664h-151z
M319,515L402,251L233,251L318,515L319,515z
" />
</svg>
Whether a given path is "filled" or "clipped" depends on the "winding" algorithm being used, which is determined by the SVG fill-rule property, which defaults to nonzero.
For the nonzero mode:
This rule determines the "insideness" of a point on the canvas by drawing a ray from that point to infinity in any direction and then examining the places where a segment of the shape crosses the ray. Starting with a count of zero, add one each time a path segment crosses the ray from left to right and subtract one each time a path segment crosses the ray from right to left. After counting the crossings, if the result is zero then the point is outside the path. Otherwise, it is inside. The following drawing illustrates the nonzero rule:
The nonzero default is why you'll often hear that rotational direction is important, because clockwise creates fills and counterclockwise creates clips. (This is how it works in GeoJSON as well.)
For the evenodd mode:
This rule determines the "insideness" of a point on the canvas by drawing a ray from that point to infinity in any direction and counting the number of path segments from the given shape that the ray crosses. If this number is odd, the point is inside; if even, the point is outside. The following drawing illustrates the evenodd rule:
The evenodd mode is a simpler to understand for basic shapes with holes, but not as flexible for clipping arbitrary chunks out that may not be completely isolated as islands.
Here's a great article called Understanding the SVG fill-rule Property that explains it further with code examples.
It seems that specifying line coordinates of an SVG line in percentages makes that coordinate exist in viewport coordinate system instead of the user coordinate system established by a viewBox. To me that sounds strange, especially after reading the specs.
In the example below, the green line is defined by user space coordinates while the blue line's y coordinate is in percentage units (50%). When the button is clicked, the viewBox is applied - the green line is scaled properly while the blue line is not ... What is going on there?
The spec says:
For any y-coordinate value or height value expressed as a percentage of the SVG viewport, the value to use must be the percentage, in user units, of the height parameter of the ‘viewBox’ applied to that viewport. If no ‘viewBox’ is specified, then the value to use must be the percentage, in user units, of the height of the SVG viewport.
UPDATE: I grouped all elements in the svg and supplied transform="scale(0.5 0.5)" and the percentage coordinate worked as expected. I start suspecting that the viewBox only transforms coordinates specified in user units and not in explicit units. But I would think the percentage should not be considered as an explicit unit as it is not really physical value. And it would contradict the spec excerpt from above. So what is it ?
function myFunction(){
document.getElementById("maxi").setAttribute("viewBox","0,0,492,124");
}
<svg id="maxi" version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" width="246" height="62" font-size="23px" xml:space="preserve" >
<line id="greenline" x1="0" y1="31" x2="232" y2="31" stroke="#00FF00" stroke-width="4"/>
<line id="blueline" x1="0" y1="50%" x2="232" y2="50%" stroke="#0000FF"/>
<path class="cutContour" fill="none" stroke="#EC008C" stroke-miterlimit="3.8637" d="M6.8,2.3H225
c2.3,0,4.3,1.9,4.3,4.3v48.2c0,2.3-1.9,4.3-4.3,4.3H6.8c-2.3,0-4.3-1.9-4.3-4.3V6.6C2.5,4.2,4.4,2.3,6.8,2.3z"/>
</svg>
<input type="button" value="Click Me" onClick="myFunction();">
I'm not seeing anything unusual about the behaviour you're describing. See Codepen at https://codepen.io/MSCAU/pen/JapPQd.
The blue line is staying in the (vertical) centre of the SVG as its Y-coordinates are expressed as 50%. When the viewBox changes value, this is not affected. Only its X values are expressed in absolute terms so the line gets halved when the viewBox is made explicit.
The green line is getting displaced in X- and Y-axes when the button is clicked as the coordinate system is changing - in your example doubling - and its coordinates are expressed in absolute terms.
I changed the colours and stroke-widths to make it clearer what's going on. I've also commented out your PATH and put a (dotted) border round the SVG for clarity. I have also added a handy reset button:
function reset() {
document.getElementById("maxi").removeAttribute("viewBox");
}
I would like to learn SVG, and am trying to learn how the same image can be rendered by using either the point (with polygon) or by dynamically by paths (path).
I would like a few examples of the SAME polygon (triangle, square, and pentagon are enough to begin) in BOTH SVG polygon AND SVG path, so that I can compare the code. I can find individual images drawn by either, but none that are the SAME.
It's trivial: You can basically take the points attribute of a polygon and turn it into a path's d attribute by prepending M and appending z.
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<polygon points="20,20 100,20 100,100 30,110"/>
<path d="M20,20 100,20 100,100 30,110z" fill="green" transform="translate(100,0)"/>
</svg>
Both can create shapes.
Polygon will automatically close the shape for
you (by returning to the first point) after drawing at least three
sides, and is composed of a series of connected straight lines, which
means it does not scale well.
Paths can use straight OR curved lines, and do not auto-close the
shape for you. Path is probably the most powerful basic shape element
in SVG.
Source
Below is a screen capture of SVG image which is rendered on Chrome 22.0.1229.79 Mac. The original svg is on jsfiddle:
http://jsfiddle.net/LGBk5/
The left image is made using SVG:s dilate and erode filters. The right one is made using Illustrator's Offset Path effect.
The left one has problems: the border at the bottom is distorted and curves are not as smooth. Meanwhile the thick black border is the same in both.
Has my SVG some parameter wrong or are the dilate and erode filters so seemingly buggy?
EDIT: The purpose is to make paths thinner or thicker in SVG, but according to this example, the erode/dilate is not stable enough to rely.
Filter effects are done on pixel data (the rasterized path), while the path offset operation in Illustrator (similar in Inkscape) is done using the original path data (or vector data if you wish).
The former is like using photoshop filters, the latter is creating new paths by using the existing path. They're both stable, but they're not the same operation.
Illustrator's path offset and SVG filters erode/dilate are different operations.
This erode filter is working as designed - there is no bug here. For every pixel in the input image, the filter looks at the maximum RGBA values in a rectangle around it (the radius). In a normal image this tends to generate "rectangular highlights" for want of a better term. And results in weird artifacts when applied to curved draw paths. From the spec:
The dilation (or erosion) kernel is a rectangle with a width of
2*x-radius and a height of 2*y-radius. In dilation, the output pixel
is the individual component-wise maximum of the corresponding R,G,B,A
values in the input image's kernel rectangle. In erosion, the output
pixel is the individual component-wise minimum of the corresponding
R,G,B,A values in the input image's kernel rectangle.
So, imagine that that single pixel at the pointy end of your shape. With a 10 pixel "radius" in your filter (and remember that radius is an incredibly misleading term because it's using a rectangle not a circle!). Let's say it's at 100,100, for arguments sake. When the filter processes values for pixels in the range 90,110 to 110,110, its dilation radius is going to detect that pixel at 100,100 and paint all pixels in that range black. And just like that, your nice pointy end has been dilated into a straight line.
Note that you can achieve most offset path effects using nested strokes (some of which have masks to trim the inside or outside of the path.
For example, here is the OP's path reimplemented this way:
<!-- Left drawing is made using erode and dilate -->
<!-- Right one is made by Illustrator's Offset Path -->
<svg width="612" height="792" viewBox="0 0 612 792" xmlns="http://www.w3.org/2000/svg">
<defs>
<path id="curve" d="M21.552,74.438c2.531-28.879,73.668-52.734,102.629-53.971
c32.164-1.373,74.764,23.746,61.766,53.197c-32,72.5-84.236-59.594-109.5-29.5c-23.367,27.833,55.4,142.969,55.4,142.969
S18.109,113.708,21.552,74.438z"/>
<mask id="inner">
<use xlink:href="#curve" fill="white"/>
</mask>
</defs>
<!-- this black outermost line -->
<use x="10" y="10" xlink:href="#curve" style="stroke:black;stroke-width:26;stroke-linejoin:miter;stroke-miterlimit:10"></use>
<!-- thick red outer line -->
<use x="10" y="10" xlink:href="#curve" style="stroke:#f00;stroke-width:24;stroke-linejoin:miter;stroke-miterlimit:10"></use>
<!-- innermost black thin line, with green fill -->
<use x="10" y="10" xlink:href="#curve" style="fill:#1CFF00;stroke:black;stroke-width:32;stroke-linejoin:miter;stroke-miterlimit:10" mask="url(#inner)"></use>
<!-- blue inner stroke -->
<use x="10" y="10" xlink:href="#curve" style="fill:none;stroke:#5555FF;stroke-width:30;stroke-linejoin:miter;stroke-miterlimit:10" mask="url(#inner)"></use>
<!-- lastly, the black line -->
<use x="10" y="10" xlink:href="#curve" style="fill:none;stroke:black;stroke-width:10;stroke-linejoin:miter;stroke-miterlimit:10"></use>
<g transform="translate(210,10)">
<path fill="#FF0000" stroke="#231F20" d="M126.273,201.917c-1.188-0.766-29.407-19.044-57.679-42.532c-41.739-34.676-60.31-60.754-58.441-82.068
c1.575-17.974,18.042-34.105,48.943-47.945c21.673-9.707,48.782-16.997,65.925-17.729c1.023-0.043,2.057-0.065,3.096-0.065
c26.722,0,55.103,13.789,67.484,32.787c7.866,12.07,9.101,25.736,3.476,38.482c-8.697,19.704-20.608,29.697-35.403,29.702
c-0.002,0-0.007,0-0.01,0C144.382,112.551,127.62,95,111.407,78.028c-7.054-7.385-18.575-19.446-23.912-21.338
c-1.086,2.002-6.186,15.821,20.666,67.477c16.226,31.214,35.475,59.438,35.668,59.72l35.977,52.589L126.273,201.917z"/>
<path fill="#5555FF" stroke="#231F20" stroke-width="10" stroke-miterlimit="10" d="M22.939,78.438
c2.531-28.879,73.668-52.734,102.629-53.971c32.164-1.373,74.764,23.746,61.766,53.197c-32,72.5-84.237-59.594-109.5-29.5
c-23.366,27.833,55.401,142.969,55.401,142.969S19.497,117.709,22.939,78.438z"/>
<path fill="#00FF00" stroke="#231F20" d="M79.986,131.678C38.498,95.796,38.41,81.397,38.549,79.807c0.289-3.29,5.843-10.151,19.371-17.933
C57.676,78.899,64.972,101.816,79.986,131.678L79.986,131.678z M163.665,84.044c-7.09,0-22.461-16.091-31.646-25.706
c-5.867-6.143-11.433-11.969-16.966-16.846c4.324-0.776,8.128-1.238,11.184-1.368c0.621-0.027,1.249-0.04,1.88-0.04
c16.911,0,36.471,8.903,43.603,19.846c3.317,5.089,2.508,8.623,1.278,11.408C168.884,80.659,165.163,84.043,163.665,84.044
L163.665,84.044z"/>
</g>
</svg>