Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a circular clipPath to mask flags #11

Closed
HatScripts opened this issue Jul 31, 2020 · 22 comments
Closed

Use a circular clipPath to mask flags #11

HatScripts opened this issue Jul 31, 2020 · 22 comments
Labels
enhancement New feature or request

Comments

@HatScripts
Copy link
Owner

Per waldyrious's suggestion on #8, here are some experiments with converting flags to use a circular <clipPath>. This helps to avoid having longer than necessary <path d="...">s, and generally makes the code more human readable. It also seems to result in smaller files when minified with svgo (For these examples I'm using svgo 1.3.2; the latest release at the time of writing).

We also trying to avoid the anti-aliasing/color-bleeding issues described here and on the graphic design SE.

Russia

Original (302 bytes minified):

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <circle cx="256" cy="256" r="256" fill="#eee"/>
  <path fill="#0052b4" d="M496 345a255.4 255.4 0 0 0 0-178H16a255.5 255.5 0 0 0 0 178l240 22.3L496 345z"/>
  <path fill="#d80027" d="M256 512a256 256 0 0 0 240-167H16a256 256 0 0 0 240 167z"/>
</svg>

New (298 bytes minified):

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <defs>
    <clipPath id="circle-mask">
      <circle r="256" cy="256" cx="256"/>
    </clipPath>
  </defs>
  <g clip-path="url(#circle-mask)">
    <rect y="0" x="0" height="160" width="512" fill="#eeeeee"/> <!-- white -->
    <rect y="160" x="0" height="192" width="512" fill="#0052b4"/> <!-- blue -->
    <rect y="352" x="0" height="160" width="512" fill="#d80027"/> <!-- red -->
  </g>
</svg>

United States

Original (836 bytes minified):

16px, 24px, 32px, 48px (zoomed to 400%)

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <circle cx="256" cy="256" r="256" fill="#eee"/>
  <path fill="#d80027" d="M246 55v67h227.9A256 256 0 0 0 414 55H246zm0 134v67h266a256 256 0 0 0-9-67H246zM9 323a256 256 0 0 0 29.1 67h435.7a256 256 0 0 0 29-67H9zm89 134a256 256 0 0 0 158 55 256 256 0 0 0 158.5-55H97.9z"/>
  <path fill="#0052b4" d="M256 0a256 256 0 0 0-135 38.9h20l-21.6 15.7 8.2 25.4L106 64.3 84.4 80l6.3-19.4a256 256 0 0 0-48 54.3H48L38 122a256 256 0 0 0-9.2 16.1l5.8 17.8-10.5-7.7a256 256 0 0 0-9.3 22.7l6.5 19.9H48l-21.6 15.7 8.2 25.4L13 216.3 2.2 224A256 256 0 0 0 0 256h256V0z"/>
  <path fill="#eee" d="M84.4 156l56.6-41.1H71l56.6 41.1L106 89.4zm93 0l56.6-41.1h-70l56.6 41.1L199 89.4zm0-76L234 38.9h-70L220.6 80 199 13.4zm-93 152l56.6-41.1H71l56.6 41.1-21.6-66.6zm93 0l56.6-41.1h-70l56.6 41.1-21.6-66.6z"/>
</svg>

New (784 bytes minified):

16px, 24px, 32px, 48px (zoomed to 400%)

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <defs>
    <clipPath id="circle-mask">
      <circle r="256" cy="256" cx="256"/>
    </clipPath>
    <path id="star" d="M-8.6 80L48 38.9h-70L34.6 80 13 13.4z" fill="#eeeeee"/>
  </defs>
  <g clip-path="url(#circle-mask)">
    <rect y="0" x="0" height="512" width="512" fill="#eeeeee"/> <!-- White background -->

    <g fill="#d80027"> <!-- Red stripes -->
      <rect y="64" x="0" height="64" width="512"/>
      <rect y="192" x="0" height="64" width="512"/>
      <rect y="320" x="0" height="64" width="512"/>
      <rect y="448" x="0" height="64" width="512"/>
    </g>

    <rect y="0" x="0" height="256" width="256" fill="#0052b4"/> <!-- Blue background -->

    <use href="#star" transform="translate(0 0)"/>
    <use href="#star" transform="translate(0 76)"/>
    <use href="#star" transform="translate(0 152)"/>
    <use href="#star" transform="translate(93 0)"/>
    <use href="#star" transform="translate(93 76)"/>
    <use href="#star" transform="translate(93 152)"/>
    <use href="#star" transform="translate(186 0)"/>
    <use href="#star" transform="translate(186 76)"/>
    <use href="#star" transform="translate(186 152)"/>
  </g>
