Jump to content

Recommended Posts

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

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 by shineworld

Share this post


Link to post
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

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.   

  • Like 1

Share this post


Link to post
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
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;

 

  • Like 1

Share this post


Link to post
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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×