Euclidean Distance in N dimensions and Naive Image Quantization

I'll find something good to put here.
Search for a command to run...

I'll find something good to put here.
One thing worth noting is that the HEX color representation is an abstraction of actual color. There are other color representations such as 48-bit color that can represent 281.5 trillion colors and CYMK that uses a 4 dimensional vector. All of these are simply abstractions of the visible light spectrum.
Also, the cones in eyes can generally distinguish 100 shades. Color blind people, typically only have 2 types of cones and thus can distinguish 10,000 colors (100x100). Most people are "trichomatic" meaning they have 3 types of cones and can thus distinguish 1,000,000 (100x100x100) colors. This is a far cry from the 16 million colors that can be represented with RGB. Although, some people (women exclusively) have an extra cone that allows them to see a wopping 100,000,000 colors. These people are considered "tetrachomatic".
Love it! Keep โem coming!
This is rad.
In this series I will write about applying various mathematics principals to software development. Photo by [Valdemaras D.](https://www.pexels.com/@valdemaras-d-784301) from Pexels
In this post I explain mapping number scales and how to write simple function to convert from one scale to another. Every time I find myself needing to do this, I have to spend time reasoning my way through it. It's been a number of years since my l...
A quick code snippet to hide button text on small screens using MUI and TypeScript

A look at 7 different TypeScript scenarios requiring dependent and conditional typing

The Covid-19 pandemic got me scrambling to come up with some web based solutions to support local Summer art festivals. In April I started digging into WebRTC as a potential in-browser low-barrier-to entry Real Time video solution. After some success...

Refs in React have gone through several changes since their introduction. With the release of hooks in React 16.8, useRef became a handy way to interact with refs, especially in functional components. However, as TypeScript has come to dominate the J...

When I pitched this article to my graphic designer wife, she looked at me like I was speaking in tongues. However, I can't think of a more concise title. What this article will ultimately discuss is how something as seemingly non-quantitative as color can actually be represented as linear data and thus have linear operations applied. One such operation is finding how close two colors are to each other. If that is more interesting than the title suggests, keep reading.
First some definitions of data:
The values "large", "medium", and "small" are qualitative because they do not represent quantifiable values on a number scale even though they have an obvious relationship. In the USA, most Jeans are measured by waist size in inches. A measurement in inches is a quantitative value. It doesn't make sense to say that "small" is half that of "large". Whereas, it does make sense to say a 25 inch waist is half that of a "50 inch waist".
๐คท Aside: When trying to come up with an example, my wife tried to explain how women's clothes sizing works... It's a nonsensical nightmare using numbers in a qualitative way.
Next, let's discuss multiple "dimensions". No, this won't cover quantum physics or the Teenage Mutant Ninja Turtle multiverse. In algebra, a dimension is simply a collection of values needed to represent a point in a space.
One thing worth noting is that each dimension N is a subset of dimension N+1. That is to say a 1D line exists within a 2D plane. A 2D plane exists within a 3D space. All 3D spaces existing within a 4D space and so on. In the example of a line, there is only one dimension we care about. All the other dimensions still exist, they just have a value of 0 when describing that point in space.
You probably learned the "distance formula" in basic algebra for finding the distance between two points on a 2D graph. Here is a review:
๐=sqrt((๐ฅ2โ๐ฅ1)^2+(๐ฆ2โ๐ฆ1)^2)
Essentially, you square the difference in x values, add it to the square of differences in y values and then take the square root of that sum.
For example, given, p1 = (2, 3) and p2 = (4, 5), applying the distance formula gives:
=> sqrt((4-2)^2 + (5-3)^2)
=> sqrt(2^2 + 2^2)
=> sqrt(4 + 4)
=> 2.828427
Next, think about distance in 1 dimension (a line). Intuitively, the distance between two points is a simple subtraction. However, let's apply the distance formula in 1 dimension. To do this, remember the aforementioned bit about a 1D line being a subset of a 2D plane.
For example, given the points on a line x1 = 2 and x2 = 6, we can express this as 2D points p1 = (2, 0) and p2 = (6, 0). Note: The y values are 0.
=> sqrt((6-2)^2 + (0-0)^2)
=> sqrt(4^2 + 0^2)
=> sqrt(16 + 0)
=> 4

This should make intuitive sense. However, notice the y related term (0-0)^2 evaluates to 0. This is because there are no y values. If we eliminated that term completely, the formula would become:
๐=sqrt((๐ฅ2โ๐ฅ1)^2)
Since the square root and the square cancel each other out, we get the intuitive formula for d = ๐ฅ2โ๐ฅ1.
If distance in one dimension is ๐=sqrt((๐ฅ2โ๐ฅ1)^2) and distance in two dimensions is ๐=sqrt((๐ฅ2โ๐ฅ1)^2 + (๐ฆ2โ๐ฆ1)^2) we simply need to keep adding terms for each dimension N.
๐3=sqrt((๐ฅ2โ๐ฅ1)^2 + (๐ฆ2โ๐ฆ1)^2 + (z2โz1)^2)
๐4=sqrt((๐ฅ2โ๐ฅ1)^2 + (๐ฆ2โ๐ฆ1)^2 + (z2โz1)^2 + (t2โt1)^2)
๐n=sqrt((๐ฅ2โ๐ฅ1)^2 + (๐ฆ2โ๐ฆ1)^2 + (z2โz1)^2 + ... + (n2โn1)^2)
A color, such as "purple" or "orange" is qualitative data using the above definitions. However, you are almost surely familiar with HTML hexadecimal notation or RGB notation. Both systems represent colors as 3 values (red, green, blue), each on a scale from 0 - 255. A color is thus 3 dimensional data! Assuming each of the three values can be in the range of 0 - 255, then there are over 16 million unique colors using these representations (256 x 256 x 256 = 16777216). This is also known as 24 bit or True Color).
๐ง Note: A hexadecimal number is merely a value in the range of 0-15 (represented as 0-F). A two digit hexadecimal number (0-FF) can represent 16 different values per digit (including 0). Thus, a two digit hexadecimal can represent 256 different values (16x16 or 16^2). As such, HTML hexadecimal color codes are just a convenient syntax for representing r,g,b format into a single base 16 number. #FFFFFF is the color "white" but also the decimal number 16777215.
The w3c defines 140 named colors for the web. Part of defining these is to assign quantitive r,g,b values to qualitative color names.

