将旗帜压缩至 11 位
Compressing a Flag to 11 Bits

原始链接: https://read.vantezzen.io/miniflags

受矩阵分解的数学效率启发,作者开发了一种用于存储国旗的紧凑二进制编码方案。通过识别常见的旗帜学元素(如条纹、形状和纵横比),该项目利用哈夫曼编码为常见模式分配更短的位序列,从而实现了约 12 个字符(使用 Base94)的平均存储大小。 该系统通过一系列分层组件(例如条纹或星星位置)来定义旗帜,而非基于像素的数据,随后通过一个轻量级的 5.29 KB TypeScript 解码器将其渲染为 SVG。虽然该模型对于法国或印度尼西亚国旗等简单的几何设计非常有效,但在处理复杂的徽章、非矩形形状或精细的书法等复杂图像时则较为吃力。 除了标准化旗帜数据外,该项目还展示了信息论在视觉设计中的实际应用,成功将 100 多面旗帜编码为极简且高度可移植的代码。源代码和在线渲染器可在 GitHub 上获取,为我们定义和存储视觉标识提供了一个新颖的视角。

Hacker News最新 | 过往 | 评论 | 提问 | 展示 | 招聘 | 提交登录将标志压缩至 11 位 (vantezzen.io)9 分,由 bennett_dev 发布于 1 小时前 | 隐藏 | 过往 | 收藏 | 1 条评论帮助 Smalltalker-80 8 分钟前 [–] 没人说吗?那好吧:“旗帜的乐趣!”(Fun with flags!)。好了,我说完了。回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文
I recently saw an interesting YouTube video by “Physics for the Birds”. The main video is about matrixes, but he used flags to explain some of the operations on matrixes:

He broke down how you could break down flags across its axes to represent them as matrixes, and save some storage space. In the end, this was used to circle back to actual matrixes and calculations on them.

However this inspired me: Flags are often so simple - just some colored stripes, common elements like a star, crescent or cross. Using some custom encoding scheme, shouldn’t defining the 🇫🇷French flag like “3 stripes, Blue, White, Red” with some custom decoder and renderer only take a handful of bits? How would such an encoding look like?

So I tried to create one:

Requirements

To start, I put down some acceptance criteria for my encoding:

  • The decoded flag has to be "recognizable enough”
    • Details don't have to be exactly correct. Small deviations, inaccuracies are ok - as long as somebody can look at it and say "Ah that's that flag!"
    • This includes things like the exact placement, shape details of objects.
    • This also includes the exact color. I know how proud France is of their new shade of blue, but for this encoding we can break down any blue tone into some middle blue
  • Only country flags
    • No state flags, city flags, or other vexillological designs
  • No 🇳🇵Nepal
    • Sorry Nepal, I like your non-rectangular flag but this would just complicate stuff
  • Simple flags should require very little bits, while very intricate flags are allowed to take up some more bits - so no fixed length
  • No coat of arms
    • Flags like 🇦🇩 Andorra contain their own coat of arms, for which you pretty much need some custom bitmap/vector to render it. That would just put some SVG or something in the tiny format so no

 

What makes a flag a flag?

To start, I needed to figure out what elements the common flag has. Worldometers List of all country flags was very helpful for this:

First thing: Damn, flags are more varied than I thought. Of course you have the simple striped ones like 🇫🇷France, 🇮🇹Italy or 🇩🇪Germany. Then you have designs like 🇧🇮Burundi or 🇧🇦Bosnia with stars, sections and everything. 🇲🇰North Macedonia and 🇸🇨Seychelles with radial stripes. 🇨🇿Czechia, 🇧🇸Bahamas and more with custom triangles at the left edge and so many more.

