I'd like to take advantage of symmetry in my SVG designs, but most viewers (including Firefox and Chromium browsers) render gaps / lines where the numbers clearly tell me there shouldn't be any. Using zoom to render those designs bigger reduces the amount and thickness of those wrongly rendered lines.
Am I misunderstanding the svg format?
Is it too resource intensive to implement svg rendering mathematically correct?
Or does nobody care for such edge cases?
An example to better illustrate what I'm talking about:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="98" height="98" fill="red" fill-opacity="0.7">
<g id="2">
<g id="4">
<path id="p" d="m49,35c-3,0 -4.575713,0.605785 -6.9934,1.888339L31.330807,18.397364 37,15C38,7.76 38,6 39,0.7 39.7,0 44,0 49,0Z"/>
<use xlink:href="#p" transform="rotate(-60, 49, 49)"/>
<use xlink:href="#p" transform="translate(98) scale(-1, 1) rotate(60, 49, 49)"/>
</g>
<use xlink:href="#4" transform="translate(98) scale(-1, 1)"/>
</g>
<use xlink:href="#2" transform="rotate(180, 49, 49)"/>
</svg>
I found here:
If two partially opaque shapes overlap, can I show only one shape where they overlap?
that it's possible to use filters to make overlapping shapes behave as if they weren't when it comes to opacity, but it still bugs me that workarounds like that are required. It increases complexity, file-size and decreases compatibility with viewers that don't implement those filters.
Application of that workaround to the example above:
<svg width="98" height="98" fill-opacity=".7" fill="red" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<filter id="f">
<feComponentTransfer>
<feFuncA type="table" tableValues="0 .7 .7" />
</feComponentTransfer>
</filter>
<g filter="url(#f)">
<g id="2">
<g id="4">
<path id="p" d="m51 35c-3-0-6 0-10 3l-15-15 11-7c2-8 1-8 2-14 0.7-0.7 7-0.7 12-0.7"/>
<use transform="rotate(-60 49 49)" xlink:href="#p"/>
<use transform="translate(98) scale(-1 1) rotate(60 49 49)" xlink:href="#p"/>
</g>
<use transform="translate(98) scale(-1 1)" xlink:href="#4"/>
</g>
<use transform="rotate(180 49 49)" xlink:href="#2"/>
</g>
</svg>
Think about how any sort of rendering engine has to render your SVG. It steps through the SVG, drawing one element at a time.
Unless that shape is a line or rectangle that hits pixel boundaries exactly, it needs to smooth the edges of that shape. It does that using a technique called ant-aliasing. It draws semi-transparent pixels to approximate an edge that only partially covers a pixel.
For example, if a shape covers exactly half a pixel, the renderer will draw the colour into that pixel with 50% alpha.
It then moves on and draws the next shape. Even if that shape has a mathematically congruent border, you probably won't end up with a perfect join.
Here's why.
Picture three adjacent pixels where the edge of a shape passes exactly halfway through the centre pixel.
+--------+--------+--------+
First shape drawn: | 100% | 50% | 0% |
+--------+--------+--------+
The percentages here represent the amount of the shape's colour that is drawn into each pixel. 100% in the left pixel. 50% colour (alpha) in the middle pixel. And no colour drawn into the right pixel.
Now imagine a second shape that shares a border with the first shape. You might imagine that the following is what happens.
+--------+--------+--------+
First shape drawn: | 100% | 50% | 0% |
+--------+--------+--------+
Second shape drawn: | 0% | 50% | 100% |
+--------+--------+--------+
Resulting image: | 100% | 100% | 100% |
+--------+--------+--------+
But that isn't what happens. The first shape has already been rendered out as pixels. The renderer has no memory about the shape of previous things it has drawn. It only has the colour of the previously rendered pixels to go by.
When it goes to draw the middle pixel, in either the first or second step, it blends the 50% new colour with what the pixel value already is. The formula will be roughly the following:
result = 0.5 * old_pixel_colour + 0.5 * new_pixel_colour
So, for example, let's take the pixel percentage examples from above and imagine we are drawing two red shapes onto a white background.
After the first shape is drawn, the pixels should look something like this.
rgb(255, 0, 0) rgb(255, 128, 128) rgb(255, 255, 255)
[0 * bg + 1.0 * red] [0.5 * bg + 0.5 * 50%_red] [1.0 * bg + zero_red]
Where bg represents the white background colour the pixels start with. And 50%_red means the 50% transparent red that antialiasing is using to represent a half-covered pixel.
After the second pass, the pixels will look something like this:
rgb(255, 0, 0) rgb(255, 192, 192) rgb(255, 0, 0)
[1.0 * first + no_red] [0.5 * first + 0.5 * 50%_red] [0 * first + 1.0 * red]
Where first represents the colour of the pixel after the first shape is drawn. I hope this makes sense.
Or in terms of percentage of colour (red).
+--------+--------+--------+
First shape drawn: | 100% | 50% | 0% |
+--------+--------+--------+
Second shape drawn: | 0% | 50% | 100% |
+--------+--------+--------+
Resulting image: | 100% | 75% | 100% |
+--------+--------+--------+
So I hope you can see why those border pixels can end up showing a faint white line. It's due to the fact that you are blending two layers of antialiased pixels. It's actually a light red line.
Theretically, a renderer could analyse the pixel coverage of a stack of shapes. But that is mathematically very complex. It would slow down the rendering process enormously.
The general explanation how anti-aliasing works has been given by Paul LeBeau. It is notable that you are able to turn this algorithm off with the shape-rendering presentation attribute set to crispEdges. The renderer will try to draw every pixel either fully opaque or fully transparent:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="98" height="98" fill="red" fill-opacity="0.7">
<g id="2" shape-rendering="crispEdges">
<g id="4">
<path id="p" d="m49,35c-3,0 -4.575713,0.605785 -6.9934,1.888339L31.330807,18.397364 37,15C38,7.76 38,6 39,0.7 39.7,0 44,0 49,0Z"/>
<use xlink:href="#p" transform="rotate(-60, 49, 49)"/>
<use xlink:href="#p" transform="translate(98) scale(-1, 1) rotate(60, 49, 49)"/>
</g>
<use xlink:href="#4" transform="translate(98) scale(-1, 1)"/>
</g>
<use xlink:href="#2" transform="rotate(180, 49, 49)"/>
</svg>
This might be helpful, depending on the use case, or not. Edges will appear "ragged", but that may be acceptable.
But that is not the end of the problems. In an ideal environment, the renderer would always decide that a pixel that sits on top of a "seam" either belongs to one side or the other. But in the real world of discrete numbers, it is possible it decides the pixel belongs to neither, and leave it transparent.
Especially if you have one or more transformations applied to a shape, an application might decide it has to round numbers multiple times to compute the final rendering, and rounding errors could add up. (In my experience, Chrome is more prone to such effects than other browsers.)
And finally, the specification leaves renderers with a lot of leeway in what they "might" (or not) do with that attribute value:
To achieve crisp edges, the user agent might turn off anti-aliasing for all lines and curves or possibly just for straight lines which are close to vertical or horizontal. Also, the user agent might adjust line positions and line widths to align edges with device pixels.
The conclusion, unfortunately, is that every renderer uses its own optimization strategy, and you will have no guarantee where you end up.
The blending of semitransparent lines on top of each other behaves differently depending on the used stroke width when viewed in Chrome or Firefox.
Example:
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" version="1.1">
<path d="M 0 0 L 200 200 L 0 200 L 200 200 L 0 200 L 200 0" fill="none" stroke="#000000" stroke-width="0.80" stroke-opacity="0.75" shape-rendering="geometricPrecision"
</svg>
The line at the bottom is drawn three times on top of each other, therefore it should appear darker when blended. With a stroke width of <= 0.8, it behaves as expected. A width > 0.8 changes all lines to be uniform in color. Note that the behavior is also dependent on the zoom level used in the browser.
stroke-width="0.8"
stroke-width="0.81"
stroke-width="5"
The problem I'm facing is that I have a lot of overlapping semitransparent lines in my application and the resulting image should implicitly highlight which paths have been used more often than others (Example in codepen):
correct with stroke-width="0.8"
wrong with stroke-width="0.81"
Is there any approach for correct blending? shape-rendering does have an influence, but doesn't solve the problem.
Sorry for the garish colors, but it most clearly shows the bad blending with this combination.
On the left side of the inner circle, there is a dark line where the blue meets the red. But on the right half of the inner circle, there is not. This is happening in Chrome, FF, and IE11.
Any idea why?
<svg viewBox="0 0 500 500" width="500" height="500">
<circle fill="red" cx="250" cy="215" r="165"/>
<circle fill="#2994FF" cx="250" cy="215" r="100"/>
</svg>
Picture version:
What you think you are seeing is not actually real. It is an optical illusion caused mostly by the contrast change between the two colours. Dark to light and then light to dark.
The layout of the subpixels on your monitor may be contributing as well - I'm not sure on that.
If we create a magnified version of the two edges next to one another, you should see that those strong dark and light borders are not actually there.
I'm new in svg drawing.
Is there any option to colorize an svg <polyline> with gradient? I need to colorize only stroke, but all filters I founded is applied gradient both to stroke and body.
In fact I'm trying to make glow neon effect like this: http://screencloud.net/v/j2hE and it works fine for now with code below when I'm draw a strict line:
<linearGradient id="grad">
<stop offset="0%" stop-color="#ffd95d"/>
<stop offset="100%" stop-color="#ffd95d" stop-opacity="0" />
</linearGradient>
But when I'm drawing a line by circle, it looks like:
http://screencloud.net/v/9M6x (bottom is start and around circle to top where finish). As you can see, gradiend is applied to all polyform, but I need it to be gradiented only line as I draw it.
Is there any option to make neon glow lines like I need it?
For better understanding - I'm trying to get effect similar to default Windows screensaver named "glowing lines".
There is no way to apply a linearGradient along the length of a line's stroke.
The only way you could do it is to draw a sequence of individually coloured line/path segments that slowly fade out.
Using Raphael, I noticed that if I tried to apply a radial fill on a circle using 0.9 and 0.2 as the radial focus points, it fails to draw the radial fill.
paper.circle(50,300,20).attr({"fill":"r(0.5,0.1)#f00-000"});
paper.circle(100,300,20).attr({"fill":"r(0.9,0.2)#f00-000"}); // <-- fails
paper.circle(150,300,20).attr({"fill":"r(0.9,0.3)#f00-000"});
I've set up a fiddle, here, and did a 10x10 grid, and the (0.9,0.2) is the only one that failed.
I'd like to understand why. http://jsfiddle.net/ENMry/2/
This is not problem of Raphael library but most probably of JS SVG rendering. You can repeat the same problem using just JavaScript and SVG markup without Raphael library. See example at jsBin
I changed your example to have bigger circles with 11x11 grid (from 0.0 to 1.0) and put also one row separately on the top to show how is focus point moving. See example at jsBin.
Using browser console (ctrl+shift+J in Chrome) you can inspect DOM elements. The following markup is set for our white element (the 2nd one in the first row):
<radialGradient id="2r_0.1_0.2__f00-_000" fx="0.1" fy="0.2" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">
<stop offset="0%" stop-color="#ff0000" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></stop>
<stop offset="100%" stop-color="#000000" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></stop>
</radialGradient>
<circle cx="175" cy="50" r="30" fill="url(#2r_0.1_0.2__f00-_000)" stroke="#000" opacity="1" fill-opacity="1" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); opacity: 1; fill-opacity: 1;">
</circle>
This MDN Gradients tutorial explains how radial gradient works.
If I understand correctly radial gradient is defined with 5 data and stop markup:
where is point of radiation: for example cx="0.25" cy="0.25" r="0.25". They define position and where the gradient ends. If not set cx and cy are in the midle of the element (same as value 0.5 or 50%; cx=0, cy=0 means top-left), r is 1 or 100%. In our case radiation starts in the middle of the circle and ends on its edge.
stop markup defines which colors should be at certain position.
where is focal (focus) point: in our case fx="0.1" fy="0.2". Standard says: fx and fy define the focal point for the radial gradient. The gradient will be drawn such that the 0% gradient stop is mapped to (fx, fy).
If you imagine a rectange around the circle, fx="0.1" fy="0.2" is somewhere to the left upper corner. This tutorial says: If the focal point is moved outside the circle described earlier, its impossible for the gradient to be rendered correctly, so the spot will be assumed to be on the edge of the circle. If the focal point isn't given at all, its assumed to be at the same place as the center point.
The first circle in the top row has fx="0.0" fy="0.2" and is out of radiation circle. So the spot is set on the edge: left, middle.
The "problematic" white circle has fx="0.1" fy="0.2" and this point is exactly on the edge of (radiation) circle. And rendering somehow fails. The same is for fx="0.9" fy="0.2", fx="0.2" fy="0.1" and fx="0.2" fy="0.9".
Following the same logic we should have another 4 white circles:
fx="0.8" fy="0.1"
fx="0.8" fy="0.9"
fx="0.1" fy="0.8"
fx="0.9" fy="0.8"
but they are rendered correctly.
You can easily see all those "problematic" points if you draw a circle and a grid.
So, I do not know if this is some rounding problem or something else. Anyway, it could be a bug. I found one connected with radial rendering but it is not exactly the same.
BTW, FireFox and IE10 render it without problem.
Note: I submit an issue 322487