</svg>
@waldyrious
Copy link
Collaborator

Love it! By the way (and please don't let this side-note derail the focus of this issue! 😄), it should be possible to avoid some repetition in the human-readable form by using <pattern>s. In the case of the USA flag, we could replace

    <rect y="0" x="0" height="512" width="512" fill="#eeeeee"/> <!-- White background -->

    <g fill="#d80027"> <!-- Red stripes -->
      <rect y="64" x="0" height="64" width="512"/>
      <rect y="192" x="0" height="64" width="512"/>
      <rect y="320" x="0" height="64" width="512"/>
      <rect y="448" x="0" height="64" width="512"/>
    </g>

with a repeating pattern in the <defs>:

    <pattern id="stripes" width="512" height="128" patternUnits="userSpaceOnUse">
      <rect x="0" y="0" width="512" height="64" fill="#eeeeee"/> <!-- White stripe -->
      <rect x="0" y="64" width="512" height="64" fill="#d80027"/> <!-- Red stripe -->
    </pattern>

and then filling the entire rectangle with it:

    <rect y="0" x="0" height="512" width="512" fill="url(#stripes)"/> <!-- Stripes -->

Something similar could be done for the stars, avoiding the multiple clones + translations. But again, these are just possible optimizations we could consider in the future :) for now, using the circle mask is already a nice improvement!

@waldyrious
Copy link
Collaborator

waldyrious commented Jul 31, 2020

Another possible improvement for the future is to make sure the geometry of the flags works without the circular mask, i.e. with the flags as actual square icons. Of course, this should not sacrifice the aesthetics of the circular form, which should remain the primary representation.

@HatScripts
Copy link
Owner Author

Love it! By the way, it should be possible to avoid some repetition in the human-readable form by using <pattern>s.

Oh wow! Thanks for this information! I never knew SVG had such a feature. 😭 Looks to be extremely useful.

Another possible improvement for the future is to make sure the geometry of the flags works without the circular mask, i.e. with the flags as actual square icons. Of course, this shouls not sacrifice the aesthetics of the circular form, which should remain the primary representation.

Indeed, I agree with you. It took me a while to figure out what you were talking about. The stars, right?

us flag
I agree, this looks very wrong with the stars misaligned like that. I didn't realise (and was probably lazy) and kept the circle mask on for the entire time. But the <pattern> optimisation should make for an easy fix and help to prevent ugly things like this from occurring in the future.

@waldyrious
Copy link
Collaborator

Oh wow! Thanks for this information! I never knew SVG had such a feature. 😭 Looks to be extremely useful.

To be honest, I had a vague recollection of that functionality existing, but had never used it until the experiments that led to my comment above. I'm still learning about the syntax — MDN's reference and tutorial docs have been useful so far for that.

It took me a while to figure out what you were talking about. The stars, right?

Apologies, that was indeed why I made that comment. In usual "obvious once you figured it out" fashion, it didn't occur to me that the rationale wouldn't be clear without context. It's exactly as you put it :)

@HatScripts HatScripts added the enhancement New feature or request label Aug 9, 2020
@HatScripts
Copy link
Owner Author

HatScripts commented Aug 30, 2020

Another possible improvement for the future is to make sure the geometry of the flags works without the circular mask, i.e. with the flags as actual square icons. Of course, this should not sacrifice the aesthetics of the circular form, which should remain the primary representation.

