Lazarus

Programming => Graphics and Multimedia => Graphics => Topic started by: devEric69 on July 17, 2019, 02:45:54 pm

Title: [Solved] How to obtain the complementary color of a TColor ?
Post by: devEric69 on July 17, 2019, 02:45:54 pm
Hi,

I was using the Jedi's method  JvgUtils.ComplementaryColor(AColor: TColor): TColor; , under Delphi.
Is there an equivalent in FPC or LCL packages?
Title: Re: How to obtain the complementary color of a TColor ?
Post by: marcov on July 17, 2019, 02:51:01 pm
jedi is third party, just port the relevant parts?
Title: Re: How to obtain the complementary color of a TColor ?
Post by: wp on July 17, 2019, 06:14:09 pm
I was search through all the sources in jvcl (https://github.com/project-jedi/jvcl/tree/master/jvcl/run). The current repository does not contain a unit JvgUtils any more; an older one which I have on my HD has it but it is lacking a function "ComplementaryColor". Please be more specific where to find it.

But when my understanding of "complementary color" is the same as theirs then why don't you just simply use
Code: Pascal  [Select]
  1. uses
  2.   Graphics;
  3.  
  4. function ComplementaryColor(AColor: TColor): TColor;
  5. begin
  6.   Result := RGBToColor(255 - Red(AColor), 255- Green(AColor), 255 - Blue(AColor));
  7. end;

A bit more refined version is "InvertColor" in unit Graphics.
Title: Re: How to obtain the complementary color of a TColor ?
Post by: circular on July 17, 2019, 09:59:16 pm
Or this:
Code: Delphi  [Select]
  1. function ComplementaryColor(AColor: TColor): TColor;
  2. begin
  3.   Result := clWhite - ColorToRGB(AColor);
  4. end;
Title: Re: How to obtain the complementary color of a TColor ?
Post by: garlar27 on July 17, 2019, 11:20:24 pm
Or this:
Code: Delphi  [Select]
  1. function ComplementaryColor(AColor: TColor): TColor;
  2. begin
  3.   Result := clWhite - ColorToRGB(AColor);
  4. end;
This is in case you need the complementary of the light color palette. If you need the complementary for the ink color palette then I think you should use CMYK (Cyan Magenta Yellow and Ink) colors, and I don't know well how to use them since I'd never programed using that palette.
Title: Re: How to obtain the complementary color of a TColor ?
Post by: winni on July 18, 2019, 12:22:21 am
RGB and CMYK are linear converted. In theory you need only CMY to paint (print) everything, but 100% of CMY only gives an ugly gray-brown. So for printing you need the K (Kontrast = ink).

For converting RGB to CMYK see the following procedure. Notice that the resulting values of C,M,Y and K are in the range of 0..255 - not 0..100!!

Code: Pascal  [Select]
  1.  
  2. procedure RGBtoCMYK(r,g,b: byte; var c,m,y,k : byte);inline;
  3. var r1,g1,b1,c1,m1,y1,k1 : single;
  4.  
  5.     begin
  6.       r1 := r/255;
  7.       g1 := g/255;
  8.       b1 := b/255;
  9.       k1 := max(r1,g1);
  10.       k1 := max(k1,b1);
  11.       k1 := 1-k1;
  12.       if k1 = 1 then
  13.          begin
  14.           c1 := 0; m1 := 0; y1 := 0;
  15.          end else
  16.          begin
  17.            c1 := (1-r1-k1) / (1-k1);
  18.            m1 := (1-g1-k1) / (1-k1);
  19.            y1 := (1-b1-k1) / (1-k1);
  20.          end;
  21.       c := round(c1*255);
  22.       m := round(m1*255);
  23.       y := round(y1*255);
  24.       k := round(k1*255);
  25.     end;
  26.          
  27.  

Enough info?

Winni
Title: Re: How to obtain the complementary color of a TColor ?
Post by: devEric69 on July 18, 2019, 11:54:12 am
@wp: sorry for forgetting to mention the Jedi version number.
@others: thank you for your answers.

I had the same result (but not as well, in 3..a few lines :) ):
Code: Pascal  [Select]
  1. uses
  2.   Graphics, FPImage;
  3.  
  4. function CompementaryColor2(AColor: TColor): TColor;
  5. var
  6.   aRGB: record
  7.     iRed, iGreen, iBlue: Byte;
  8.   end;
  9.   aFPColor: TFPColor;
  10.   aColorRef: TColorRef;
  11. begin
  12.   aFPColor:= TColorToFPColor(AColor);
  13.   aColorRef:= FPColorToTColorRef(aFPColor);
  14.   //(1) verification of RGB components, from the basic AColor composite
  15.   with aRGB do begin
  16.     iRed:= Graphics.Red(aColorRef);
  17.     iGreen:= Graphics.Green(aColorRef);
  18.     iBlue:= Graphics.Blue(aColorRef);
  19.   end;
  20.   Form1.lblRedBase.Caption:= 'RedBase: ' + IntToStr(aRGB.iRed);
  21.   Form1.lblGreenBase.Caption:= 'GreenBase: ' + IntToStr(aRGB.iGreen);
  22.   Form1.lblBlueBase.Caption:= 'BlueBase: ' + IntToStr(aRGB.iBlue);
  23.   //(2) obtain the complements of the RGB components, from the basic AColor composite
  24.   with aRGB do begin
  25.     iRed:= 255 - Graphics.Red(aColorRef);
  26.     iGreen:= 255 - Graphics.Green(aColorRef);
  27.     iBlue:= 255 - Graphics.Blue(aColorRef);
  28.   end;
  29.   (* Form1.lblRedCompl:= 'RedCompl: ' + ; Form1.lblGreenCompl:= 'GreenCompl: ' + ; Form1.lblBlueCompl:= 'BlueCompl: ' + ; *)
  30.   result:= RGBToColor(aRGB.iRed, aRGB.iGreen, aRGB.iBlue);
  31. end;  
       