So what do most flags have in common. From what I see:

  1. Stripes
    1. Stripes, lot’s of stripes: 🇫🇷Horizontal, 🇩🇪Vertical, 🇵🇱 2 Stripes, 🇦🇲3 Stripes, 🇺🇸13 Stripes, 🇨🇴 Uneven stripes, 🇨🇬Angled Stripes
  1. Common Shapes
    1. 🇻🇳Stars, 🇩🇿Crescents, 🇯🇵Circles
    2. At custom sizes and positions - 🇻🇳one or 🇺🇸many
  1. Left color triangle
    1. Suprisingly common and in many colors - but often the same general shape 🇰🇲🇧🇸🇨🇿
  1. Top left corner colored
    1. Some kind of custom rectangle happening in the top left 🇺🇸🇬🇷
  1. Union Jack
    1. 🇬🇧🇦🇺🇳🇿🇹🇻
  1. And then the nordic countries
    1. They’re definitely a big plus to have… 🇳🇴🇸🇪🇩🇰🇫🇮

Breaking down the flag

From what I can tell, the protocol should define these elements:

  1. Aspect Ratio
    1. Flags all have their individual aspect ratios but there are clear patterns: around 45% of flags have the ratio 2:3, around 28% have 1:2, around 9% have 3:5 - and then some longtail of other formats
  1. Color palette
    1. As defined, we’ll focus on general color tones instead of relicating the exact color code. So we’ll focus on color groups like Blue, Green, Yellow
    2. Once again we have a similar picture: Surpisingly a lot of red, then white, blue, yellow/gold, green, black and orange - and then a longtail or other colors
  1. Layers
    1. I think it makes sense to define the actual contents using a handful of “Layers” instead of trying to hardcode all the common things separately. Each layer can then define its own list of options to supply
    2. This should work like in Photoshop or other apps: e.g. for the 🇺🇸US Flag you’d first have the stripe layer, then a layer for the blue rectangle, then a star layer
    3. The most common layer will be the “Stripe” layer. This can have options for the amount of stripes and its colors, the direction, even or uneven distribution, and repeating patterns
    4. Other layers should be a “Shapes” layer for stars, crecents and others - defining the position and rotation. A “Band” layer and a “Region” layer to color a specific rectangle

Turning it into bits

As with most things, so many aspects of flags also seem to follow Zipf’s Law - aspect ratio, colors used, elements on it (Stripes, stars etc.). To keep common elements short I thus decided to encode almost everything into its own Huffman Tree, assigning nice short binary codes for the common cases.

To allow the longtail to exist without creating a huge tree, I decided to cut off all values that only exist in a single flag and instead set the last leaf of the tree to a “Custom” value, followed by a fixed-length free value option. For example, this allowed 🇸🇻El Salvador to keep its 189:335 aspect ratio without needing that in the tree itself.

As an example, here is the full Huffman tree for the flag aspect ratio:

