Sorting a colour grid in to order

mickle026

Member
Joined
Oct 31, 2009
Messages
20
Programming Experience
3-5
Can anyone help me, I have a problem that im not sure how to solve. I am writing a program for changing skin tones in images by remapping the colours. I have the colour extraction working fine in order to create a swatch, but my problem is getting the swatch in order of the lightest colour first. This i want to do so that I can create a scale or histogram from it.

Colours in the swatch are written in order of argb from -1. The fromArgb colours are obviously not in order like this as the swatch shows white (or light yellow) further down the grid followed by skin colours again.

If you have any ideas how to do this it would be greatly appreciated. If you have a sample or can point me to one that would be fantastic.

Many thanks
 

Attachments

  • colourextract.JPG
    colourextract.JPG
    61.6 KB · Views: 48
Last edited:
If you have a List(Of Color) you can for example sort it according to HLS color space:
    Public Function HLSComparison(c1 As Color, c2 As Color) As Integer

        Dim h = c1.GetHue.CompareTo(c2.GetHue)
        If h <> 0 Then Return h

        Dim l = c1.GetBrightness.CompareTo(c2.GetBrightness)
        If l <> 0 Then Return l

        Dim s = c1.GetSaturation.CompareTo(c2.GetSaturation)
        If s <> 0 Then Return s

        Return 0
    End Function

colors.Sort(AddressOf HLSComparison)
 
Many thanks for the reply John, i'll take a look at HLS color tonight. I had a feeling it was down the lines of hue and light. I know nothing about how this works so will have to research.. Im still learning about the graphics object to be honest but I think I get how that works now.
Always something new to learn!

Thanks again.
 
I have never used "List of T" so to be honest i am sort of struggling with it. I think "the penny will drop" eventually - so to speak. You see im a self taught hobbiest (probably could have done with learning the basics upwards)

Anyway, I created a class

VB.NET:
Imports System.Drawing

Public Class colors
    Public Colour As Color

    Public Sub New( _
       ByVal m_Colour As Color)
        Colour = m_Colour
    End Sub
End Class


Then in my main code class added
VB.NET:
Dim colorList As New List(Of colors)

And then used
VB.NET:
colorList.Add(New colors(clrTmp))

Which works fine. It does add the same amount of pixels that I want in my sample. My problem arises when I try to sort it by the HLSComparison function

I then added your function
VB.NET:
 Public Function HLSComparison(ByVal c1 As Color, ByVal c2 As Color) As Integer

        Dim h = c1.GetHue.CompareTo(c2.GetHue)
        If h <> 0 Then Return h

        Dim l = c1.GetBrightness.CompareTo(c2.GetBrightness)
        If l <> 0 Then Return l

        Dim s = c1.GetSaturation.CompareTo(c2.GetSaturation)
        If s <> 0 Then Return s

        Return 0
    End Function

And After I have finished adding to the list, used the sort method described earlier.
VB.NET:
 colorList.Sort(AddressOf HLSComparison)

However the IDE underlines it in blue and will not make a new build.

I get this error message:
VB.NET:
Error	1	Overload resolution failed because no accessible 'Sort' can be called with these arguments:
    'Public Sub Sort(comparison As System.Comparison(Of colors))': Method 'Public Function HLSComparison(c1 As System.Drawing.Color, c2 As System.Drawing.Color) As Integer' does not have a signature compatible with delegate 'Delegate Function Comparison(Of colors)(x As colors, y As colors) As Integer'.
    'Public Sub Sort(comparer As System.Collections.Generic.IComparer(Of colors))': 'AddressOf' expression cannot be converted to 'System.Collections.Generic.IComparer(Of Skin_Tone_Sample_Creator.colors)' because 'System.Collections.Generic.IComparer(Of Skin_Tone_Sample_Creator.colors)' is not a delegate type.

I see from this It is because of the "AddressOf" is not/has no a delegator type. This is where I am Lost. I did a bit of research on delegates before I responded here, and WOW . a complex study case!

I think I have to Dimension the function then invoke it, but im not sure how to do this whilst still using it in the way you describe.....

To be honest I think i am misunderstanding something here.

Help ..... ??

Many thanks
 
Last edited:
Class colors
What's the point of that?
My problem arises when I try to sort it by the HLSComparison function
Yes, because it expects all items in list to be Color values, not objects of your type 'colors'.
 
What's the point of that?

Yes, because it expects all items in list to be Color values, not objects of your type 'colors'.

Ok, got rid of the class - this was because I did not fully understand list of T and was following examples on the net.
I replaced with:
VB.NET:
Dim colorList As New List(Of Color)

Added samples to the list with - So I dont add duplicates:
VB.NET:
                With colorList
                    If Not .Contains(clrTmp) Then
                        colorList.Add(clrTmp)
                    End If
                    If .Count < 1 Then
                        colorList.Add(clrTmp)
                    End If
                End With

And sorted with:
VB.NET:
colorList.Sort(AddressOf HLSComparison)

It works!

However, its not exactly in order of the colours I wanted, but many thanks anyway. At least I came out of this learning a whole lot more than I did already. List of T is much faster than what I was doing before. And now I have a little knowledge of hue , saturation and light - I need to do more reading on this but thanks again! - its all appreciated.
 

Attachments

  • colorswatch.JPG
    colorswatch.JPG
    52.4 KB · Views: 40
