# Simplification of the colors of a picture

Hello friends,
Starting from a picture I try to standardize the colors, simplify them.
I assumed that each R, G and B component can take the values ​​0, 64, 128, 192 and 255.
We thus have 125 colors.
For each pixel of the image, we search the closest color in the palette.

Unfortunately for example on the extract here below our eye perceives a sky blue color but for different color modes (RGB, HSV, LAB, XYZ) it is white after simplification.

Does anyone have an idea for making sky blue stay sky blue (255,255,192)?

What algorithm are you using to map the original colors to the simplified colors?

2 Likes

How about converting the source and target colours to LAB, calculating the delta-e between the source and target colours and use the one with the smallest value.

1 Like

Eric : I divided each component (R, G and B) by 64 by rounding the value, so the components took as value 0, 64, 128, 192 or 255.
Kevin : I used this method but as we nest 2 loops it can take a lot of time.
In the meantime I tried to transform the RGB color into HSV. There the color is recognized and depending on the brightness and the saturation I would like to be able to bring the color back to the closest to that in the palette.

Distance.

It is my understanding that you’re converting an image to pelleted color (without converting the data format of the image).

It can be done with RGB, albeit it probably will produce different results when compared to other color formats. Yes, it requires at minimum two loops.

If you get the pixel data as a memory block (this can be done via declares), you loop through each pixel in a linear fashion (instead of a grid).

You then compare each pixel to all the values in the palette, and like @kevin_g says, you can calculate the delta-e or distance between the source pixel and the palette element. The one with the smallest distance wins.

A simple distance calc could be

`colorDifference = abs( source.red - target.red ) + abs( source.green - target.green) + abs( source.blue - target.blue )`

in GLSL you simply use the distance function

`colorDifference = distance( source, target )`

Personally, I believe if you want it to be as accurate as can be, you would use a luminance priority color format and you find all the certain palette elements that come close to the source pixel’s luminance. Then you loop again through those comparing the remaining color channels. This way it reduces the chances of one channel offsetting another and creating a false match. Heck because you’re only matching one channel at a time, it may even be slightly faster… Albeit you have the overhead of the color format conversion.

This yellow color would be a completely wrong approximation for sky blue (87CEEB)

Notice that blue is the highest value of the RGB, and in your simplification it is the lowest one.

But why choosing a 125 color palette? Why not for example the standard 216 colors Web Safe palette?

I made my own WebSafe one. Seems to work.

``````Private Function NearestWebSafe(oneColorValue As Integer) As Integer

Var near As Integer = oneColorValue \ 51
If oneColorValue mod 51 > 25 Then near = near + 1 // the nearest is the next one above
Return near * 51

End Function

Private Function WebSafeColor(c As Color) As Color

Return Color.RGB(NearestWebSafe(c.Red), NearestWebSafe(c.Green), NearestWebSafe(c.Blue), c.Alpha)

End Function

``````

A complete WebSafe Module with indexed palette handling

``````#tag Module
Protected Module WebSafeColors
#tag Method, Flags = &h0
Function GetWebSafeColor(idx As Integer) As Color
If not WebSafeTablesInited Then InitWebSafeTables()
If idx < 0 Then idx = 0
If idx > 215 Then idx = 215
Return WebSafeIndexToColorTable(idx)
End Function
#tag EndMethod

#tag Method, Flags = &h0
Function GetWebSafeIndex(c As Color) As Integer

If not WebSafeTablesInited Then InitWebSafeTables()
Return WebSafeColorToIndexTable.Value(WebSafeColor(c.Red, c.Green, c.Blue, 0))

End Function
#tag EndMethod

#tag Method, Flags = &h21
Private Sub InitWebSafeTables()

WebSafeTablesInited = True
WebSafeColorToIndexTable = New Dictionary
For r As Integer = 0 to 255 step 51
For g As Integer = 0 to 255 step 51
For b As Integer = 0 to 255 step 51
Var aColor As Color = Color.RGB(r,g,b,0)
WebSafeColorToIndexTable.Value(aColor) = WebSafeIndexToColorTable.LastIndex
Next
Next
Next

End Sub
#tag EndMethod

#tag Method, Flags = &h21
Private Function NearestWebSafe(oneColorValue As Integer) As Integer

Var near As Integer = oneColorValue \ 51
If oneColorValue mod 51 > 25 Then near = near + 1 // the nearest is the next one above
Return near * 51

End Function
#tag EndMethod

#tag Method, Flags = &h0
Function WebSafeColor(c As Color) As Color