graph TD
    %% Internal Nodes
    root((Root))
    n1(( ))
    n11(( ))
    n110(( ))
    n1101(( ))
    n11011(( ))
    n111(( ))
    n1110(( ))
    n11100(( ))
    n111001(( ))
    n11101(( ))
    n111010(( ))
    n111011(( ))
    n1111(( ))
    n11110(( ))
    n111100(( ))
    n111101(( ))
    n11111(( ))
    n111110(( ))
    n1111100(( ))
    n1111101(( ))
    n111111(( ))
    n1111110(( ))
    n1111111(( ))
    n11111111(( ))

    %% Leaf Nodes (Ratios)
    L_2_3[2:3]
    L_1_2[1:2]
    L_3_5[3:5]
    L_5_8[5:8]
    L_10_19[10:19]
    L_3_4[3:4]
    L_4_7[4:7]
    L_1_1[1:1]
    L_7_10[7:10]
    L_8_11[8:11]
    L_11_18[11:18]
    L_11_20[11:20]
    L_11_28[11:28]
    L_18_25[18:25]
    L_1_phi[1:φ]
    L_4_5[4:5]
    L_6_7[6:7]
    L_10_17[10:17]
    L_13_15[13:15]
    L_15_22[15:22]
    L_16_25[16:25]
    L_189_335[189:335]
    L_28_37[28:37]
    L_5_7[5:7]
    L_7_11[7:11]
    L_CUSTOM[CUSTOM]

    %% Left Branch (0...)
    root -- 0 --> L_2_3
    root -- 1 --> n1
    
    n1 -- 0 --> L_1_2
    n1 -- 1 --> n11
    
    %% 110... Branch
    n11 -- 0 --> n110
    n110 -- 0 --> L_3_5
    n110 -- 1 --> n1101
    n1101 -- 0 --> L_5_8
    n1101 -- 1 --> n11011
    n11011 -- 0 --> L_10_19
    n11011 -- 1 --> L_3_4

    %% 111... Branch
    n11 -- 1 --> n111
    n111 -- 0 --> n1110
    
    %% 1110... Sub-branches
    n1110 -- 0 --> n11100
    n11100 -- 0 --> L_4_7
    n11100 -- 1 --> n111001
    n111001 -- 0 --> L_1_1
    n111001 -- 1 --> L_7_10
    
    n1110 -- 1 --> n11101
    n11101 -- 0 --> n111010
    n111010 -- 0 --> L_8_11
    n111010 -- 1 --> L_11_18
    n111011 -- 0 --> L_11_20
    n111011 -- 1 --> L_11_28
    n11101 -- 1 --> n111011

    %% 1111... Branch
    n111 -- 1 --> n1111
    n1111 -- 0 --> n11110
    
    %% 11110... Sub-branches
    n11110 -- 0 --> n111100
    n111100 -- 0 --> L_18_25
    n111100 -- 1 --> L_1_phi
    n1111001(( ))
    n11110 -- 1 --> n111101
    n111101 -- 0 --> L_4_5
    n111101 -- 1 --> L_6_7

    %% 11111... Branch
    n1111 -- 1 --> n11111
    n11111 -- 0 --> n111110
    
    %% 111110... Sub-branches
    n111110 -- 0 --> n1111100
    n1111100 -- 0 --> L_10_17
    n1111100 -- 1 --> L_13_15
    n111110 -- 1 --> n1111101
    n1111101 -- 0 --> L_15_22
    n1111101 -- 1 --> L_16_25

    %% 111111... Deepest Branch
    n11111 -- 1 --> n111111
    n111111 -- 0 --> n1111110
    n1111110 -- 0 --> L_189_335
    n1111110 -- 1 --> L_28_37
    
    n111111 -- 1 --> n1111111
    n1111111 -- 0 --> L_5_7
    n1111111 -- 1 --> n11111111
    n11111111 -- 0 --> L_7_11
    n11111111 -- 1 --> L_CUSTOM

    %% Styling for better readability
    classDef leaf fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
    classDef internal fill:#eceff1,stroke:#607d8b,stroke-width:1px;
    
    class L_2_3,L_1_2,L_3_5,L_5_8,L_10_19,L_3_4,L_4_7,L_1_1,L_7_10,L_8_11,L_11_18,L_11_20,L_11_28,L_18_25,L_1_phi,L_4_5,L_6_7,L_10_17,L_13_15,L_15_22,L_16_25,L_189_335,L_28_37,L_5_7,L_7_11,L_CUSTOM leaf;
    class root,n1,n11,n110,n1101,n11011,n111,n1110,n11100,n111001,n11101,n111010,n111011,n1111,n11110,n111100,n111101,n11111,n111110,n1111100,n1111101,n111111,n1111110,n1111111,n11111111 internal;

So for the 45% of flags with an aspect ratio of 2:3, all that’s needed is setting the first bit to 0.

 

The format then contains Huffman trees for:

  • Aspect Ratio (Most common: 2:3, 1:2, 3:5)
    • Custom Width:Height for longtail
  • Color Palette size (Most common: 3, 2, 4, 5)
    • Custom “Count - 7” for longtail, since the tree goes up to 7
  • Color (Most common: Red, White, Blue)
    • Custom colors can be given as compact 10-bit RGB approximation (RRR GGGG BBB)
  • Layer Type (Stripes, Shape, Regions, Cross, Band, Buildin)
  • Some more specialized subtrees
    • e.g. Number of points on the star, shape placement

