How to manipulate colors to give a real-time glowing effect?
Asked Answered
H

2

6

I'm trying to make a button which glows when the mouse is pointed over it. There has to be a timer in the background to control a smooth fading in-and-out of these colors. It doesn't jump from one color to the next, it gradually fades to the next color.

Now my problem is that when it fades back to its regular color, it doesn't lock into its final color. In fact, it keeps jumping from light to dark.

This is a custom button of mine, which I'm not going to post the code of, but this code can be placed onto anything with a mouse enter/exit event and a color property.

It works by setting a variable FMenuDestColor. ExtractColor gets RGB values based on a color. The timer compares each RGB channel between the current color and the destination color. Then the timer modifies the current color to fade to the next. These calculations are done in a function called CalcColorFade - run 3 times for each channel.

procedure ExtractColor(const Color: TColor; var R, G, B: Byte);
begin
  R:= GetRValue(Color);
  G:= GetGValue(Color);
  B:= GetBValue(Color);
end;

function CalcColorFade(const C1, C2: Byte): Byte;
const
  RGB_MULT = 1.2;
  RGB_SENS = 5;
begin
  if C1 <> C2 then begin    
    if (C1 >= C2 - RGB_SENS) and (C1 <= C2 + RGB_SENS) then
      Result:= C2
    else
      if C1 > C2 then
        Result:= EnsureRange(Trunc(C1 / RGB_MULT), 0, 255)
      else
        Result:= EnsureRange(Trunc(C1 * RGB_MULT), 0, 255);
  end else begin
    Result:= C2;
  end;
end;

procedure TfrmMain.tmrMenuGlowTimer(Sender: TObject);
var
  R1, G1, B1: Byte;
  R2, G2, B2: Byte;
  R3, G3, B3: Byte;
begin
  if MenuButton.Color <> FMenuDestColor then begin
    ExtractColor(MenuButton.Color, R1, G1, B1);
    ExtractColor(FMenuDestColor, R2, G2, B2);
    R3:= CalcColorFade(R1, R2);
    G3:= CalcColorFade(G1, G2);
    B3:= CalcColorFade(B1, B2);
    MenuButton.Color:= RGB(R3, G3, B3);
  end;
end;

procedure TfrmMain.MenuButtonMouseEnter(Sender: TObject);
begin
  FMenuDestColor:= clBlue;
end;

procedure TfrmMain.MenuButtonMouseLeave(Sender: TObject);
begin
  FMenuDestColor:= clNavy;
end;

Point the mouse over it, and it will fade to the next color. But take the mouse off of it, and it kinda doesn't lock into position at its original color - it shakes back and forth between light and dark.

I'm assuming there must be a cleaner approach to accomplish what I'm doing, and I'm open to those suggestions too.

The timer's interval is at 70 and the constant of 1.2, when changed to 1.1, it works just fine. So something about changing it to 1.2 that messed this up.

Hydra answered 29/12, 2011 at 0:37 Comment(10)
I think you'd find your code easier to read if you take the identical code handling R, G, and B and factor that into a procedure on its own and call it three times: passing in R1 and R2 on the first run, G1 and G2 on the second run, etc.Riha
@Riha Good tip which I would have wound up doing anyway, although unrelated to the question.Hydra
True enough, which is why I left it as a comment -- I can't spot the problem you're actually trying to solve. Just giving the hint that it'd be easier to fix it only once rather than three times if it is in those blocks... :)Riha
@Riha I took your advice and did that - updated code in question, much cleaner :DHydra
In fact, I'm pondering the idea of running each R/G/B channel in its own thread like that :P But it would have to be something on a much larger scale to need 3 threads.Hydra
Excellent; the newer code does feel cleaner, and I'm glad you spotted it. Don't forget to mark your answer "accepted" just as soon as the software will let you. :)Riha
I find it ironically hilarious how people are up-voting an answer which does not direct my question at all, and yet my answer is obviously answering my question but is ignored. Go figure.Hydra
I'm going to guess it is because your answer doesn't really address the why your change fixes the problem -- but gotafex's answer suggests another approach that many people believe lead to "more pleasing" color shifting. (At least, when I gave an answer with pseudo-code for something similar with RGB values, another answer that came along with HSV suggested got much better feedback...)Riha
@Riha But I'm not about to re-write almost 100 lines of code which already works perfectly to something I know nothing about.Hydra
Added more info about why my fix worked to my answer below. Also tweaked the code and put some comments about it.Hydra
H
1

Problem was solved by adjusting the other constant RGB_SENS to 7.

So it seems the higher I go with RGB_MULT, the higher I have to take RGB_SENS.

What happens is it first gets the current color of the control, then gets the destination color, splits each of those colors into RGB channels, then for each of those channels, it does this function to get the "new" RGB value...

