Reputation: 285
I have several SVG icons for social media links. I want them to have a white background. With help from the community, I found out that it can be done by adding to SVG code <path fill="white" d="XXX">
where XXX equals to the first part of the d=""
code, up until the first m
. For example, there's the following SVG code:
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill='rgb(255, 64, 0)' viewBox="0 0 24 24"><path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm3.445 17.827c-3.684 1.684-9.401-9.43-5.8-11.308l1.053-.519 1.746 3.409-1.042.513c-1.095.587 1.185 5.04 2.305 4.497l1.032-.505 1.76 3.397-1.054.516z"/></svg>
To fill the inside with white color the code needs to be changed like so:
<svg xmlns="http://www.w3.org/2000/svg" width='24' height='24' fill='rgb(255, 64, 0)' viewBox="0 0 24 24">
<path fill="white" d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12z"/>
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm3.445 17.827c-3.684 1.684-9.401-9.43-5.8-11.308l1.053-.519 1.746 3.409-1.042.513c-1.095.587 1.185 5.04 2.305 4.497l1.032-.505 1.76 3.397-1.054.516z"/>
</svg>
That's what was added:
<path fill="white" d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12z"/>
This method works fine until there's an SVG like this one:
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M15.233 5.488c-.843-.038-1.097-.046-3.233-.046s-2.389.008-3.232.046c-2.17.099-3.181 1.127-3.279 3.279-.039.844-.048 1.097-.048 3.233s.009 2.389.047 3.233c.099 2.148 1.106 3.18 3.279 3.279.843.038 1.097.047 3.233.047 2.137 0 2.39-.008 3.233-.046 2.17-.099 3.18-1.129 3.279-3.279.038-.844.046-1.097.046-3.233s-.008-2.389-.046-3.232c-.099-2.153-1.111-3.182-3.279-3.281zm-3.233 10.62c-2.269 0-4.108-1.839-4.108-4.108 0-2.269 1.84-4.108 4.108-4.108s4.108 1.839 4.108 4.108c0 2.269-1.839 4.108-4.108 4.108zm4.271-7.418c-.53 0-.96-.43-.96-.96s.43-.96.96-.96.96.43.96.96-.43.96-.96.96zm-1.604 3.31c0 1.473-1.194 2.667-2.667 2.667s-2.667-1.194-2.667-2.667c0-1.473 1.194-2.667 2.667-2.667s2.667 1.194 2.667 2.667zm4.333-12h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm.952 15.298c-.132 2.909-1.751 4.521-4.653 4.654-.854.039-1.126.048-3.299.048s-2.444-.009-3.298-.048c-2.908-.133-4.52-1.748-4.654-4.654-.039-.853-.048-1.125-.048-3.298 0-2.172.009-2.445.048-3.298.134-2.908 1.748-4.521 4.654-4.653.854-.04 1.125-.049 3.298-.049s2.445.009 3.299.048c2.908.133 4.523 1.751 4.653 4.653.039.854.048 1.127.048 3.299 0 2.173-.009 2.445-.048 3.298z"/></svg>
I tried adding different paths. This one fills only the inner circle and the dot:
M15.233 5.488c-.843-.038-1.097-.046-3.233-.046s-2.389.008-3.232.046c-2.17.099-3.181 1.127-3.279 3.279-.039.844-.048 1.097-.048 3.233s.009 2.389.047 3.233c.099 2.148 1.106 3.18 3.279 3.279.843.038 1.097.047 3.233.047 2.137 0 2.39-.008 3.233-.046 2.17-.099 3.18-1.129 3.279-3.279.038-.844.046-1.097.046-3.233s-.008-2.389-.046-3.232c-.099-2.153-1.111-3.182-3.279-3.281z
This one fills a little bit more, but it's still not complete fill:
M15.233 5.488c-.843-.038-1.097-.046-3.233-.046s-2.389.008-3.232.046c-2.17.099-3.181 1.127-3.279 3.279-.039.844-.048 1.097-.048 3.233s.009 2.389.047 3.233c.099 2.148 1.106 3.18 3.279 3.279.843.038 1.097.047 3.233.047 2.137 0 2.39-.008 3.233-.046 2.17-.099 3.18-1.129 3.279-3.279.038-.844.046-1.097.046-3.233s-.008-2.389-.046-3.232c-.099-2.153-1.111-3.182-3.279-3.281zm4.271-7.418c-.53 0-.96-.43-.96-.96s.43-.96.96-.96.96.43.96.96-.43.96-.96.96zm4.333-12h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm.952 15.298c-.132 2.909-1.751 4.521-4.653 4.654-.854.039-1.126.048-3.299.048s-2.444-.009-3.298-.048c-2.908-.133-4.52-1.748-4.654-4.654-.039-.853-.048-1.125-.048-3.298 0-2.172.009-2.445.048-3.298.134-2.908 1.748-4.521 4.654-4.653.854-.04 1.125-.049 3.298-.049s2.445.009 3.299.048c2.908.133 4.523 1.751 4.653 4.653.039.854.048 1.127.048 3.299 0 2.173-.009 2.445-.048 3.298z
And then there's an SVG like this, which wouldn't fill at all:
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 12.713l-11.985-9.713h23.97l-11.985 9.713zm0 2.574l-12-9.725v15.438h24v-15.438l-12 9.725z"/></svg>
If you could help me fill those tricky SVGs I would sincerely appreciate it. Thank you!
P.S. A great instrument for coding SVGs and live preview - here.
Upvotes: 1
Views: 611
Reputation: 17268
Overview
SVG has a rich DOM API, so if there is a path that would not fill, how about constructing one that does ?
The approach presented consists of the following steps:
The CH-path is guaranteed to fill. Since SVGs render by applying the painter's algorithm, the CH-path can be used as a backdrop for the O-path, coloring the abstract canvas right behind the O-path and simulating what most users would intuitively consider a fill of the path.
The idea might seem like overkill but should be reliable across most path definitions encountered in the wild (see the section 'Limitation' for a conceptual caveat). It can easily be extended to consider a set of path elements used to draw the target icon.
Limitation
CH-path will cover the O-path by definition. The other way round that only holds for convex O-paths. If the O-path is not convex, it's a case-by-case decision whether this solution would still be useful (eg. icons like Pac-Man have better odds than star shapes).
Details
Computing the convex hull (CH)
The CH of a point set computes as the smallest convex point set that completely covers these points. Employing getPointAtLength
and getTotalLength
methods of the path
element's DOM API, a suitable point set can be obtained without the need to analyze the path specification as these methods allow to programmatically walk along the path and sample points on the path (in the code below in equispaced intervals of 0.1 distance units length). The resulting point set is then fed into a CH algorithm.
Build a path from the CH-path
The CH algorithm returns the CH points in proper order to draw the boundary by connecting adjacent points with straight-line segments. This translates trivially into the d
attribute of a path
element using the M
(move to) and L
(line to) path commands in absolute coords.
Insert the CH-path into the SVG just before the O-path.
The method insertAdjacentElement
of SVG elements' DOM API does the trick when applied to the O-path.
Fill the CH-path with the desired color, keep the O-path transparent
Use the fill
attribute.
Inline Demo
The demo transforms the icon path for the sake of better visibility and draws the CH boundary. The actual computations are performed on the original path coordinates. The CH is drawn in red. Production code will harness some library to compute the CH. The demo shows the mail icon but includes the path definitions for the phone and instagram icons - outcomment the respective use
element to use them.
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="250" viewBox="0 0 2000 1000">
<defs>
<g
id="icon_instagram"
transform="translate(500,400) scale(20,20) translate(-10,-10)"
>
<path d="M15.233 5.488c-.843-.038-1.097-.046-3.233-.046s-2.389.008-3.232.046c-2.17.099-3.181 1.127-3.279 3.279-.039.844-.048 1.097-.048 3.233s.009 2.389.047 3.233c.099 2.148 1.106 3.18 3.279 3.279.843.038 1.097.047 3.233.047 2.137 0 2.39-.008 3.233-.046 2.17-.099 3.18-1.129 3.279-3.279.038-.844.046-1.097.046-3.233s-.008-2.389-.046-3.232c-.099-2.153-1.111-3.182-3.279-3.281zm-3.233 10.62c-2.269 0-4.108-1.839-4.108-4.108 0-2.269 1.84-4.108 4.108-4.108s4.108 1.839 4.108 4.108c0 2.269-1.839 4.108-4.108 4.108zm4.271-7.418c-.53 0-.96-.43-.96-.96s.43-.96.96-.96.96.43.96.96-.43.96-.96.96zm-1.604 3.31c0 1.473-1.194 2.667-2.667 2.667s-2.667-1.194-2.667-2.667c0-1.473 1.194-2.667 2.667-2.667s2.667 1.194 2.667 2.667zm4.333-12h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm.952 15.298c-.132 2.909-1.751 4.521-4.653 4.654-.854.039-1.126.048-3.299.048s-2.444-.009-3.298-.048c-2.908-.133-4.52-1.748-4.654-4.654-.039-.853-.048-1.125-.048-3.298 0-2.172.009-2.445.048-3.298.134-2.908 1.748-4.521 4.654-4.653.854-.04 1.125-.049 3.298-.049s2.445.009 3.299.048c2.908.133 4.523 1.751 4.653 4.653.039.854.048 1.127.048 3.299 0 2.173-.009 2.445-.048 3.298z"/>
</g>
<g
id="icon_mail"
transform="translate(500,400) scale(40,40) translate(-12,-12.713)"
>
<path d="M12 12.713l-11.985-9.713h23.97l-11.985 9.713zm0 2.574l-12-9.725v15.438h24v-15.438l-12 9.725z"/>
</g>
<g
id="icon_skype"
transform="translate(500,400) scale(20,20) translate(-12, -12)"
>
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm3.445 17.827c-3.684 1.684-9.401-9.43-5.8-11.308l1.053-.519 1.746 3.409-1.042.513c-1.095.587 1.185 5.04 2.305 4.497l1.032-.505 1.76 3.397-1.054.516z"/>
</g>
</defs>
<!-- only one 'use' element should be active at any one time -->
<use href="#icon_mail"/>
<!--use href="#icon_instagram"/-->
<!--use href="#icon_skype"/-->
<text x="1100" y="150" width="200" height="100" style="font-size:300%;">Click on the filled area of the icon to the left.</text>
<script type="text/javascript">
<![CDATA[
const SVG_XLINK = "http://www.w3.org/1999/xlink";
const SVG_NS = 'http://www.w3.org/2000/svg';
function _cmp ( a, b ) {
let n_cmp
;
n_cmp =
(a.x > b.x)
? 1
: ((a.x < b.x)
? -1
: ((a.y > b.y)
? 1
: ((a.y < b.y) ? -1 : 0)
)
)
;
return n_cmp;
} // _cmp
// Get convex Hull of point set
function getConvexHull ( pa_points ) {
console.log(`getConvexHull: started, point set size: ${pa_points.length};`); // `
pa_points.sort ( _cmp );
console.log(`getConvexHull: done: sort;`);
let a_ch = []
, m_base
, n_base = 0
, n_idxCurrentCandidate // index of current candidate for the next CH point
, n_count = 0
;
// !!! Reminder: svg y axis oriented downwards !
// Sweep 1/2: x -> x+ (lower CH)
let i = 0;
while (pa_points[i].x === pa_points[0].x) {
//***console.log(`getConvexHull: pushing point #${i}.`);
a_ch.push ( pa_points[i] ); // yep, points have been lexicographically ordered
i++;
}
n_idxCurrentCandidate = i-1;
while (n_idxCurrentCandidate + 1 < pa_points.length) { // guard against degenerate case
n_base = n_idxCurrentCandidate;
m_base = {
dx: (pa_points[n_base+1].x - pa_points[n_base].x)
, dy: (pa_points[n_base+1].y - pa_points[n_base].y)
};
n_idxCurrentCandidate = n_base+1;
if (n_base+2 < pa_points.length) {
let k = n_base+2;
do {
let n_fac = (pa_points[k].x - pa_points[n_base].x) / m_base.dx
;
if ((pa_points[k].y - pa_points[n_base].y) > n_fac * m_base.dy) {
// steeper slope detected.
n_idxCurrentCandidate = k;
m_base.dx = pa_points[k].x - pa_points[n_base].x;
m_base.dy = pa_points[k].y - pa_points[n_base].y;
}
k++;
} while ( k < pa_points.length );
}
// We will get the end point in the upper CH sweep.
if (n_idxCurrentCandidate !== pa_points.length-1) {
//***console.log(`getConvexHull: pushing point #${n_idxCurrentCandidate}.`);
a_ch.push ( pa_points[n_idxCurrentCandidate] );
}
n_count++;
} // lower CH
console.log(`getConvexHull: lower CH completed after ${n_count} slope checks, ${a_ch.length} CH points found so far;`);
// Sweep 2/2: x -> x- (upper CH)
i = pa_points.length - 1;
while (pa_points[i].x === pa_points[pa_points.length-1].x) {
//***console.log(`getConvexHull: pushing point #${i}.`);
a_ch.push ( pa_points[i] ); // yep, points have been lexicographically ordered
i--;
}
n_idxCurrentCandidate = i+1;
while (n_idxCurrentCandidate - 1 >= 0) { // guard against degenerate case
n_base = n_idxCurrentCandidate;
m_base = {
dx: (pa_points[n_base-1].x - pa_points[n_base].x)
, dy: (pa_points[n_base-1].y - pa_points[n_base].y)
};
n_idxCurrentCandidate = n_base-1;
if (n_base-2 >= 0) {
let k = n_base-2;
do {
let n_fac = (pa_points[k].x - pa_points[n_base].x) / m_base.dx
;
if ((pa_points[k].y - pa_points[n_base].y) < n_fac * m_base.dy) {
// shallower slope detected.
n_idxCurrentCandidate = k;
m_base.dx = pa_points[k].x - pa_points[n_base].x;
m_base.dy = pa_points[k].y - pa_points[n_base].y;
}
k--;
} while ( k >= 0 );
}
// We got the start point already in the lower CH sweep.
if (n_idxCurrentCandidate !== 0) {
//***console.log(`getConvexHull: pushing point #${n_idxCurrentCandidate}.`);
a_ch.push ( pa_points[n_idxCurrentCandidate] );
}
n_count++;
} // upper CH
console.log(`getConvexHull: upper CH completed. TL of ${n_count} slope checks, ${a_ch.length} CH points found.`);
return a_ch;
} // getConvexHull
// Get convex hull of path
function getPathHull ( e_path ) {
console.log(`getPathHull: started;`);
let a_CH
, a_pointset = []
, n_pathLengthCurrent = 0
, n_pathLengthTotal = e_path.getTotalLength()
, n_steps = 10 * Math.floor(n_pathLengthTotal)
;
console.log(`getPathHull: path length = ${n_pathLengthTotal};`);
// Get point coords along the path.
// Sliding [-1,+1]-window to eliminate interior points on straight line segments.
let o_svgpoint_prevprev
, o_svgpoint_prev
, o_svgpoint
;
a_pointset.push( e_path.getPointAtLength(0) );
for (let i = 0; i < n_steps; i++) {
o_svgpoint_prevprev = o_svgpoint_prev;
o_svgpoint_prev = o_svgpoint;
o_svgpoint = e_path.getPointAtLength(i * n_pathLengthTotal / n_steps);
if (i > 1) {
// Optimizing away points on straight line axis-parallel segments
if (!(
(
(o_svgpoint_prevprev.x === o_svgpoint_prev.x)
&& (o_svgpoint_prev.x === o_svgpoint.x)
)
|| (
(o_svgpoint_prevprev.y === o_svgpoint_prev.y)
&& (o_svgpoint_prev.y === o_svgpoint.y)
)
)) {
a_pointset.push( o_svgpoint_prev );
}
}
}
a_pointset.push( o_svgpoint ); // the last one - always included
console.log(`getPathHull: pointset size = ${n_steps}, after straight line segment optimization ${a_pointset.length};`);
let n_checkAt = Math.floor(Math.random() * a_pointset.length)
;
console.log(`getPathHull: @${Number(100 * n_checkAt / n_steps).toFixed(2)}%: (x,y) = (${Number(a_pointset[n_checkAt].x).toFixed(2)}, ${Number(a_pointset[n_checkAt].y).toFixed(2)});`);
// Compute the Convex Hull for the point set
a_CH = getConvexHull ( a_pointset );
// Build a path from the CH. The CH point set is ordered counter-clockwise
a_CH = a_CH.map ( (po_svgpoint) => {
return { x: Number(po_svgpoint.x).toFixed(3), y: Number(po_svgpoint.y).toFixed(3) };
});
return a_CH;
} // getPathHull
//
// paintPathHull
//
function paintPathHull ( eve, ps_id ) {
console.log(`paintPathHull: started; eve.target.parentElement.id = '${eve.target.id}', ps_id = '${ps_id}'`);
let a_CH
, e_g = eve.target.parentElement
, e_hull
, e_path = document.querySelector(`#${e_g.getAttribute('id')} > path`)
, e_svg = document.querySelector('svg')
, s_attr_d
;
a_CH = getPathHull(e_path);
console.log ( `a_CH - first 10 coords: ${JSON.stringify(a_CH.slice(0,9))};`);
s_attr_d =
`M${a_CH[0].x},${a_CH[0].y} `
+ a_CH.map( ( po_coords, pn_idx ) => {
return `L${po_coords.x},${po_coords.y}`;
}).join(' ')
;
e_hull = document.createElementNS(SVG_NS, 'path');
e_hull.setAttribute('d', s_attr_d);
e_hull.setAttribute('fill', 'green');
e_hull.setAttribute('stroke', 'red');
e_hull.setAttribute('stroke-width', '0.1');
e_path.insertAdjacentElement('beforebegin', e_hull);
} // paintPathHull
document.querySelector('#icon_instagram').addEventListener ( 'click', (eve) => { paintPathHull(eve, 'icon_instagram'); } );
document.querySelector('#icon_mail').addEventListener ( 'click', (eve) => { paintPathHull(eve, 'icon_mail'); } );
document.querySelector('#icon_skype').addEventListener ( 'click', (eve) => { paintPathHull(eve, 'icon_skype'); } );
]]>
</script>
</svg>
Upvotes: 1