3

I have an SVG with a gradient-like background and a foreground icon. Currently, the icon is just one filled path with polylines on top, but it can be instead split into multiple paths where the polyline would be the stroke. However, I want to, instead of the polyline/stroke having a colour, have them display the background, as though it has cut a hole in the foreground. Due to the background being a gradient (it's actually a shadow filter) and not a solid colour, I can't just have a stroke with the same colour as the background to do this. This is what I imagine the result would be if I could have a negative stroke-width on the multiple paths.

As an example, I'll use the SVG this is actually for (my profile picture) So, this is my current SVG code

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" stroke="black" fill="rgb(95,95,95)" viewBox="-30 -30 276 260" width="276" height="260" stroke-width="4">
  <defs>
    <filter id="shadow" x="-30" y="-30" width="276" height="260">
      <feGaussianBlur in="SourceAlpha" out="blurOut" stdDeviation="32"/>
      <feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
    </filter>
  </defs>

  <rect x="-30" y="-30" width="276" height="260" fill="#c41313" stroke="none"/>
  <path d="M108 0 L216 54 L216 103 L162 130 L162 152 L135 166 L135 187 L108 200 L82 187 L82 166 L54 152 L54 130 L0 103 L0 54 Z" filter="url(#shadow)"/>
  <g fill="none">
    <polyline points="82 166, 108 179, 135 166"/>
    <polyline points="54 130, 108 157, 162 130"/>
    <polyline points="0 54, 108 108, 216 54"/>
    <polyline points="49 78.5, 108 49, 167 78.5"/>
    <line x1="108" y1="0" x2="108" y2="49"/>
    <line x1="108" y1="200" x2="108" y2="108"/>
  </g>
</svg>
I would like, instead of the black outline, it to be a gap in the icon, like this. Of course, for this particular image, I could just manually make each section- separated by the black outlines- have it's own path without a stroke, re-creating the effect, but due to how I am using this image, I need the stroke-width to be variable, and having specific paths would not allow for this, unless there were a way to automatically generate these paths in Javascript or PHP but I wouldn't know how to implement this. The only way I can think of getting around this would be to find the centres of each section and increase a grey outline to decrease the gap (so a maximum gap would be a stroke-width of 0 and a minimum gap would have a stroke-width equivalent to the radius of each section), but for my situation this would still not be effective. So what alternatives are there?
hopperelec
  • 133
  • 1
  • 2
  • 13
  • 1
    Have you tried a transparent stroke? – Zach Jensz Apr 26 '22 at 03:31
  • @ZachJensz Yes, but sadly, that just shows what would be there if the stroke wasn't there at all (so, the path fill instead of the background) – hopperelec Apr 26 '22 at 03:35
  • I think it would be much easier to just have a bunch of separate gray rectangles rather than trying to get the stroke the way you want – Zach Jensz Apr 26 '22 at 03:42
  • @ZachJensz That's what I was meant @ "Of course, for this particular image, I could... having specific paths would not allow for this" – hopperelec Apr 26 '22 at 03:44
  • My apologies I skimmed over that part, will continue to think of a solution – Zach Jensz Apr 26 '22 at 03:49
  • That's fine haha, I worded it badly. Thank you! – hopperelec Apr 26 '22 at 03:49
  • 1
    Could you explain in your question more as to why you need the stroke width? Because you could generate the shapes based on a theoretical stroke width with JS if that could be an option – Zach Jensz Apr 26 '22 at 04:29
  • I'm wanting to make a smooth animation to 'generate' the logo, where it goes from a 3D version, to a solid colour version, to the version with these gaps. So I'd need to transition between, for example, a 'stroke-width' of 0 to -4. Also, I change the design of my logo quite often, to the point I've make a website specifically to design new versions of the logo, with sliders for things like the brightness of the grey, the shadow length and the outline width. However, for both of these, I suppose Javascript could work. I'll add this to the question and I'd accept a Javascript solution – hopperelec Apr 26 '22 at 04:35
  • Made a [comparison between the two working solutions](https://codepen.io/hopperelec/pen/PoQYzjr) (with some edits, including copying the translation from herrstrietzel's method into Michael Mullany's method) and thought I should share it. I find herrstrietzel's method more confusing, but it doesn't require disabling anti-aliasing so I will go with that method for now. However, if there was a way to re-apply the anti-aliasing to Micahel's method, that method would be preferable in my opinion – hopperelec Apr 28 '22 at 21:05

3 Answers3

3

A more conventional approach would be to use a <mask>:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 276 260" width="276" height="260">
  <defs>
    <mask id="mask" stroke-width="4" stroke="#000">
      <g transform="translate(30 30)">
        <path id="shapeBG" d="M108 0 L216 54 L216 103 L162 130 L162 152 L135 166 L135 187 L108 200 L82 187 L82 166 L54 152 L54 130 L0 103 L0 54 Z" fill="#fff" />
        <g fill="none">
          <polyline points="82 166, 108 179, 135 166" />
          <polyline points="54 130, 108 157, 162 130" />
          <polyline points="0 54, 108 108, 216 54" />
          <polyline points="49 78.5, 108 49, 167 78.5" />
          <line x1="108" y1="0" x2="108" y2="49" />
          <line x1="108" y1="200" x2="108" y2="108" />
        </g>
      </g>
    </mask>
    <filter id="shadow" x="0" y="0" width="276" height="260">
      <feGaussianBlur in="SourceAlpha" out="blurOut" stdDeviation="32" />
      <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
    </filter>
  </defs>
  <rect id="bg" x="0" y="0" width="100%" height="100%" fill="#c41313" stroke="none" />
  <g filter="url(#shadow)">
    <rect x="0" y="0" width="100%" height="100%" mask="url(#mask)" fill="rgb(95,95,95)" />
  </g>
</svg>

<svg xmlns="http://www.w3.org/2000/svg" viewBox="-30 -30 276 260" width="276" height="260">
  <g id="mask" stroke-width="4" stroke="#000">
    <path id="shapeBG" d="M108 0 L216 54 L216 103 L162 130 L162 152 L135 166 L135 187 L108 200 L82 187 L82 166 L54 152 L54 130 L0 103 L0 54 Z" fill="#fff" />
    <g fill="none">
      <polyline points="82 166, 108 179, 135 166" />
      <polyline points="54 130, 108 157, 162 130" />
      <polyline points="0 54, 108 108, 216 54" />
      <polyline points="49 78.5, 108 49, 167 78.5" />
      <line x1="108" y1="0" x2="108" y2="49" />
      <line x1="108" y1="200" x2="108" y2="108" />
    </g>
    <text style="font-size:11; font-family:Arial, sans-serif" stroke-width="0" x="0" y="80%" dominant-baseline="middle">Mask: white fills/strokes=opaque; <tspan x="0" dy="12">black fills/strokes=transparent<tspan></text>
</svg>

On the right you see the actual mask graphic:

  • white fills/strokes will become opaque
  • black fills/strokes will become transparent

<mask> could also be used to get semi-transparent strokes e.g by setting your stroke color to something like rgb(128,128,128).

Regarding software support you might also realign your graphics via a transform to avoid negative viewBox values: some editors have issues with these.

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
1

Using CSS mix-blend-mode seems to achieve what you want, not sure how to fix the gray and the shadow yet though:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" stroke="black" fill="rgb(95,95,95)" viewBox="-30 -30 276 260" width="276" height="260" stroke-width="4">
  <defs>
    <filter id="shadow" x="-30" y="-30" width="276" height="260">
      <feGaussianBlur in="SourceAlpha" out="blurOut" stdDeviation="32"/>
      <feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
    </filter>
  </defs>
  
  <rect x="-30" y="-30" width="276" height="260" fill="#c41313" stroke="none"/>
    <g stroke="black" style="mix-blend-mode: color-dodge;">
    <path d="M108 0 L216 54 L216 103 L162 130 L162 152 L135 166 L135 187 L108 200 L82 187 L82 166 L54 152 L54 130 L0 103 L0 54 Z" filter="url(#shadow)"/>
    <g fill="none">
      <polyline points="82 166, 108 179, 135 166"/>
      <polyline points="54 130, 108 157, 162 130"/>
      <polyline points="0 54, 108 108, 216 54"/>
      <polyline points="49 78.5, 108 49, 167 78.5"/>
      <line x1="108" y1="0" x2="108" y2="49"/>
      <line x1="108" y1="200" x2="108" y2="108"/>
    </g>
  </g>
</svg>
Zach Jensz
  • 3,650
  • 5
  • 15
  • 30
  • black stroke and color dodge gives an interesting result where the stroke is how you want but the shapes are a bright red – Zach Jensz Apr 26 '22 at 04:18
  • 1
    For some reason, I've always thought it was unsafe to use styling in SVGs that were going to be used outside of just websites because not all image software would support it, but after doing a bit of research, I can't find anything backing this up, and a few websites specifically demonstrating how to use CSS inside of SVGs. So, assuming it is safe to use CSS for non-web SVGs, this will be perfect if it can be made to not affect the grey or shadow. Thank you, I'll do some research into mix-blend-mode too to see if I can see how these could be fixed! – hopperelec Apr 26 '22 at 04:20
  • white stroke and color-burn gives a nice result where the stroke is a solid red and the shapes are dark like the shadow behind them – Zach Jensz Apr 26 '22 at 04:20
  • 1
    Just realised [mdn docs specifically lists which browsers support mix-blend-mode on SVG elements](https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode#browser_compatibility), and sadly quite a few browsers still have no support for it. And, of course, that's only browsers, but for example things like Photoshop or ffmpeg may not support it either. I'd still rather have a solution that works in most cases than in none, though, so I'll keep looking for ways to keep the shadow and grey – hopperelec Apr 26 '22 at 04:26
1

You can do this in a single filter with a green-screen technique. You make the exterior and interior lines pure blue and green and then use a ColorMatrix to select them into separate layers and then composite/out them from the original shape. It's important to use shape-rendering=crispEdges because there are anti-aliasing artifacts that look bad if you don't.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-30 -30 276 260" width="276" height="260" stroke-width="4" color-interpolation-filters="sRGB" shape-rendering="crispEdges">
  <defs>
    <filter id="green-screen" x="-30" y="-30" width="276" height="260">
      <feColorMatrix type="matrix" values="0 0 0 0 0  0 2 0 0 -1  0 0 0 0 0  -1 2 -1 0 -1" result="exterior-line"/>
      <feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0   0 0 0 0 0  0 0 2 0 -1  -1 -1 2 0 -1" result="interior-line"/>
      
      <feComposite operator="out" in="SourceGraphic" in2="exterior-line"/>
      <feComposite operator="out"  in2="interior-line" result="masked-shape"/>
      
      <feGaussianBlur in="SourceAlpha" out="blurOut" stdDeviation="32"/>
      <feComposite operator="over" in="masked-shape"/>
    </filter>
  </defs>

  <rect x="-30" y="-30" width="276" height="260" fill="#c41313"/>
  <g filter="url(#green-screen)">
    <path d="M108 0 L216 54 L216 103 L162 130 L162 152 L135 166 L135 187 L108 200 L82 187 L82 166 L54 152 L54 130 L0 103 L0 54 Z" fill="rgb(95,95,95)" stroke="rgb(0,255,0)"/>
    <g fill="none" stroke="rgb(0,0,255)">
      <polyline points="82 166, 108 179, 135 166"/>
      <polyline points="54 130, 108 157, 162 130"/>
      <polyline points="0 54, 108 108, 216 54"/>
      <polyline points="49 78.5, 108 49, 167 78.5"/>
      <line x1="108" y1="0" x2="108" y2="49"/>
      <line x1="108" y1="200" x2="108" y2="108"/>
    </g>
  </g>
</svg>
hopperelec
  • 133
  • 1
  • 2
  • 13
Michael Mullany
  • 30,283
  • 6
  • 81
  • 105
  • Is there a way to only apply `shape-rendering=crispEdges` to pre-filtered version, then add anti-aliasing back to the filtered version? – hopperelec Apr 28 '22 at 06:46
  • You can hack something that looks a bit like an antialias, but it doesn't look great. So ... not really. – Michael Mullany Apr 30 '22 at 14:35