As a follow-up to this, and for clarification, do you think flags that don't require a mask should be given one?

To choose one of the simplest examples, Japan's flag requires just two circle elements and is 165 bytes minified:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <circle cx="256" cy="256" r="256" fill="#eee"/>
  <circle cx="256" cy="256" r="112" fill="#d80027"/>
</svg>

But with the clipPath it's 268 bytes 196 bytes minified:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <defs>
    <clipPath id="circle-mask">
      <circle r="256" cy="256" cx="256"/>
    </clipPath>
  </defs>
  <g clip-path="url(#circle-mask)">
    <!--
    EDIT: Forgot to use a rect instead of a circle. This reduces the minified size to 196 bytes.
    <circle cx="256" cy="256" r="256" fill="#eee"/>
    -->
    <rect x="0" y="0" width="512" height="512" fill="#eee"/>
    <circle cx="256" cy="256" r="112" fill="#d80027"/>
  </g>
</svg>

In a perfect world, SVGO would surely compress either of these SVG files down to the same 165 bytes. But I guess it just doesn't support removing redundant clipPaths. Maybe I should look into writing a plugin for SVGO.

@waldyrious
Copy link
Collaborator

waldyrious commented Sep 1, 2020

Do you think flags that don't require a mask should be given one?
To choose one of the simplest examples, Japan's flag requires just two circle elements and is 165 bytes minified (...) But with the clipPath it's 268 bytes minified.

Hmm, that's a good point. I suppose we might have to decide whether we want to prioritize consistency and semantic clarity of the design definitions — as well as the extensibility to square/squircle icons (the latter being a common trend in icons lately), as opposed to maximum compactness of the resulting images.

Perhaps unsurprisingly, I'm tempted to side with the former over the latter, especially since a total of bites in the order of hundreds is still a pretty small payload, and even orders of magnitude more compact than the equivalent PNG even in small resolutions (I tested saving a 32x32 PNG of the Japanese flag image, and even after compressing it with optipng it is about 4KB).

That said, if we do want to keep the maximum compression possible, maybe we could add some custom logic to the optimization process — for example, storing both a canonical source foo.svg and a "manually compressed" version foo.compact.svg without the clippath, and wrap the svgo call in a small loop that uses the shorter file if present, and the regular one otherwise. Honestly, I'm not sure it's worth the extra effort and complexity, though.