function DoCalc(const Curr, Dest: Byte): Byte;
const
  RGB_MULT = 1.2; //How much color change per step
  RGB_SENS = 10;  //Distance from dest +/- until "locked" into dest
begin
  if Curr <> Dest then begin
    if (Curr >= Dest - RGB_SENS) and (Curr <= Dest + RGB_SENS) then begin
      Result:= Dest; //Lock color into destination (sensitivity)
    end else begin
      if Curr > Dest then begin //Needs to go down
        Result:= EnsureRange(Trunc(Curr / RGB_MULT), 0, 255);
      end else begin            //Needs to go up
        Result:= EnsureRange(Trunc(Curr * RGB_MULT), 0, 255);
      end;
    end;
  end else begin
    Result:= Dest; //Just return the dest color, no change
  end;
end;

Before, RGB_MULT used to be set at 1.1, which worked fine. But when I increased it to 1.2, that's when my problem started. At that time, RGB_SENS was set to 5. RGB_SENS determines how far from the Destination color until the color should "Lock" into place. Evidently, 1.2 caused it to never land in this range. After expanding RGB_SENS to 7 (and now 10), it works fine.

Hydra answered 29/12, 2011 at 1:24 Comment(2)
Funny thing is I wrote the math for this thing, yet can't explain in words how it is actually working :P I tend to read/speak in code language, I can't translate code to plain English that well...Hydra
That's totally common. Don't try to force it into language,sometimes it can mess with the beauty ofthe logic. :PWork
W
4

Why not just switch to HSB/HSV mode?

Then you can set your Hue (0-359), and control saturation, brightness and alpha.

Each of the latter three give you options for a rolling glow effect.

Work answered 29/12, 2011 at 1:33 Comment(5)
Cool, but does it include blending from one color to another, for example, blue to green? My objective is to go from one color to another color, not just a little lighter or darker.Hydra
If you roll through hue, the colors would change but keep the relative brightness and saturation intact.Riha
Understood, but how would it work to go from any one color to any other color? Red to green, blue to purple, cyan to black, white to sky blue, any specified 2 colors, I don't see how that would work.Hydra
If the colors are adjacent in frequency you can just roll across the values. If you want to do a non-adjacent gradient, you'll have to think a little more mathematically. I think something like, find the hue range of color A, and find the hue range of color B, then make like in the old movies and blend two overlayed rectangles between them using alpha (andpossibly sat/bright) as well. You can also jiggle within the hue ranges. tada!Work
@gotafex No doubt it should work, but it sounds a little more complicated (at least for me) because mainly - I've never worked with these attributes. I try not to "Fix" what isn't "Broken", meaning it works, and I'm comfortable with it, so I'm not going to change it to something I'm not so comfortable with, unless I see proof that is in some sort better than what I have now (and quite frankly I'd like to see some samples as you suggest) :DHydra
H
1

Problem was solved by adjusting the other constant RGB_SENS to 7.

So it seems the higher I go with RGB_MULT, the higher I have to take RGB_SENS.

What happens is it first gets the current color of the control, then gets the destination color, splits each of those colors into RGB channels, then for each of those channels, it does this function to get the "new" RGB value...

function DoCalc(const Curr, Dest: Byte): Byte;
const
  RGB_MULT = 1.2; //How much color change per step
  RGB_SENS = 10;  //Distance from dest +/- until "locked" into dest
begin
  if Curr <> Dest then begin
    if (Curr >= Dest - RGB_SENS) and (Curr <= Dest + RGB_SENS) then begin
      Result:= Dest; //Lock color into destination (sensitivity)
    end else begin
      if Curr > Dest then begin //Needs to go down
        Result:= EnsureRange(Trunc(Curr / RGB_MULT), 0, 255);
      end else begin            //Needs to go up
        Result:= EnsureRange(Trunc(Curr * RGB_MULT), 0, 255);
      end;
    end;
  end else begin
    Result:= Dest; //Just return the dest color, no change
  end;
end;

Before, RGB_MULT used to be set at 1.1, which worked fine. But when I increased it to 1.2, that's when my problem started. At that time, RGB_SENS was set to 5. RGB_SENS determines how far from the Destination color until the color should "Lock" into place. Evidently, 1.2 caused it to never land in this range. After expanding RGB_SENS to 7 (and now 10), it works fine.

Hydra answered 29/12, 2011 at 1:24 Comment(2)
Funny thing is I wrote the math for this thing, yet can't explain in words how it is actually working :P I tend to read/speak in code language, I can't translate code to plain English that well...Hydra
That's totally common. Don't try to force it into language,sometimes it can mess with the beauty ofthe logic. :PWork

© 2022 - 2024 — McMap. All rights reserved.