The whole format is pretty much just one Huffman tree walk after the other to define what the flag is made of.

 

With this, we can try encoding our first flag into the format. For this, I’ll take 🇮🇩Indonesia, as its definitely our encoding winner, or I guess “The most average flag”:

  • A 2:3 aspect ratio (1 bit to encode) → 0
    • the most common aspect ratio
  • A color palette size of 2 colors (2 bits) → 10
    • This is the only place it looses a bit, since 3 colors is the most common in the tree
  • Then definining the 2 colors: Red (2 bits) → 00 and white (2 bits) → 01
    • the 2 top-most colors in the tree
  • A layer count of 1 layer (1 bit) → 0
  • A stripe layer (1 bit) → 0, “palette-equal” mode (i.e. give every color in the palette one equal stripe, 1 bit) → 0 and horizontal stripes (1 bit) → 0
    • All the top of their Huffman tree
  • → All combined: 0 10 00 01 0 0 0 0, or base64 encoded “QgA=

Using this format, the average flag can be represented in 76 bits, with a median of 55 bits.

 

The longest one is 🇶🇦Qatar at 420bits: #gHR1Y$?-+]m.0xS3F!0{.UH{uDppW5u2^+s|6~(p@GwHH<N:?57K99\(s)~!G4`! . I’d say that flag can only barely be encoded into our format: It has a zig-zagged edge between the sides, which I encoded as 11 individual rectangle layers.

Union Jack 🇬🇧

You could say I cheated a little here. The union jack is so common but so complicated to build from layers that I just put the whole thing as just a build-in shape in the protocol. So instead of building it, a flag can just specify “Union Jack Layer in the lop left” to place it.

Encoding it

I first used base64 to simply turn the bits into saveable text. With its 6 payload bits per ASCII byte, the average flag definition needed 14 characters with a median of 12 characters. The shortest flag code is “QgA=” for Indonesias flag.

However, I decided to use Base94, which uses every visible one-byte ASCII character from “!” through “~” to try improving the efficiency of encoding some more.

I also thought of some Emoji-based encoding, but since every character would require more than 1 byte to save there, the reduced character count would probably still require mote bits in total. (Also I didn’t want to risk encoding a countries flag as “💩🤮👎” or something like that…)

With this, we’re down to an average of 12 characters for a flag, with a median of 9 characters. Indonesia stays the shortest encoding: “<F”.

Rendering it

I used ChatGPT Codex to turn the format into a two-step system: A encoder/decoder and an SVG renderer.

The decoder first turns the binary blob into a readable format. For 🇮🇩Indonesias flag we already broke down, this looks like this:

{
  "aspectRatio": {
    "kind": "rational",
    "height": "2",
    "width": "3"
  },
  "palette": [
    {
      "r": 210,
      "g": 16,
      "b": 52
    },
    {
      "r": 255,
      "g": 255,
      "b": 255
    }
  ],
  "layers": [
    {
      "kind": "stripes",
      "direction": "horizontal",
      "stripes": [
        {
          "color": 0
        },
        {
          "color": 1
        }
      ]
    }
  ]
}

The renderer then takes this decoded flag and turns it into an SVG code like this:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1.5 1">
	<rect x="0" y="0" width="1.5" height="0.5" fill="#d21034"/>
	<rect x="0" y="0.5" width="1.5" height="0.5" fill="#fff"/>
</svg>

The code behind it is pretty nice, with some separate class and interfaces for the encoded binary parts and layers. However, even tsc-compiled the encoder/decoder sits at 27kB and the renderer at 12.5kB - maybe a bit much after just optimizing every bit of the source format…

So I went back to Codex to create an alternative “minidecoder”: Combining decoding and rendering into one pass instead of two separate engines, getting rid of the nice interface/class infrastructure, and just boiling everything down to small primitive functions.

WIth this, we got down to one 470-line TypeScript file, 5.29 kB (2.66 kB gzipped) when compiled.

Flags that aren’t covered

