FPiette 360 Posted January 7, 2021 I'm writing an application which read data from an external one. An XML file is used between. The external application has color defined by names while in my application, I have ARGB values. It is trivial to convert from color name to ARGB value: function ConvertGpxColor(const S : String) : UInt32; begin // AABBGGRR if SameText(S, 'Red') then Result := $FF0000FF else if SameText(S, 'Green') then Result := $FF00FF00 else if SameText(S, 'Yellow') then Result := $FF00F0F0 else if SameText(S, 'Blue') then Result := $FFFF4040 else if SameText(S, 'Magenta') then Result := $FFFE01FE else if SameText(S, 'Cyan') then Result := $FFFFFF00 else if SameText(S, 'White') then Result := $FFFFFFFF else if SameText(S, 'Black') then Result := $FF000000 else if SameText(S, 'DarkRed') then Result := $FF000060 else if SameText(S, 'DarkGreen') then Result := $FF006000 else if SameText(S, 'DarkYellow') then Result := $FF008080 else if SameText(S, 'DarkBlue') then Result := $FF600000 else if SameText(S, 'DarkMagenta') then Result := $FF600060 else if SameText(S, 'DarkCyan') then Result := $FF808000 else if SameText(S, 'LightGray') then Result := $FFC0C0C0 else if SameText(S, 'DarkGray') then Result := $FF606060 else if SameText(S, 'Transparent') then Result := $00000000 else Result := $FFC0C0C0; // Light Gray end; In my application, the user can select any ARGB colors using a color dialog. Now I must write the XML file for the external application and convert my ARGB colors to the name. For original values, there is no problem finding an exact match. But for other colors, undefined in the external application, I want to find the nearest color name. The alpha channel can be ignored. I tough about computing a distance between colors and pick the color name having the smallest distance. The distance could be computed considering the RGB channels as axis of a space volume where a distance in a 3-dimensional volume has a well known formula. But will this give a visually correct result ? Maybe a formula specific to color has already been designed? Share this post Link to post
Der schöne Günther 303 Posted January 7, 2021 (edited) I'm not sure if the RGB space the right choice for an approach to subjective perception which colour is the closest. Have you considered HSL/HSV? HSL and HSV - Wikipedia Edited January 7, 2021 by Der schöne Günther 1 Share this post Link to post
shineworld 64 Posted January 7, 2021 (edited) If you can define a palette of accepted colors you can check the color closer to one of the palette. In past, I've made a program with fixed palette colors and I need to import PNG or other formats and convert them to a palletized bmp. Attached a very old pas file, used in a Borland Builder C++ 3.0, that I've used to reduce any color to a palletized color. Function RecuceColors. Is very old however... my caller: Graphics::TBitmap* __fastcall QImageManager::CreateBitmapPaletteFromBitmap24BPP(Graphics::TBitmap* Bitmap) { if (FPalette != NULL && FPalette->ColorCount != 0) { // creates paletted bitmap Graphics::TBitmap* BitmapPalette = ReduceColors ( Bitmap, rmPalette, dmNearest, FPalette->ColorBits, FPalette->HPalette ); return BitmapPalette; } else return NULL; } ColorFunctions.pas Edited January 7, 2021 by shineworld Share this post Link to post
FPiette 360 Posted January 7, 2021 23 minutes ago, shineworld said: If you can define a palette of accepted colors you can check the color closer to one of the palette. That's what I explained in my question. What I'm looking for is an algorithm to compute the color closer to a list of colors. 24 minutes ago, shineworld said: ColorFunctions.pas Thanks for your code. This is not exactly what I'm looking for. I don't play with bitmap at all and no dithering is obviously needed. In your code, TFastColorLookup.Lookup actually implement the distance as I said in my question in the RGB space. Share this post Link to post
timfrost 73 Posted January 7, 2021 For this sort of question I always used to go to www.efg2.com to find the answer, usually with success. It had code samples for all sorts of graphics and color algorithms. The site has gone now, but if you go to that URL, it has links to the Internet Archive where it can all still be found. A bit slower to navigate now, but perhaps worth a look. 1 Share this post Link to post
mvanrijnen 121 Posted January 7, 2021 (edited) Use HLS/HSL colorspace for distance ? Edited January 7, 2021 by mvanrijnen Share this post Link to post
FPiette 360 Posted January 7, 2021 11 minutes ago, timfrost said: it has links to the Internet Archive where it can all still be found. And this site experience a 404 error. Will retry later. Thanks. Share this post Link to post
FPiette 360 Posted January 7, 2021 (edited) Found this: https://en.wikipedia.org/wiki/Color_difference And there is a CIEDE2000 distance computation C++ implementation on GitHub: https://github.com/gfiumara/CIEDE2000 I will port this code to Delphi. Thanks everybody. Edited January 7, 2021 by FPiette 1 Share this post Link to post
Anders Melander 1592 Posted January 7, 2021 41 minutes ago, FPiette said: And there is a CIEDE2000 distance computation C++ implementation on GitHub: https://github.com/gfiumara/CIEDE2000 I will port this code to Delphi. Please post it here (or in a Git repository somewhere) when you're done. It's probably too computationally expensive for the stuff I use color distance functions for, but I'd like to give it a go regardless. Here are the ones I use for stuff like flood fill and magic wand selection with tolarance, dithering etc. The ColorDistanceRGBA function gives a fast approximation of "visual" difference and is based on this: https://www.compuphase.com/cmetric.htm interface //------------------------------------------------------------------------------ // // Color distance functions // //------------------------------------------------------------------------------ function ColorDistanceSimpleRGB(Old, New: TColor32): Integer; function ColorDistanceRGBA(Old, New: TColor32): Integer; // Note: Not linear. Visual difference. function ColorDistanceRGBALinear(Old, New: TColor32): Integer; function ColorDistanceRGBLinear(Old, New: TColor32): Integer; function ColorDistanceAverageRGB(Old, New: TColor32): Integer; // The HSV values of the first (Old) parameter is cached so when comparing // multiple values you should attempt to keep the first parameter constant. function ColorDistanceHue(Old, New: TColor32): Integer; function ColorDistanceSaturation(Old, New: TColor32): Integer; function ColorDistanceBrightness(Old, New: TColor32): Integer; implementation //------------------------------------------------------------------------------ // // Color distance functions // //------------------------------------------------------------------------------ function ColorDistanceSimpleRGB(Old, New: TColor32): Integer; begin if (Old <> New) then Result := 255 else Result := 0; end; function ColorDistanceRGBA(Old, New: TColor32): Integer; begin if (Old = New) then Exit(0); if (Old and $FF000000 = 0) xor (New and $FF000000 = 0) then Exit(255); var AlphaOld := AlphaComponent(Old); var AlphaNew := AlphaComponent(New); if (AlphaOld <> AlphaNew) then begin if (AlphaOld <> 255) then Old := ColorScale(Old, AlphaOld); if (AlphaNew <> 255) then New := ColorScale(New, AlphaNew); EMMS; end; // See "Colour metric" by Thiadmer Riemersma // http://www.compuphase.com/cmetric.htm // Modified to consider alpha. Adjusted to maintain 0..255 range var Mean := (RedComponent(Old) + RedComponent(New)) div 2; var dA := AlphaNew - AlphaOld; var dR := RedComponent(New) - RedComponent(Old); var dG := GreenComponent(New) - GreenComponent(Old); var dB := BlueComponent(New) - BlueComponent(Old); Result := Round(Sqrt(( dA*dA + (((512+Mean)*dR*dR) shr 8) + ((dG*dG) shl 2) + (((767-Mean)*dB*dB) shr 8)) / 4)); end; function ColorDistanceRGBALinear(Old, New: TColor32): Integer; begin if (Old = New) then Exit(0); if (Old and $FF000000 = 0) xor (New and $FF000000 = 0) then Exit(255); var AlphaOld := AlphaComponent(Old); var AlphaNew := AlphaComponent(New); if (AlphaOld <> AlphaNew) then begin if (AlphaOld <> 255) then Old := ColorScale(Old, AlphaOld); if (AlphaNew <> 255) then New := ColorScale(New, AlphaNew); EMMS; end; var dA := AlphaNew - AlphaOld; var dR := RedComponent(New) - RedComponent(Old); var dG := GreenComponent(New) - GreenComponent(Old); var dB := BlueComponent(New) - BlueComponent(Old); Result := Round(Sqrt(( dA*dA + dR*dR + dG*dG + dB*dB) / 4 )); end; function ColorDistanceRGBLinear(Old, New: TColor32): Integer; begin if (Old = New) then Result := 0 else // All transparent colors are considered equal regardless of RGB if (Old and $FF000000 <> $FF000000) and (New and $FF000000 <> $FF000000) then Result := 0 else // Difference in transparency = max difference if (Old and $FF000000 = 0) xor (New and $FF000000 = 0) then Result := 255 else begin var dR := RedComponent(New) - RedComponent(Old); var dG := GreenComponent(New) - GreenComponent(Old); var dB := BlueComponent(New) - BlueComponent(Old); Result := Round(Sqrt(( dR*dR + dG*dG + dB*dB) / 3 )); end; end; function ColorDistanceAverageRGB(Old, New: TColor32): Integer; asm AND EAX,$00FFFFFF AND EDX,$00FFFFFF MOVD MM0,EAX MOVD MM1,EDX PSADBW MM0,MM1 MOVD EAX,MM0 IMUL EAX,$555555 SHR EAX,24 //Result := ((C shr 16 and $FF) + (C shr 8 and $FF) + (C and $FF)) div 3; end; var HSLCacheColor: TColor32 = 0; HSLCacheH: Byte = 0; HSLCacheS: Byte = 0; HSLCacheL: Byte = 0; function ColorDistanceHue(Old, New: TColor32): Integer; var Z, A, B: Byte; begin if (Old = New) then Exit(0); if (Old and $FF000000 = 0) xor (New and $FF000000 = 0) then Exit(255); if (Old and $FF000000 <> New and $FF000000) then begin if (Old and $FF000000 <> $FF000000) then Old := ColorScale(Old, AlphaComponent(Old)); if (New and $FF000000 <> $FF000000) then New := ColorScale(New, AlphaComponent(New)); EMMS; end; if (Old <> HSLCacheColor) then begin HSLCacheColor := Old; RGBtoHSL(Old, HSLCacheH, HSLCacheS, HSLCacheL); end; A := HSLCacheH; RGBtoHSL(New, B, Z, Z); Result := Abs(A - B); end; function ColorDistanceSaturation(Old, New: TColor32): Integer; var Z, A, B: Byte; begin if (Old = New) then Exit(0); if (Old and $FF000000 = 0) xor (New and $FF000000 = 0) then Exit(255); if (Old and $FF000000 <> New and $FF000000) then begin if (Old and $FF000000 <> $FF000000) then Old := ColorScale(Old, AlphaComponent(Old)); if (New and $FF000000 <> $FF000000) then New := ColorScale(New, AlphaComponent(New)); EMMS; end; if (Old <> HSLCacheColor) then begin HSLCacheColor := Old; RGBtoHSL(Old, HSLCacheH, HSLCacheS, HSLCacheL); end; A := HSLCacheS; RGBtoHSL(New, Z, B, Z); Result := Abs(A - B); end; function ColorDistanceBrightness(Old, New: TColor32): Integer; var Z, A, B: Byte; begin if (Old = New) then Exit(0); if (Old and $FF000000 = 0) xor (New and $FF000000 = 0) then Exit(255); if (Old and $FF000000 <> New and $FF000000) then begin if (Old and $FF000000 <> $FF000000) then Old := ColorScale(Old, AlphaComponent(Old)); if (New and $FF000000 <> $FF000000) then New := ColorScale(New, AlphaComponent(New)); EMMS; end; if (Old <> HSLCacheColor) then begin HSLCacheColor := Old; RGBtoHSL(Old, HSLCacheH, HSLCacheS, HSLCacheL); end; A := HSLCacheL; RGBtoHSL(New, Z, Z, B); Result := Abs(A - B); end; 1 Share this post Link to post
FPiette 360 Posted January 8, 2021 14 hours ago, Anders Melander said: Please post it here (or in a Git repository somewhere) when you're done. This will be part of an OpenSource application handling OpenStreetMap maps (And other similar map sources). The color distance is needed to import/export GPX files which contain tracks recorded by GPS device. Quote Here are the ones I use for stuff like flood fill and magic wand selection with tolarance, dithering etc. Thanks a lot for sharing. Share this post Link to post