A plugin for SVGO sounds pretty dope, actually! It would be a nice solution, but I must point out that it would only work if we hardcode circles for the background shape, which would prevent other icon shapes. (Not saying it's a deal-breaker, but I do find the possibility quite interesting.)

@waldyrious
Copy link
Collaborator

waldyrious commented Sep 1, 2020

Btw, I haven't tested this, but perhaps we could reuse the mask code from a single file, so that we don't have to repeat the same code in all files? That would also help with the compactness angle.

I found an article saying that something like this is possible for <use> — not sure what the story is for <clipPath>, but it sounds plausible in principle. This guy seems to have the same question, but no answer.

@HatScripts
Copy link
Owner Author

HatScripts commented Sep 20, 2020

Hmm, that's a good point. I suppose we might have to decide whether we want to prioritize consistency and semantic clarity of the design definitions — as well as the extensibility to square/squircle icons (the latter being a common trend in icons lately), as opposed to maximum compactness of the resulting images.

What about if we didn't use clipPath at all, and instead added an inline style attribute to the svg itself (or, say, a g containing all the elements) specifying a border-radius? This would greatly simplify the common use cases:

Square icons

A border radius of 0% or unspecified.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="border-radius: 0%">
  <rect x="0" y="0" width="512" height="512" fill="#eee"/>
  <circle cx="256" cy="256" r="112" fill="#d80027"/>
</svg>

japan flag square

Rounded square icons

A border radius of 10%, 25%, 1rem, etc.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="border-radius: 25%">
  <rect x="0" y="0" width="512" height="512" fill="#eee"/>
  <circle cx="256" cy="256" r="112" fill="#d80027"/>
</svg>

japan flag rounded square

Circle icons

A border radius of 50%.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="border-radius: 50%">
  <rect x="0" y="0" width="512" height="512" fill="#eee"/>
  <circle cx="256" cy="256" r="112" fill="#d80027"/>
</svg>

japan flag circle


One drawback to this approach is of course that applying custom masks (such as a squircle) is no longer possible. A squircle can (hypothetically) be made with pure CSS, but it's far more verbose than just using a clipPath.

@HatScripts
Copy link
Owner Author

HatScripts commented Sep 20, 2020

Btw, I haven't tested this, but perhaps we could reuse the mask code from a single file, so that we don't have to repeat the same code in all files? That would also help with the compactness angle.

I found an article saying that something like this is possible for <use> — not sure what the story is for <clipPath>, but it sounds plausible in principle. This guy seems to have the same question, but no answer.

In my recent experiments with <use>, it, unfortunately, appears to have a couple big issues:

  • It seems to only work when the files are hosted online. It breaks when they are stored locally on the computer, whether I open them in File Explorer, Chrome, Firefox, Edge, etc.
  • Even when files are online, <use> seems to ignore clipPaths within the linked SVG.

@waldyrious
Copy link
Collaborator

The style approach is ingenious! And yeah, I agree that custom shapes, and a squircle in particular, would not be that much of an advantage once we're able to easily produce squares, rounded squares, and circles.

One caveat of using style is that we might need to stick with width and height instead of viewBox, because the border radius appears to be applied to the entire viewport's dimensions, not the SVG document's dimensions (which may be a browser bug). Here's how both Firefox and Chrome display the 50% border-radius SVG you listed above:

demo of border-radius with

I tried adding a preserveAspectRatio attribute but none of its configurations seemed to help. That said, it may be possible with further CSS+SVG tweaking. I played a bit with object-fit and box-sizing, but no luck so far...

@HatScripts
Copy link
Owner Author

Oh damn, nice catch! I thought the border-radius solution had to be too good to be true. Are you aware of any downsides to using width and height over viewBox? And on a related note I realised I never properly responded to your original post on #7 where you suggested this, so for that I apologise.

I tried adding a preserveAspectRatio attribute but none of its configurations seemed to help. That said, it may be possible with further CSS+SVG tweaking. I played a bit with object-fit and box-sizing, but no luck so far...

Thanks for going to such lengths looking for a solution. You are right that this seems to be a browser bug. Strange that it renders incorrectly in both Chrome and Firefox. I get the same results on my end.

@waldyrious
Copy link
Collaborator

Are you aware of any downsides to using width and height over viewBox? And on a related note I realised I never properly responded to your original post on #7 where you suggested this, so for that I apologise.

No problem :) I'll respond there.

You are right that this seems to be a browser bug.

Yeah, it's possible that it's been reported; otherwise, we should probably do so to make sure it's addressed eventually. It's not the first edge case I've found while trying to do smarter stuff with SVGs (some of the experiments I did with , as mentioned above did produce inconsistent and IMO erroneous results, too). I just haven't found the energy to prepare a proper report.

In the meantime, we should be fine working with width and height, as I suggest in my response at #7.

@HatScripts
Copy link
Owner Author

Hey @waldyrious, sorry it's taken me so long to get back to you. Just to keep you in the loop, @climech's PR #18 (also related: #19) has effectively resolved this issue, i.e., applying a circular mask to all flags. So it's probably safe to close this issue now.

@climech opted for using masks over clipPaths to save a byte on each file. Since border-radius wasn't used, converting the viewBox to width/height (as per #7, and to avoid the browser bug you mentioned) isn't strictly necessary, but I still agree that it would be a worthwhile thing to do.

@Shatur
Copy link

Shatur commented Feb 8, 2021

@HatScripts, thanks! The new flags show up correctly in GitHub:

изображение

But look square on my system:
изображение

I have also tried embedding them in my application and they look square too (native app).

@climech
Copy link
Collaborator

climech commented Feb 9, 2021

Hello, @Shatur95!