With this (rather primitive) format I managed to encode 128 flags with varying success.

However, there we also 67 flags that I couldn’t manage to encode. Some notable reasons:

  • 🇪🇸 Spain, 🇬🇶 Equatorial Guinea, 🇦🇩 Andorra, 🇧🇿 Belize, 🇧🇳 Brunei, 🇰🇭 Cambodia, 🇨🇷 Costa Rica, 🇭🇷 Croatia, 🇩🇴 Dominican Republic, 🇪🇨 Ecuador, 🇸🇻 El Salvador, 🇫🇯 Fiji, 🇭🇹 Haiti, 🇲🇽 Mexico, 🇲🇩 Moldova, 🇲🇪 Montenegro, 🇳🇮 Nicaragua, 🇴🇲 Oman, 🇵🇾 Paraguay, 🇵🇹 Portugal, 🇸🇲 San Marino, 🇷🇸 Serbia, 🇸🇰 Slovakia, 🇸🇮 Slovenia, 🇻🇪 Venezuela
    • Contain a coat of arms, seal or national emblem (25)
  • 🇦🇴 Angola, 🇧🇧 Barbados, 🇸🇿 Eswatini, 🇬🇹 Guatemala, 🇰🇪 Kenya, 🇱🇸 Lesotho, 🇱🇮 Liechtenstein, 🇲🇹 Malta, 🇲🇿 Mozambique, 🇹🇯 Tajikistan, 🇻🇦 Vatican City
    • Object glyphs such as weapons, tools, crowns, shields or a hat (11)
  • 🇦🇱 Albania, 🇧🇹 Bhutan, 🇩🇲 Dominica, 🇪🇬 Egypt, 🇰🇮 Kiribati, 🇵🇬 Papua New Guinea, 🇱🇰 Sri Lanka, 🇺🇬 Uganda, 🇿🇲 Zambia, 🇿🇼 Zimbabwe
    • Animal glyphs, mostly eagles and other birds (10)
  • 🇨🇦 Canada, 🇨🇾 Cyprus, 🇪🇷 Eritrea, 🇬🇩 Grenada, 🇱🇧 Lebanon
    • Plant glyphs such as leaves, branches or a nutmeg (5)
  • 🇦🇬 Antigua and Barbuda, 🇧🇷 Brazil, 🇳🇵 Nepal, 🇿🇦 South Africa, 🇻🇺 Vanuatu
    • Geometry the layer model cannot express, such as Y-shapes, V-fields, a globe or a non-rectangular outline (5)
  • 🇦🇫 Afghanistan, 🇮🇷 Iran, 🇮🇶 Iraq, 🇸🇦 Saudi Arabia
    • Arabic text or calligraphy (4)
    • Adding some custom Text layer could help here
  • 🇮🇳 India, 🇰🇬 Kyrgyzstan, 🇲🇳 Mongolia, 🇰🇷 South Korea
    • Religious or cultural symbols such as the Ashoka Chakra, tunduk, Soyombo or Taegeuk (4)
  • 🇧🇾 Belarus, 🇰🇿 Kazakhstan, 🇹🇲 Turkmenistan
    • Ornamental hoist patterns

→ So pretty much all contain some custom glyph or text that’s not easily representable with out geometry-based layers

Random flags!

Now we have kind of a structured language of what a flag might look like, so I decided to add a randomizer to fill a new flag with random items from the various Huffman trees we have.

…yeah I guess I can see some country having a flag like this?

 

All in all

I’m sure others can find even more bit-saving methods or completely different ways of compressing or laying out the data. However it was still quite a lot of fun to go from a list of all flags, finding common elements and searching for ways to pack as much data as possible into the bits. Finally getting to use my Huffman Coding knowledge from my Bachelors and writing some bit-level code was also very fun.

 

You can find the final page with the flags on https://vantezzen.github.io/miniflags/ and the source code at . Please don’t expect super clean code there - in the end it was mostly vibe coded based on my format ideas. All details about the format are documented at .
联系我们 contact @ memedata.com