But, I have the following comprehension problem; when I code...:
Code: Pascal  [Select]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. var
  3.   aBaseColor: TColor;
  4. begin
  5.   aBaseColor:= TColor($AA6239); //with $00AA6239, I get the same result; //with $AA623900, this color is a light grey, not the brown displayed by websites
  6.   Panel1.Color:= aBaseColor;
  7. end;
... then, I will check on https://www.colorhexa.com/aa6239 (https://www.colorhexa.com/aa6239): I don't see the same color, as the one on the TForm??!
However, when I take a calculator, I get:
Red = $aa = 170.
Green = $62 = 98.
Blue = $39 = 57.

Nevertheless, Free Pascal tells me, that:
Code: Pascal  [Select]
  1. with aRGB do begin
  2.     iRed:= Graphics.Red(aColorRef); // == 57.
  3.     iGreen:= Graphics.Green(aColorRef); // == 98.
  4.     iBlue:= Graphics.Blue(aColorRef); // == 170.
  5.   end;
==> So, iRed and iBlue are reversed (!!?), compared to all tested web sites of colors verification (by entering #aa6239, as in the *.css files).
➔ My question is ( using only simple RGB; no Hue, sensitivity, ..., ink that I don't know yet ): why this inversion :( ( i.e. how to get from $aa6239 in Free Pascal, the same color on a TForm as #aa6239 displayed by the *.css and Javascript )?
Title: Re: How to obtain the complementary color of a TColor ?
Post by: circular on July 18, 2019, 01:27:29 pm
Indeed TColor written in hex is swaped compared to #...

In binary that makes sense because the first bits are the ones on the right of the number.

You need to swap red and blue if you write as if it was #...

Code: Delphi  [Select]
  1. function SwapRedBlue(AColor: TColor): TColor;
  2. begin
  3.   Result := RGBToColor(Blue(AColor), Green(AColor), Red(AColor));
  4. end;
  5.  
  6. ...
  7.  
  8. Panel1.Color := SwapRedBlue($AA6239);
  9.  
Title: Re: How to obtain the complementary color of a TColor ?
Post by: devEric69 on July 18, 2019, 02:05:27 pm
Thank you very much, circular, for your technical clarification, and your function\trick...
...which poses 2 problems for a basic Lazarus user, with the semantic:
- the TColor type could have a synonym named TBGRColor for exemple (but it's falsely clever, right?).
- some graphics.pas functions are bug traps, always because of semantics: they are called function xxxRGByyyy(R, G, B: Byte): TThis;, eg function RGBToColor(R, G, B: Byte): TColor; (the naming \ order of parameters is also a trap).

So - I'm asking the moderators (I have no vision at all of the involvement of these methods in the LCL) - first of all, what do I do?
A bug (or modification?) report with this hyperlinked thread? Nothing?
Title: Re: How to obtain the complementary color of a TColor ?
Post by: wp on July 18, 2019, 02:24:57 pm
This leads to the elemental issue with "little endian" and "big endian" byte order: look at the number 256 - this is $01FF in hex. However, when this value is stored on a file by an Intel processor you'll the the bytes in the order FF 01. When you store to file with a Motorola processor you'll find the bytes in the "correct" order 01 FF.

Some file formats store the rgb bytes in the order r-g-b, others in b-g-r, and when there is a forth alpha channel byte it gets even more compilcated.

Confusing? Yes.

So, please: no error in the graphics unit. It's just a matter of convention - and to listen to the alarm clock in your head when you try to determine the rgb components out of a "color".
Title: Re: How to obtain the complementary color of a TColor ?
Post by: devEric69 on July 18, 2019, 02:43:31 pm
All right: thank you for the explanations \ arguments.
I will think about increasing the volume of the alarm clock in my head, when I'll use a TColor (should I resonate contextually with the TColor , thinking about TRGBColor or TBGRColor ?).

PS: by the way, your complementary color algorithm is correct.
Title: Re: How to obtain the complementary color of a TColor ?
Post by: wp on July 18, 2019, 03:37:21 pm
bby the way, your complementary color algorithm is correct.
In this case you should use circular's routine (reply #3), it is much more efficient and does exactly the same.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: Thaddy on July 18, 2019, 04:38:53 pm
You can probably use a cast a mask and the not operator? Should be faster than subtraction. It does a bit-wise negation.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: devEric69 on July 18, 2019, 06:32:23 pm
Quote
You can probably use a cast a mask and the not operator?

Too complicated for me %) .

The main thing, is that I didn't understand that ( déjà vu ):
To call or not to call SwapRedBlue depends from my material context, and the purpose of this call:
- depending on the CPU processor type (Motorola versus Intel), I will write - in a file - AColor's RGB in that order ie RGB; or inversed ie BGR (cross-compilation problems are not yet fully in my head).
- depending on the logic of use: let's say I found a pretty #aabbcc color on a website, i.e. a hexdecimal representation in @string format ==> it means that Red='#aa', Green='#bb', and Blue='#cc'. If you want to assign this same compiled color, then $aabbcc to a Canvas property, it will not work: indeed, the binary bits are read from right to left (as @circular pointed out). So, the compiled equivalent will have to be: Red=$cc, Green=$bb Blue=$cc (lately, I've been developing with interpreted Javascript code).

PS: another simple manipulation of the bytes, to obtain 3 differents colors - which contrast with each other at most - is the "triadic color scheme": if color_base=aabbcc ==> triad_1=bbccaa, triad_2=ccaabb.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: winni on July 18, 2019, 06:54:02 pm
You have too check first, which convention is used:

* HTML writes the hex code in Motorola format
* The GIMP shows the color hex code in Motorola Format

So if you take a color code from HTML or the  GIMP you have to swap red and blue.

Easy test: Fill something with color = $FF
If it is red you are on an Intel machine.
Otherwise if it is blue you are on a motorala maschine

Winni
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: circular on July 19, 2019, 08:05:14 am
You can probably use a cast a mask and the not operator? Should be faster than subtraction. It does a bit-wise negation.
I was worried by that in the past but in practice it made no difference. Subtracting is not so complicated, for example if you compare that to multiplication.

The main thing, is that I didn't understand that ( déjà vu ):
To call or not to call SwapRedBlue depends from my material context, and the purpose of this call:
- depending on the CPU processor type (Motorola versus Intel), I will write - in a file - AColor's RGB in that order ie RGB; or inversed ie BGR (cross-compilation problems are not yet fully in my head).
- depending on the logic of use: let's say I found a pretty #aabbcc color on a website, i.e. a hexdecimal representation in @string format ==> it means that Red='#aa', Green='#bb', and Blue='#cc'. If you want to assign this same compiled color, then $aabbcc to a Canvas property, it will not work: indeed, the binary bits are read from right to left (as @circular pointed out). So, the compiled equivalent will have to be: Red=$cc, Green=$bb Blue=$cc (lately, I've been developing with interpreted Javascript code).
Yep.

Note that when writing a file, if you write each byte individually, then you can just follow the specification of the file format. Otherwise, well TColor contains 4 bytes, so what we're saying here about swaping red and blue will be incomplete.

Quote
PS: another simple manipulation of the bytes, to obtain 3 differents colors - which contrast with each other at most - is the "triadic color scheme": if color_base=aabbcc ==> triad_1=bbccaa, triad_2=ccaabb.
Indeed.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: Thaddy on July 19, 2019, 09:28:43 am
You can probably use a cast a mask and the not operator? Should be faster than subtraction. It does a bit-wise negation.
I was worried by that in the past but in practice it made no difference. Subtracting is not so complicated, for example if you compare that to multiplication.
Well, not or negation operates on only one register, subtraction on two.... On arm it makes a difference. That was my ratio.
But I never applied this on graphics, only on audio.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: wp on July 19, 2019, 09:35:38 am
PS: another simple manipulation of the bytes, to obtain 3 differents colors - which contrast with each other at most - is the "triadic color scheme": if color_base=aabbcc ==> triad_1=bbccaa, triad_2=ccaabb.
In this algorithm, the original and modified colors will have no or little contrast when aa, bb and cc are equal or almost equal.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: devEric69 on July 19, 2019, 10:14:44 am
Indeed.
But the complementary and trid colors are the only 2 algorithms, to obtain colors by simple manipulations of R, G and B. Afterwards, the algorithms become more complicated (need to use the chromatic circle and to use [radians] angles, shadow degrees i.e. gray percentage, highlighting...).
I saw that there was an impressive library of objects \ methods to manage these concepts, once they were understood (which is not yet my case): https://wiki.lazarus.freepascal.org/mbColorLib (https://wiki.lazarus.freepascal.org/mbColorLib) .

In fact I use the complementary color sparingly (contrast is what tires the eyes the most): it's just to "massively" affect the color of the fonts in opposition to the color of the Canvas of their parent component, with recursive RTTI (I happily tested my Delphi RTTI routines: they work very well with Lazarus).
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: wp on July 19, 2019, 04:29:41 pm
But the complementary and trid colors are the only 2 algorithms, to obtain colors by simple manipulations of R, G and B.
Be careful with absolute statements ("the only 2 algorithms"). I know a third one, the result is less distracting than the multicolored ComplemantaryColor:
Code: Pascal  [Select]
  1. function HighContrastColor(AColor: TColor): TColor;
  2. var
  3.   sum: Integer;
  4. begin
  5.   sum := Red(AColor) + Green(AColor + Blue(AColor);
  6.   if sum < 3*128 then     // AColor is rather "dark" ---> return white
  7.     Result := clWhite
  8.   else                    // AColor is rather "bright" --> return black
  9.     Result := clBlack;
  10. end;

I am attaching a demo showing the high-contrast, complementary color and two byte-swapping functions.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: devEric69 on July 19, 2019, 05:20:06 pm
Yeah: it's true that the "complementary" is rather ineffective in the middle of a grey environment :D .
Thank you for sharing.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: circular on July 19, 2019, 07:13:29 pm
Well, not or negation operates on only one register, subtraction on two.... On arm it makes a difference. That was my ratio.
But I never applied this on graphics, only on audio.
Not sure how you could use only one register here as the not is applied to 3 bytes, not the highest byte.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: lucamar on July 19, 2019, 07:34:37 pm
Not sure how you could use only one register here as the not is applied to 3 bytes, not the highest byte.

Processor registers are four (or eight) bytes long, so no problem, is there? I mean, the highest pixel format is 32 bit: it just fits in a 386 register.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: circular on July 20, 2019, 05:18:35 pm
Processor registers are four (or eight) bytes long, so no problem, is there? I mean, the highest pixel format is 32 bit: it just fits in a 386 register.
Sure but the not instruction cannot be applied on 3 bytes. To invert only part of the value, one could do a xor with a mask, but where does the mask come from? Either a register or an immediate value.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: Thaddy on July 20, 2019, 09:48:05 pm
Sure but the not instruction cannot be applied on 3 bytes. To invert only part of the value, one could do a xor with a mask, but where does the mask come from? Either a register or an immediate value.
Hence the mask on a 32 bit  values, masking out the least significant byte.. You can also not the individual  bytes of course, but that would be slower.
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: circular on July 22, 2019, 12:18:21 pm
Yes
Title: Re: [Solved] How to obtain the complementary color of a TColor ?
Post by: Bazzao on July 24, 2019, 11:41:08 pm
Hi,

I was using the Jedi's method  JvgUtils.ComplementaryColor(AColor: TColor): TColor; , under Delphi.
Is there an equivalent in FPC or LCL packages?

I found this years ago and it works quite well.

Code: Pascal  [Select]
  1. var I,R,G,B:integer;  
  2.   R:=Red(Memo1.Color);
  3.   G:=Green(Memo1.Color);
  4.   B:=Blue(Memo1.Color);
  5.   I:=Round(Sqrt(R*R*0.241+G*G*0.691+B*B*0.068));
  6.   if I>128 then
  7.     Memo1.Font.Color:=clBlack
  8.   else
  9.     Memo1.Font.Color:=clWhite;
  10.  

The result colour is only black or white.

B