Some more details would be appreciated. What system was this observed on, and what kind of rendering library was used to display the images in your application? The files validate against the W3 (SVG 1.1) spec, but I suppose it's up to the libraries to adhere to it 😞

Also, can you save the following 3 snippets as separate files and test if anything changes?

1.svg:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <defs>
    <mask id="a">
      <circle cx="256" cy="256" r="256" fill="#fff"/>
    </mask>
  </defs>
  <g mask="url(#a)">
    <path fill="#0052b4" d="M0 0h144.7l36 254.6-36 257.4H0z"/>
    <path fill="#d80027" d="M367.3 0H512v512H367.3l-29.7-257.3z"/>
    <path fill="#ffda44" d="M144.7 0h222.6v512H144.7z"/>
    <path fill="#d80027" d="M256 354.5V256h66.8v47.3zm-66.8-165.3H256V256h-66.8z"/>
    <path fill="#ff9811" d="M289.4 167a22.3 22.3 0 0 0-33.4-19.3 22.1 22.1 0 0 0-11.1-3c-12.3 0-22.3 10-22.3 22.3H167v111.3c0 41.4 32.9 65.4 58.7 77.8a22.1 22.1 0 0 0-3 11.2 22.3 22.3 0 0 0 33.3 19.3 22.1 22.1 0 0 0 11.1 3 22.3 22.3 0 0 0 19.2-33.5c25.8-12.4 58.7-36.4 58.7-77.8V167zm22.3 111.3c0 5.8 0 23.4-27.5 40.9a136.5 136.5 0 0 1-28.2 13.3c-7-2.4-17.8-6.7-28.2-13.3-27.5-17.5-27.5-35.1-27.5-41v-77.9h111.4z"/>
  </g>
</svg>

2.svg:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <clipPath id="a">
    <circle cx="256" cy="256" r="256"/>
  </clipPath>
  <g clip-path="url(#a)">
    <path fill="#0052b4" d="M0 0h144.7l36 254.6-36 257.4H0z"/>
    <path fill="#d80027" d="M367.3 0H512v512H367.3l-29.7-257.3z"/>
    <path fill="#ffda44" d="M144.7 0h222.6v512H144.7z"/>
    <path fill="#d80027" d="M256 354.5V256h66.8v47.3zm-66.8-165.3H256V256h-66.8z"/>
    <path fill="#ff9811" d="M289.4 167a22.3 22.3 0 0 0-33.4-19.3 22.1 22.1 0 0 0-11.1-3c-12.3 0-22.3 10-22.3 22.3H167v111.3c0 41.4 32.9 65.4 58.7 77.8a22.1 22.1 0 0 0-3 11.2 22.3 22.3 0 0 0 33.3 19.3 22.1 22.1 0 0 0 11.1 3 22.3 22.3 0 0 0 19.2-33.5c25.8-12.4 58.7-36.4 58.7-77.8V167zm22.3 111.3c0 5.8 0 23.4-27.5 40.9a136.5 136.5 0 0 1-28.2 13.3c-7-2.4-17.8-6.7-28.2-13.3-27.5-17.5-27.5-35.1-27.5-41v-77.9h111.4z"/>
  </g>
</svg>

3.svg:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <defs>
    <clipPath id="a">
      <circle cx="256" cy="256" r="256"/>
    </clipPath>
  </defs>
  <g clip-path="url(#a)">
    <path fill="#0052b4" d="M0 0h144.7l36 254.6-36 257.4H0z"/>
    <path fill="#d80027" d="M367.3 0H512v512H367.3l-29.7-257.3z"/>
    <path fill="#ffda44" d="M144.7 0h222.6v512H144.7z"/>
    <path fill="#d80027" d="M256 354.5V256h66.8v47.3zm-66.8-165.3H256V256h-66.8z"/>
    <path fill="#ff9811" d="M289.4 167a22.3 22.3 0 0 0-33.4-19.3 22.1 22.1 0 0 0-11.1-3c-12.3 0-22.3 10-22.3 22.3H167v111.3c0 41.4 32.9 65.4 58.7 77.8a22.1 22.1 0 0 0-3 11.2 22.3 22.3 0 0 0 33.3 19.3 22.1 22.1 0 0 0 11.1 3 22.3 22.3 0 0 0 19.2-33.5c25.8-12.4 58.7-36.4 58.7-77.8V167zm22.3 111.3c0 5.8 0 23.4-27.5 40.9a136.5 136.5 0 0 1-28.2 13.3c-7-2.4-17.8-6.7-28.2-13.3-27.5-17.5-27.5-35.1-27.5-41v-77.9h111.4z"/>
  </g>
