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.
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.
Extrait

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)

image

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

image

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

Google about it. Maybe you find conversion algorithms.

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)
		        WebSafeIndexToColorTable.Add aColor
		        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. :wink:

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.

image

image

É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