Return Color.RGB(NearestWebSafe(c.Red), NearestWebSafe(c.Green), NearestWebSafe(c.Blue), c.Alpha)

End Function
#tag EndMethod

#tag Method, Flags = &h0
Function WebSafeColor(r As Integer, g As Integer, b As Integer, a As Integer = 0) As Color

Return Color.RGB(NearestWebSafe(r),NearestWebSafe(g),NearestWebSafe(b), a)

End Function
#tag EndMethod

#tag Property, Flags = &h21
Private WebSafeColorToIndexTable As Dictionary
#tag EndProperty

#tag Property, Flags = &h21
Private WebSafeIndexToColorTable() As Variant
#tag EndProperty

#tag Property, Flags = &h21
Private WebSafeTablesInited As Boolean = False
#tag EndProperty

#tag ViewBehavior
#tag ViewProperty
Name="Name"
Visible=true
Group="ID"
InitialValue=""
Type="String"
EditorType=""
#tag EndViewProperty
#tag ViewProperty
Name="Index"
Visible=true
Group="ID"
InitialValue="-2147483648"
Type="Integer"
EditorType=""
#tag EndViewProperty
#tag ViewProperty
Name="Super"
Visible=true
Group="ID"
InitialValue=""
Type="String"
EditorType=""
#tag EndViewProperty
#tag ViewProperty
Name="Left"
Visible=true
Group="Position"
InitialValue="0"
Type="Integer"
EditorType=""
#tag EndViewProperty
#tag ViewProperty
Name="Top"
Visible=true
Group="Position"
InitialValue="0"
Type="Integer"
EditorType=""
#tag EndViewProperty
#tag EndViewBehavior
End Module
#tag EndModule

``````

This morning I revisited the problem, and simplified it to pure math instead of precalculated tables

``````#tag Module
Protected Module WebSafeColors
#tag Method, Flags = &h21
Private Function NearestWebSafe(oneColorValue As Integer) As Integer

Var near As Integer = oneColorValue \ 51
If oneColorValue mod 51 > 25 Then near = near + 1 // the nearest is the next one above
Return near * 51

End Function
#tag EndMethod

#tag Method, Flags = &h0
Function WebSafeColor(c As Color) As Color

Return Color.RGB(NearestWebSafe(c.Red), NearestWebSafe(c.Green), NearestWebSafe(c.Blue), c.Alpha)

End Function
#tag EndMethod

#tag Method, Flags = &h0
Function WebSafeColor(idx As Integer) As Color

Var r,g,b As Integer

If idx < 0 Then idx = 0
If idx > 215 Then idx = 215

// Find a WebSafe RGB based on its index and an imaginary color distribution
b = idx mod 6 * 51
idx = idx \ 6
g = idx mod 6 * 51
r = idx \ 6 * 51

Return Color.RGB(r, g, b)

End Function
#tag EndMethod

#tag Method, Flags = &h0
Function WebSafeColor(r As Integer, g As Integer, b As Integer, a As Integer = 0) As Color

Return Color.RGB(NearestWebSafe(r),NearestWebSafe(g),NearestWebSafe(b), a)

End Function
#tag EndMethod

#tag Method, Flags = &h0
Function WebSafeColorIndex(c As Color) As Integer

// Calculate a virtual index for a Web Safe color (Palette of 216 colors)
Return NearestWebSafe(c.Red) \ 51 * 36 + NearestWebSafe(c.Green) \ 51 * 6 + NearestWebSafe(c.Blue) \ 51

End Function
#tag EndMethod

#tag ViewBehavior
#tag ViewProperty
Name="Name"
Visible=true
Group="ID"
InitialValue=""
Type="String"
EditorType=""
#tag EndViewProperty
#tag ViewProperty
Name="Index"
Visible=true
Group="ID"
InitialValue="-2147483648"
Type="Integer"
EditorType=""
#tag EndViewProperty
#tag ViewProperty
Name="Super"
Visible=true
Group="ID"
InitialValue=""
Type="String"
EditorType=""
#tag EndViewProperty
#tag ViewProperty
Name="Left"
Visible=true
Group="Position"
InitialValue="0"
Type="Integer"
EditorType=""
#tag EndViewProperty
#tag ViewProperty
Name="Top"
Visible=true
Group="Position"
InitialValue="0"
Type="Integer"
EditorType=""
#tag EndViewProperty
#tag EndViewBehavior
End Module
#tag EndModule

``````

Thank you Rick,
I will try that.
I am not familiar with this kind of representations like for example `#tag Method, Flags = &h0` .
What is their meaning?

