Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fastest way to calculate colors for a gradient?

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.

like image 563
Jerry Dodge Avatar asked Nov 24 '11 06:11

Jerry Dodge


2 Answers

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.

like image 137
David Heffernan Avatar answered Oct 01 '22 07:10

David Heffernan


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;
like image 29
Svein Bringsli Avatar answered Oct 01 '22 09:10

Svein Bringsli