While thinking of posts to write about, the title "Fun with Flags" came to mind from a certain TV show and I wondered how I might connect that to programming. There are enum flags in C# via the Flags
attribute so maybe that was something I could write about. That said, I wanted to do something more creative than some humdrum post about using enums even if the end result isn't really practical.
Instead, I decided to make real flags in C# with enums - turning a number like 52357729848
into a flag:
I gave myself certain requirements:
I needed to be able to generate more than one flag
I wanted to encode everything about the flag in a single value via enums
There are technical limitations too as I can't store a lot of data in an enum and will need to make compromises. An enum can be backed by a few different types but I chose long
so I could get a full 64-bits of data to play with.
My initial thought with this was to pick the easiest form of flag - simple flags with stripes. If I split a typical flag into 9 segments, maybe I can store 9 colours and that would allow drawing of horizontal and vertical stripes. It seemed like the most straightforward approach at the time (I realised later it might have been better if I stored "shape" data instead for more flag variety but oh well).
The problem is, storing 9 segments in 64 bits is pretty hard and would leave me with about 7.11-bits per segment making colour data very limited. I wanted 3 colour channels so that only gives me really 2-bits per colour which is not a lot of variety. Having then 6-bits per segment left me with 10-bits that I don't really have much use for. Initially, I tried using those bits to help extend the range of colours, acting as a multiplier for a specific channel. In the end though, it wasn't overly useful for this so I cut it.
This is the data structure I ended with:
[PPPPPPPPPP]
[RRGGBB][RRGGBB][RRGGBB]
[RRGGBB][RRGGBB][RRGGBB]
[RRGGBB][RRGGBB][RRGGBB]
P = Padding
R = Red Intensity
G = Green Intensity
B = Blue Intensity
My primary data only takes 54-bits so my data structure has 10-bits of padding at the front. Having the padding at the front allows the generated number to be smaller.
I will be using ImageSharp for converting this value into an actual image. Because I have 9 segments, it seemed like the best idea to treat the image as a 3x3 pixel square and get ImageSharp to resize it for me. The pixel format for the data though was RGB24 so I needed to work out how to scale up my colours from 2-bits to 8-bits per channel.
With 8-bits, the max value I can have is 255 for a single channel. Full-colour intensity for any channel is 3 so I decided to simply divide the max value by the full-colour intensity leaving me the magic number of 85 to scale my values by.
That is the nuts-and-bolts of the format, now it was just to make that work in code.
Knowing I can store the data is one thing, actually making it work was another. I don't often use Bitwise and shift operators but for this, it was going to use them quite heavily.
Firstly, we need an enum of colour intensity values:
public enum Intensity : byte
{
None = 0,
OneThird = 1,
TwoThirds = 2,
Max = 3
}
The specific values here are important because of what they represent in binary.
00000000 // Intensity.None
00000001 // Intensity.OneThird
00000010 // Intensity.TwoThirds
00000011 // Intensity.Max
Because these values represent a single channel's intensity, we need to combine 3 of them together to form our full colour. We combine them by using bit shifting and bitwise OR operations to create our 6-bit colour value.
public enum Colour : long
{
Black = 0,
Red = Intensity.Max << 4,
Green = Intensity.Max << 2,
Blue = Intensity.Max,
White = Red | Green | Blue
}
While we are using a long
here (helping us with our later bit shifting operations), the values we are setting fit within 6-bits. Viewing the colours as bytes in binary, the shifting and OR-ing of data would look a little like this:
00110000 // Red = Intensity.Max << 4
00001100 // Green = Intensity.Max << 2
00000011 // Blue = Intensity.Max
00111111 // White = Red | Green | Blue
Using different intensity values for the different colour channels, we can create new colours too.
00110000 // Red = Intensity.Max << 4
00001000 // Green = Intensity.TwoThirds << 2
00000000 // Blue = Intensity.None
========
00111000 // Yellow = (Intensity.Max << 4) | (Intensity.TwoThirds << 2)
To create a few different types of flags, we will need a few more colours...
public enum Colour : long
{
Black = 0,
Red = Intensity.Max << 4,
Green = Intensity.Max << 2,
Blue = Intensity.Max,
White = Red | Green | Blue,
Orange = (Intensity.Max << 4) |
(Intensity.OneThird << 2),
Yellow = (Intensity.Max << 4) |
(Intensity.TwoThirds << 2),
MediumGreen = Intensity.TwoThirds << 2,
LightBlue = (Intensity.TwoThirds << 2) |
Intensity.Max,
DarkBlue = Intensity.OneThird
}
Identifying the right combinations of values for colours was relatively straightforward - I used an RGB colour picker in Paint.NET and selected thirds of the different colour channels. Like if I had two-thirds red and one-third green, I'd approximately have orange.
So now we've got our colours, we need to encode the final value of a flag. In a similar approach to combining the colour channels, we need to combine the colours of the 9 segments by shifting and OR-ing.
public enum CountryFlags : long
{
Germany = Colour.Black << 48 | Colour.Black << 42 | Colour.Black << 36 |
Colour.Red << 30 | Colour.Red << 24 | Colour.Red << 18 |
Colour.Yellow << 12 | Colour.Yellow << 6 | Colour.Yellow
}
While Colour.Black
does encode as 0
so the first 3 values aren't actually needed, it made it easier to still think of it as 9 distinct segments that all needed colours set.
In binary, the operation to encode our German flag would look like:
00000000 // Black, shifted by 48-bits
00000000
00000000
00110000 // Red, shifted by 30-bits
00110000
00110000
00111000 // Yellow, shifted by 12-bits
00111000
00111000
================================================================
0000000000000000000000000000110000110000110000111000111000111000
As a decimal, that would be 52357729848
. This is only half the job though, we have our flag data as a number but we also need to decode it to an image.
So how do we take 52357729848
and turn it into an image? We use more bit shifting and now AND-ing of our data to get each individual colour. Also, we will be reading the data in reverse.
var blueComponent = (byte)(flagData & 3) * 85;
The value flagData
here is a long
of our generated number.
To get the blue component, we don't need to shift but we do need to perform a logical AND of the data. We only want the last two bits of the number - if we just convert the number to a byte, we will get the last 8-bits.
0000000000000000000000000000110000110000110000111000111000111000
// We only want this part ^^
By doing flagData & 3
, we get just the last 2-bits from the full value. To get the next components, we do the same but now on a bit shifted value so the last 2-bits are of the colour we want.
var greenComponent = (byte)((flagData >> 2) & 3) * 85;
var redComponent = (byte)((flagData >> 4) & 3) * 85;
Now we have the 3 colour channels of the bottom right segment of our flag. As a reminder, the 85x multiplier is to adjust the colour to fit within a full 8-bits for the RGB24 pixel format. Really now, it is just a matter of wrapping the code within some loops to set it to an image.
using var image = new Image<Rgb24>(3, 3);
for (var y = 2; y >= 0; --y)
{
for (var x = 2; x >= 0; --x)
{
var pixel = image[x, y];
var blueComponent = (byte)((flagData >> 0) & 3) * 85;
pixel.B = (byte)blueComponent;
var greenComponent = (byte)((flagData >> 2) & 3) * 85;
pixel.G = (byte)greenComponent;
var redComponent = (byte)((flagData >> 4) & 3) * 85;
pixel.R = (byte)redComponent;
flagData >>= 6;
image[x, y] = pixel;
}
}
In our inner-most loop, we also shift our bits in flagData
over 6-bits so we are in the next segment for the next iteration. This code though would only leave us with a 3x3 flag which doesn't look right so with a little more code, we can make it be bigger and more flag-like.
image.Mutate(x => x.Resize(400, 240, new NearestNeighborResampler()));
The NearestNeighborResampler
here is important - it allows us to scale up our specific "blocky" image here without distorting or blurring it.
And that's basically it - we can take a bunch of enums and encode a value then take the value and decode it to an image. I've set up an Azure Function running this code to show it working and a few flags I've generated:
https://funwithflags.turnerj.com/api/flag/generate.png?v=ENCODED_VALUE
Value: 52357729848
Germany = Colour.Black << 48 | Colour.Black << 42 | Colour.Black << 36 |
Colour.Red << 30 | Colour.Red << 24 | Colour.Red << 18 |
Colour.Yellow << 12 | Colour.Yellow << 6 | Colour.Yellow,
Value: 2532184938287088
Italy = Colour.MediumGreen << 48 | Colour.White << 42 | Colour.Red << 36 |
Colour.MediumGreen << 30 | Colour.White << 24 | Colour.Red << 18 |
Colour.MediumGreen << 12 | Colour.White << 6 | Colour.Red,
Value: 561852585091056
France = Colour.DarkBlue << 48 | Colour.White << 42 | Colour.Red << 36 |
Colour.DarkBlue << 30 | Colour.White << 24 | Colour.Red << 18 |
Colour.DarkBlue << 12 | Colour.White << 6 | Colour.Red,
Value: 2532459817242612
Ireland = Colour.MediumGreen << 48 | Colour.White << 42 | Colour.Orange << 36 |
Colour.MediumGreen << 30 | Colour.White << 24 | Colour.Orange << 18 |
Colour.MediumGreen << 12 | Colour.White << 6 | Colour.Orange,
Value: 13725272368788171
Luxembourg = Colour.Red << 48 | Colour.Red << 42 | Colour.Red << 36 |
Colour.White << 30 | Colour.White << 24 | Colour.White << 18 |
Colour.LightBlue << 12 | Colour.LightBlue << 6 | Colour.LightBlue,
What started out as a bit of a weird challenge I set myself turned into a fun and interesting learning experience. I rarely mess around with bit shifting and bitwise operations. While I still probably won't need to very often, I like that I know a lot more about them now.
If I were to approach this again, I'd probably look at encoding shapes instead of pixels of colours. That way I could encode a wider variety of flags like the flags of Sweden, Japan and perhaps even South Korea.
If you liked the kinda strange and interesting nature of this, you might like my deep dive into Levenshtein Distance and the various optimizations.