No idea of the meaning but Xojo inserts such annotations for their use, probably to supply missing features/infos that could be expressed/inferred entirely using proper readable idiomatic content from the language.

I wrote the module, exported it as text, and pasted above as Xojo wants.

Just copy and paste that code into a text file named websafecolors.xojo_code and save it.
Then create a desktop app and drag such file to its navigator. Say yes/ok to the “import thing”. A module with the proper methods will show up.

What I think I need to represent what I intended idiomatically could be only something like:

``````Public Module WebSafeColors

// Find nearest web safe value for a channel
Private Function NearestWebSafe(oneColorValue As Integer) As Integer

Var near As Integer = oneColorValue \ 51
If oneColorValue mod 51 > 25 Then near = near + 1 // the nearest is the next one above
Return near * 51

End Function

// receive a color, return the nearest web safe of it
Function WebSafeColor(c As Color) As Color

Return Color.RGB(NearestWebSafe(c.Red), NearestWebSafe(c.Green), NearestWebSafe(c.Blue), c.Alpha)

End Function

// Receive an index (0 to 215) and return the equivalent web safe color
Function WebSafeColor(idx As Integer) As Color

Var r,g,b As Integer

If idx < 0 Then idx = 0
If idx > 215 Then idx = 215

// Find a WebSafe RGB based on its index and a virtual web safe color distribution
b = idx mod 6 * 51
idx = idx \ 6
g = idx mod 6 * 51
r = idx \ 6 * 51

Return Color.RGB(r, g, b)

End Function

// compose a web safe color passing each RGB channel value
Function WebSafeColor(r As Integer, g As Integer, b As Integer, a As Integer = 0) As Color

Return Color.RGB(NearestWebSafe(r),NearestWebSafe(g),NearestWebSafe(b), a)

End Function

// Receive a color and return the one byte value (0to 215) equivalent
Function WebSafeColorIndex(c As Color) As Integer

// Calculate a virtual index for a Web Safe color (Palette of 216 colors)
Return NearestWebSafe(c.Red) \ 51 * 36 + NearestWebSafe(c.Green) \ 51 * 6 + NearestWebSafe(c.Blue) \ 51

End Function

End Module

``````

But this way Xojo probably will complain about something.

Excuse me, I was already wrong in the value of the sky blue color (192,255,255) but it is also true for the light yellow.
Rick, unless I’m mistaken if you take the color (242,255,255) you get white, right?

You have an explanation about RGB colors there:

If you want to experiment colors and run macOS, fire TextEdit, then Cmd-C and you will get the Color selector. Then you can play with the values and see the colors.

Same can be done with GIMP.

On Windows, I suppose you can get the “same” Color selector in Paint (the bitmap one, I do not know for Paint3D; must have it too…)

Yep. The nearest color of the WebSafe 216 colors palette to this almost white blue is white.

Émile,
My problem is not to understand the colors, I have all the possibilities that I could discover, RGB, HSV, XYZ, CMYK and the distances between all these colors but my problem is to be able to simplify the colors compared to a palette when the colors are very light.

If you want to map to the specific web safe colors, then you will always lose some light colors to White.
A routine that simplifies the colors this way would represent the pale blue as a dithered area of a darker blue, plus white

If you just want fewer colors , that would be a different problem.
What exactly do you want to achieve?

Jeff,
If you look at my second image with a list of sky blue colors, I just want to simplify those colors to RGB(192, 255,255) and not white as they are closer to white than sky blue.

So you want a (weird to me) personalized palette full of very specific non-linear exceptions causing more distortions than correctness. You need to create your own algorithm with such exceptions and adding the desired color to the palette (192,255,255, that is not Web Safe) and map all those desired exception ranges to such color.
That’s is not generally useful, but things like that are kind of used for image filters, probably such filter will add a blueish bias to the image.

I’m not sure which image you mean.
Are you always working with the same image?

If the source images always have the same set of distinct RGB values, you can easily set up a lookup table using a database or dictionary.

`mylookups.value(RGB(192, 205,241) ) = RGB(192, 255,255)`

and then recover them afterwards.

Again: what do you want to achieve?

Why do I keep asking that?

Consider this:
Someone keeps asking ‘how do I make a winch which is 20 miles in length’
So we get lots of suggestions about tying cables together, tensile strength, etc etc

But if the answer to ‘what do you want to DO?’ is: ‘I want my car to be at the seaside’, then a better answer is ‘turn the engine on and drive there’

In simple terms, what do you want to do?
(i.e not ‘how do you think it should be achieved’)

1 Like