Added samples to the list with - So I dont add duplicates:
To speed things up a lot you can fill a List(Of Integer) and check Contains, after it is filled you can convert it to List(Of Color) like this:
Dim colors = ints.ConvertAll(Function(c) Color.FromArgb(c))
 
If you have a List(Of Color) you can for example sort it according to HLS color space:
    Public Function HSLComparison(c1 As Color, c2 As Color) As Integer

        Dim h = c1.GetHue.CompareTo(c2.GetHue)
        If h <> 0 Then Return h

        Dim l = c1.GetBrightness.CompareTo(c2.GetBrightness)
        If l <> 0 Then Return l

        Dim s = c1.GetSaturation.CompareTo(c2.GetSaturation)
        If s <> 0 Then Return s

        Return 0
    End Function

colors.Sort(AddressOf HLSComparison)

The above comparison function is wrong. You can only sort on one of the HSL variables at a time. To sort by Lightness, the function should be:

    Public Function HLSComparison(c1 As Color, c2 As Color) As Integer

          Return c1.GetBrightness.CompareTo(c2.GetBrightness)
        
    End Function


EDIT: For getting rid of duplicates in the list of colours, you can use the extension method List.Distinct (VB2008+), e.g.
VB.NET:
Dim distinctColorList = allColorsList.Distinct
I have doubts whether converting the List(Of Color) to List(Of Integer) and back again will offer any significant performance advantage, but it might be worth testing.
 
Last edited:
VicJ said:
The above comparison function is wrong.
No, it is not.
You can only sort on one of the HSL variables at a time.
Why? You may have many different colors that have the same Hue value, you still want to sort them, and the next natural step is to sort these by Brightness, then if that value is equal too, sort by Saturation. If at that stage saturation is equal too, you have two identical colors.
or getting rid of duplicates in the list of colours, you can use the extension method List.Distinct
Using Distinct may be faster than checking Contains for each color. Even so, using the Integer values at this stage should be a lot faster than the compound Color values.
Return c1.GetBrightness.CompareTo(c2.GetBrightness)
That would return the colors all jumbled up, Hue is how most people primarily group colors. You would normally not sort colors by a single property only though, it would be like sorting RGB colors only by their G component.
 
No, it is not.

Why? You may have many different colors that have the same Hue value, you still want to sort them, and the next natural step is to sort these by Brightness, then if that value is equal too, sort by Saturation. If at that stage saturation is equal too, you have two identical colors.

Using Distinct may be faster than checking Contains for each color. Even so, using the Integer values at this stage should be a lot faster than the compound Color values.

That would return the colors all jumbled up, Hue is how most people primarily group colors. You would normally not sort colors by a single property only though, it would be like sorting RGB colors only by their G component.
It's true that sorting by Lightness/Brightness only isn't going to provide the answer mickle026 wants, but otherwise the problem is much more difficult to solve.

The subtle tints in post #1 are a good illustration of why comparing all three variables one by one won't work. Suppose c1 is RGB[255, 100, 80] and c2 is RGB[255, 100, 81]. These colours are too close to distinguish by eye but their hue values are different: 6.857143 and 6.551725 respectively. Small hue variations like this will apply to most pixels in a smooth colour gradient. In your method, only those colours whose hue values are exactly equal will be compared by lightness. Therefore your method will return a sort primarily based on hue, even though the different hues are virtually indistinguishable. And that is exactly the result mickle026 is getting.

Suppose we decide to sort the hues first into larger ranges. Color.GetHue returns a Single value representing an angle in degrees around a circle, so for example we could choose hue ranges 0-60, 60-120, 120-180, 180-240 and 240-270 and 270-360. That would still be unlikely to give a satisfactory result because almost identical hues could easily straddle a boundary between ranges. For example hue=59.999999 would end up in a different range from hue=60.000000 even though they are indistinguishable. The only feasible approach, in my view, would be to perform a statistical analysis of the colours in a given image to detect whether there is any significant banding of the hues, and if so use those ranges for a primary sort. Ideally that analysis should take into account the human eye's sensitivity to different colours. If we can't find an existing example, it would be an interesting project to code something like that.

Vic
 
Last edited:
What you're talking about there is the form of distinct extraction, not how colors should be sorted once you have the relevant selection.
And when you have gathered the colors you wish to present, why would you sort them by only brightness?
 
What you're talking about there is the form of distinct extraction, not how colors should be sorted once you have the relevant selection.
And when you have gathered the colors you wish to present, why would you sort them by only brightness?

The reason I suggested sorting by brightness was because it said in post #1:
I have the colour extraction working fine in order to create a swatch, but my problem is getting the swatch in order of the lightest colour first.
and because sorting by one HLS variable at a time is easy to implement. I took that too literally. He has managed to sort the colours of the pixels into some kind of order, but apparently not in a way that usefully groups them into distinct colours. I think that can only be done properly by first analysing the image to see if the colours are clustered in some significant way. In fact, the colours might be clustered by lightness in completely different way compared to clustering by hue or saturation.

An easier solution would be to let the user to select H, L or S sorting e.g. using RadioButtons. Alternatively, you could use three Panels or PictureBoxes, each one showing a different swatch: all the pixel colours sorted by H, L and S respectively. Maybe just H and L would be good enough.

Vic
 
Last edited:
VicJ said:
The reason I suggested sorting by brightness was because it said in post #1:
Ah, I see, I didn't initially read/understand that colors were already partitioned in groups by hue.
 
Back
Top