</svg>

@Shatur
Copy link

Shatur commented Feb 9, 2021

Some more details would be appreciated. What system was this observed on, and what kind of rendering library was used to display the images in your application? The files validate against the W3 (SVG 1.1) spec, but I suppose it's up to the libraries to adhere to it

I use KDE Plasma. My application written in Qt framework that should support SVG 1.1.

Also, can you save the following 3 snippets as separate files and test if anything changes?

Tested, looks same:

изображение

@HatScripts HatScripts reopened this Feb 9, 2021
@Shatur
Copy link

Shatur commented Feb 9, 2021

Oh, it looks like Qt just not support clipPath because it not included in SVG Tiny 1.2.
Spec.

@HatScripts
Copy link
Owner Author

@Shatur95 Could you please test the following?

<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" style="border-radius: 50%">
  <path fill="#0052b4" d="M0 0h144.7l36 254.6-36 257.4H0z"/>
  <path fill="#d80027" d="M367.3 0H512v512H367.3l-29.7-257.3z"/>
  <path fill="#ffda44" d="M144.7 0h222.6v512H144.7z"/>
  <path fill="#d80027" d="M256 354.5V256h66.8v47.3zm-66.8-165.3H256V256h-66.8z"/>
  <path fill="#ff9811" d="M289.4 167a22.3 22.3 0 0 0-33.4-19.3 22.1 22.1 0 0 0-11.1-3c-12.3 0-22.3 10-22.3 22.3H167v111.3c0 41.4 32.9 65.4 58.7 77.8a22.1 22.1 0 0 0-3 11.2 22.3 22.3 0 0 0 33.3 19.3 22.1 22.1 0 0 0 11.1 3 22.3 22.3 0 0 0 19.2-33.5c25.8-12.4 58.7-36.4 58.7-77.8V167zm22.3 111.3c0 5.8 0 23.4-27.5 40.9a136.5 136.5 0 0 1-28.2 13.3c-7-2.4-17.8-6.7-28.2-13.3-27.5-17.5-27.5-35.1-27.5-41v-77.9h111.4z"/>
</svg>

In the meantime I'll draft a new release v2.0.0 so that you can revert back to v1.0.0 if you like.

@Shatur
Copy link

Shatur commented Feb 9, 2021

@Shatur95 Could you please test the following?

Same here.

But not problem, there is no issue here. The icons conform to SVG 1.1 and Qt implementation conform to Tiny 1.2, so rounding just not supported by Qt.
I will use square icons, they look good too as to me :)

In the meantime I'll draft a new release v2.0.0 so that you can revert back to v1.0.0 if you like.

Yes, it would be nice if you draft a new release.

@HatScripts
Copy link
Owner Author

Same here.

But not problem, there is no issue here. The icons conform to SVG 1.1 and Qt implementation conform to Tiny 1.2, so rounding just not supported by Qt.
I will use square icons, they look good too as to me :)

Hmm, that is very strange. So even the border-radius trick didn't work, despite it conforming to the spec? 😦 I'll have to do some further investigating.

Yes, it would be nice if you draft a new release.

Done :)

@Shatur
Copy link

Shatur commented Feb 9, 2021

Hmm, that is very strange. So even the border-radius trick didn't work, despite it conforming to the spec? frowning I'll have to do some further investigating.

Yes, I double checked it. It looks square both in the system and in the Qt application.

Done :)

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants
@waldyrious @HatScripts @Shatur @climech and others