To recap, we know how to represent colors as 3 dimensional data. We also know how to find distance in 3 dimensions. Applying what we learned, we can now find the "distance" between two colors.
distance("orange", "purple")
=> distance((255, 165, 0), (128, 0, 128))
=> sqrt((128 - 255)^2 + (0 - 165)^2 + (128 - 0)^2)
=>sqrt(16129 + 27225 + 16384)
=> 244.413583911
The difference between "orange" and "purple" is quantifiable and measurable. I don't know about you, but I think this is pretty neat.
Below is the distance formula in TypeScript. It allows finding the difference between two points of different dimensions assuming one point is a subset of the other.
const distance = (p1:number[], p2: number[]): number => {
const maxDimension = p1.length > p2.length ? p1.length : p2.length
let i = 0;
let sum = 0.0;
let n1 = 0.0;
let n2 = 0.0;
while (i < maxDimension) {
n1 = p1[i] || 0;
n2 = p2[i] || 0;
sum = sum + Math.pow(n2 - n1, 2);
i++
}
return Math.sqrt(sum)
}
Using it to find the difference between "orange" and "purple":
distance([255, 165, 0], [128, 0, 128]) // 244.4135839105511
According to Wikipedia, Quantization, in general, is the process of constraining an input from a continuous or otherwise large set of values to a discrete set. In graphics, quantization is used to apply a limited palette of colors to an image.
A brute force (aka naive) approach to image quantization is as follows:
Note: I say this approach is naive, because it is terribly inefficient. Algorithms such as median cut do a far more performant job by leveraging value ranges to reduce the number of calculations required.
The algorithm for finding the color in a palette closest to a target pixel color is as follows:
type RGB = [number, number, number];
const closestColor = (color:RGB, palette: RGB[]): RGB => {
let minRGB: RGB = palette[0];
let minDistance = Infinity;
let i = 0;
let paletteSize = palette.length;
let iDistance = 0;
for (i = 0; i < paletteSize; i++) {
iDistance = distance(color, palette[i]);
if (iDistance < minDistance) {
minDistance = iDistance;
minRGB = palette[i];
}
}
return minRGB;
}
For brevity, I won't include the TypeScript version of the naive quantization algorithm. Since, it is dependent on the storage structure of pixels for image data (i.e. HTML5 canvas uses a flatUint8ClampedArray which might merit its own article).
However, here is the Jellyfish image for this post quantized to a palette of spray paint colors I have in my garage:
As we have seen, colors can be quantified into 3 Dimensional space and have algebraic operations applied to them. I did not take graphics in college, and when I discovered this, it rocked my world.
As an artist, I learned this while writing a program to do color separations for making stencils. Home Depot publishes the Hex codes for their Rustoleum 2x Spray paint. I then wrote a program to quantize an image based on my inventory of Rustoleum paint. You can see this in action on this complementary HTML5 canvas code lab example on my site.
Part of the reason I love topics like these is that they pull together programming, math, computer science and art. If you like these sorts or articles, let me know in the comments and I'll keep writing them. Check out my series: Applied Math for other ways math pops up in software development.
Image Credit: Photo by Alison M from Pexels