Fastest way to calculate colors for a gradient?
Asked Answered
S

2

9

I'm making a small collection of types/functions related to gradients for future use. I would like to make sure there's at least two procedures: ColorBetween and ColorsBetween. I may want to just get an array of TColor between any 2 colors (ColorsBetween), and I may also just need to know one color value at a percentage between two colors (ColorBetween).

I already have it mostly done below. Except, I have two core questions:

  1. How do I calculate the in-between color of each RGB channel by a given percentage? (See below where I have [???])
  2. What's the fastest method to accomplish what I'm doing (while keeping the two distinct functions)?

Here's the Code:

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  StrUtils, StdCtrls, Math;

type
  TColorArray = array of TColor;

implementation

function ColorsBetween(const ColorA, ColorB: TColor; const Count: Integer): TColorArray;
var
  X: Integer; //Loop counter
begin
  SetLength(Result, Count);
  for X:= 0 to Count - 1 do 
    Result[X]:= ColorBetween(ColorA, ColorB, Round((X / Count) * 100)); //Correct?
end;

function ColorBetween(const ColorA, ColorB: TColor; const Percent: Single): TColor;
var
  R1, G1, B1: Byte;
  R2, G2, B2: Byte;
begin
  R1:= GetRValue(ColorA);
  G1:= GetGValue(ColorA);
  B1:= GetBValue(ColorA);
  R2:= GetRValue(ColorB);
  G2:= GetGValue(ColorB);
  B2:= GetBValue(ColorB);
  Result:= RGB(
    EnsureRange(([???]), 0, 255),
    EnsureRange(([???]), 0, 255),
    EnsureRange(([???]), 0, 255)
  );
end;

EDIT: Changed Percent: Integer to Percent: Single to get a smoother effect - not restricted to 100 possible values.

Stephenstephenie answered 24/11, 2011 at 6:30 Comment(1)
How can Byte be outside 0..255 subrange?Shingly
L
7

It sounds like you want to replace your ??? with

Round((R1*Percent + R2*(100-Percent))/100.0)

The EnsureRange in your code is not necessary because this function must return values in the range 0 to 255 provided that Percent is in the range 0 to 100. I think I would apply the EnsureRange to Percent (force it into range 0.0 to 100.0) and then use the following code:

Result := RGB(
  Round((R1*Percent + R2*(100-Percent))/100.0),
  Round((G1*Percent + G2*(100-Percent))/100.0),
  Round((B1*Percent + B2*(100-Percent))/100.0),
);

Your first function returns an array whose first color is ColorA. Maybe you would be better with this:

for X:= 0 to Count - 1 do
  Result[X]:= ColorBetween(ColorA, ColorB, (X+1) / (Count+1) * 100.0);

This gives the same behaviour at both ends of the array. Or perhaps you want both ColorA and ColorB included. Then you would use:

X / (Count-1) * 100.0

But if you do this remember that Count must be greater than 1 otherwise you will be dividing by zero. That nevers works out!!


Don't worry about the performance. The code could be made slightly faster no doubt but it will certainly not be a bottleneck. You will take these colors and draw with them. That's going to consume vastly more resources than these simple routines.


One final point. Interpolation in RGB space will not look particularly smooth or linear to the human eye. Using floating point percentage cannot evade that fact. For best results when viewing you would need to interpolate in a different color space.

Lyophilize answered 24/11, 2011 at 7:21 Comment(10)
Thanks, now suppose I want to convert the Percent: Integer to a Percent: Single instead to get a smoother effect - I'll update the question to include that.Stephenstephenie
Well, that's a rather different question. I've updated my answer now to take out all the integer arithmetic.Lyophilize
and actually I still would like to use EnsureRange because if for whatever reason a user passes a percentage not between 0..100 it will keep it from getting distorted. I've seen way out of the ordinary colors come out of bad color calculations without EnsureRange. Also it should make the colors brighter/darker than the 2 colors' range.Stephenstephenie
I had quite a few edits after you changed your Q. I think I've ironed out all the little syntax problems. In any case you can see the gist of what I say. Make sure you work from the latest version of my answer.Lyophilize
For the ensure range apply it to the Percent.Lyophilize
Multiply by (1/100) rather than divide by 100 to get better performance (yes, division is still quite slower than multiplication)Hang
@Eric that's true. On the other hand I find the code less readable. I believe this routine will not be a hot spot so why trade readability away for no practical gain?Lyophilize
The idea of converting the Percent to a Single rather than an Integer is in case there's a gradient with over a count of 100. For example, drawing a gradient the width of the screen (could be over 1,000 pixels) - if I kept it an integer, there would be very clear jumps from one color to the next (because there's only 100 colors from A to B).Stephenstephenie
@Jerry But in 8 bit color, there's only 256 intensities for each color, and you only get that many gradations if you start with 0 and 255. If your colors are quite close, then integer percent will be fine. And why percent anyway, you can always use per-thousand instead. Nothing special about 100.Lyophilize
@David The question explicitly asks for the fastest way.Hang
L
4

I don't know if this is the fastest way, but it works:

function ColorBetween(const ColorA, ColorB: TColor; const Percent: Integer): TColor;
var
  R1, G1, B1: Byte;
  R2, G2, B2: Byte;
begin
  R1:= GetRValue(ColorA);
  G1:= GetGValue(ColorA);
  B1:= GetBValue(ColorA);
  R2:= GetRValue(ColorB);
  G2:= GetGValue(ColorB);
  B2:= GetBValue(ColorB);

  Result:= RGB(
    Percent * (R2-R1) div 100 + R1,
    Percent * (G2-G1) div 100 + G1,
    Percent * (B2-B1) div 100 + B1
  );
end;

function ColorsBetween(const ColorA, ColorB: TColor; const Count: Integer): TColorArray;
var
  X : integer;
begin
  SetLength(Result, Count);
  for X := 0 to Count - 1 do
    Result[X] := ColorBetween(ColorA, ColorB, Round((X / (Count-1)) * 100));  //Note: Divide by count-1
end;
Literatim answered 24/11, 2011 at 7:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.