Путь для заливки фона изображения SVG - PullRequest
1 голос
/ 01 мая 2020

У меня есть несколько иконок SVG для ссылок в социальных сетях. Я хочу, чтобы у них был белый фон. С помощью сообщества я узнал, что это можно сделать, добавив к SVG-коду <path fill="white" d="XXX">, где XXX соответствует первой части d="" кода, вплоть до первого m. Например, есть следующий код SVG:

<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>

Чтобы заполнить внутреннюю часть белым цветом, необходимо изменить код следующим образом:

<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>

Вот что было добавлено:

<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"/>

Этот метод работает хорошо, пока нет SVG, подобного этому:

<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>

Я пытался добавить разные пути. Этот заполняет только внутренний круг и точку:

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

Этот заполняет немного больше, но он еще не завершен:

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

И еще есть такой SVG, который не заполняется вообще:

<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>

Если бы вы могли помочь мне заполнить эти хитрые SVG, я был бы искренне признателен. Спасибо!

PS Отличный инструмент для кодирования SVG и предварительного просмотра в реальном времени - здесь .

1 Ответ

0 голосов
/ 05 мая 2020

Обзор

SVG имеет богатый DOM API, поэтому, если есть путь, который не будет заполнен, как насчет его создания?

Представленный подход состоит из следующих этапов:

  1. Определение выпуклой оболочки заданного пути («O-путь»)
  2. Построение пути от границы выпуклая оболочка («CH-путь»)
  3. Вставьте CH-путь в SVG непосредственно перед O-путем.
  4. Заполните CH-путь нужным цветом, оставьте O- Прозрачный путь.

CH-путь гарантированно заполнен. Поскольку SVG визуализируются с применением алгоритма рисования, CH-путь может использоваться в качестве фона для O-пути, окрашивая абстрактный холст прямо за O-путем и моделируя то, что большинство пользователей интуитивно рассматривает заполнение пути.

Идея может показаться излишней, но она должна быть надежной для большинства определений путей, встречающихся в дикой природе (см. Раздел «Ограничения» для концептуального предостережения). Его можно легко расширить, чтобы рассмотреть набор элементов пути, используемых для рисования целевого значка.

Ограничение

CH-путь покроет O-путь по определению. Обратный путь, который имеет место только для выпуклых O-путей. Если O-путь не является выпуклым, это решение для каждого конкретного случая, будет ли это решение по-прежнему полезным (например, значки, такие как Pa c - могут иметь лучшие коэффициенты, чем формы звезд).

Подробности

  1. Вычисление выпуклой оболочки (CH) CH набора точек вычисляется как наименьший выпуклый набор точек, который полностью покрывает эти точки. Используя методы getPointAtLength и getTotalLength API DOM элемента path, можно получить подходящий набор точек без необходимости анализа спецификации пути, поскольку эти методы позволяют программно проходить по пути и выбирать точки на пути ( в приведенном ниже коде в равных интервалах длиной в 0,1 единицы расстояния). Полученный набор точек затем подается в алгоритм CH.

  2. Построение пути из CH-пути. Алгоритм CH возвращает точки CH в правильном порядке, чтобы нарисовать границу путем соединения соседних точек. с прямыми отрезками. Это тривиально преобразуется в атрибут d элемента path с помощью команд пути M (перейти к) и L (от линии к) в абсолютных координатах.

  3. Вставьте CH-путь в SVG непосредственно перед O-путем.
    Метод insertAdjacentElement DOM API элементов SVG делает свое дело, когда применяется к O- дорожка.

  4. Заполните CH-путь нужным цветом, оставьте O-путь прозрачным
    Используйте атрибут fill.

Inline Demo

Демонстрация преобразует путь значков для лучшей видимости и др aws Граница СН. Фактические вычисления выполняются с исходными координатами пути. CH рисуется красным. Производственный код использует некоторую библиотеку для вычисления CH. Демонстрация показывает значок почты, но включает определения путей для значков телефона и Instagram - закомментируйте соответствующий элемент use, чтобы использовать их.

<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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...