Цвета клеток.

Сегодня я хочу рассмотреть вычисление цветов клеток. Способ несколько отличается от имевшегося в оригиналной программе и предполагает растягивание цветовой шкалы хоть на весть диапазон возможных возрастов от единицы до MaxInt.

В основе всего лежит статья Цветовое пространство HSL. Перевожу:

Цветовое пространство HSL имеет три координаты: цвет (hue), насыщенность (saturation), и яркость ( lightness) (иногда говорят светимость (luminance)) соответсвенно. Иногда его так же называют HLS. Цвет -- это угол от 0 до 360 градусов, обычно 0 это красный, 60 градусов -- жёлтый, 120 градусов -- зелёный, 180 градусов -- голубой, 240 градусов -- синий и 300 градусов -- фиолетовый. Насышенность обычно представлена диапазоном от 0 до 1 (инога от 0 до 100%) и определяет, насколько цвет серый, 0 указывает серый и 1 -- это чистый основной цвет. Яркость это то, что обозначено этим именем, изменение яркости уменьшает значение яркости основных цветов, но оставляет их столь же чистыми.Если представить цветовое пространство HSL в виде дисков различной яркости, то цвет и насыщенность будут эквивалентны полярным координатам (r,theta) любой точки на плоскости. Сами диски можно посмотреть в оригинале статьи.

Идея формирования цветов клеток такова. Имеем диапазон возрастов, для которых надо получить непрерывную цветовую шкалу. Скажем, 1000 поколений. Соответственно, возраст может иметь значения 0..1000 (потом, в принципе, цвета могут начать повторяться). Теперь переведём эту шкалу в масштаб hue (0..360). А чтобы первым цветом был не красный, а например жёлтый, прибавим к промасштабированному значению смещение (60 для жёлтого).

Hue := 60 + Age*360/1000

Возьмём так же S := 1 и L := 0.5

Теперь осталось преобразовать цвет из HSL в RGB.

Вот что у меня получилось.

Сначала разберёмся с конвертацией цветов. Код функций я взял из всё той же статьи и лишь перевёл с 'си' на pascal.

Тестовый класс для DUnit. Единственный тест проверяет соответсвие прямого и обратного преобразования для всех цветов. Не самый лучший тест, надо признать ^_^. Обращаю внимание, что методы класса TRgbHslConvertor вызываются без создания класса.

unit RgbHslConvertorTest;

interface

uses
  TestFrameWork,
  RgbHslConvertor;

type
  TRgbHslConvertorTest = class(TTestCase)
  published
    procedure RGBtoHCL;
  end;

implementation

uses
  SysUtils,
  Windows;

(* TRgbHslConvertorTest *******************************************************)

{ Published }

procedure TRgbHslConvertorTest.RGBtoHCL;
var
  r, g, b: Integer;
begin
  for r := 0 to 255 do
    for g := 0 to 255 do
      for b := 0 to 255 do
        Check(RGB(r, g, b) = TRgbHSLConvertor.ToRGB(TRgbHSLConvertor.ToHSL(RGB(r, g, b))),
          Format('Не совпал цвет в RGB[%d, %d, %d]', [r, g, b]));
end;

initialization
  TestFramework.RegisterTest(TRgbHslConvertorTest.Suite)
end.

Модуль и класс преобразования RGB в HSL и обратно. Обращаю внимание, что оба метода объявлениы как методы класса, поэтому их можно вызывать без создания самого класса. такой подход характерен для языков java или c#, где невозможно написать просто функцию саму по себе, а не метод класса. В Delphi (по правде говоря) можно было бы обойтись и функциями. ^_^

unit RgbHslConvertor;

interface

uses
  Graphics;

type
  TColorHSL = record
    H: Double; // Цвет 0..360
    S: Double; // Насыщенность 0..1
    L: Double; // Яркость 0..1
  end;

  TRgbHslConvertor = class
  public
    class function ToRGB(AColor: TColorHSL): TColor;
    class function ToHSL(AColor: TColor): TColorHSL;
  end;

implementation

// Методика преобразования взята отсюда:
// http://astronomy.swin.edu.au/~pbourke/colour/hsl/

uses
  Windows,
  Math;

type
  // RGB представление, где все составляющие имеют значения (0..1)
  TDoubleColor = record
    R, G, B: Double;
  end;

(* TRgbHslConvertor ***********************************************************)

{ Public }

class function TRgbHslConvertor.ToRGB(AColor: TColorHSL): TColor;
var
  c2, sat, ctmp: TDoubleColor;
begin
  while AColor.H < 0 do
    AColor.H := AColor.H + 360;
  while AColor.H > 360 do
    AColor.H := AColor.H - 360;

  if AColor.H < 120 then
    begin
      sat.r := (120 - AColor.H) / 60.0;
      sat.g := AColor.h / 60.0;
      sat.b := 0;
    end
  else if (AColor.h < 240) then
    begin
      sat.r := 0;
      sat.g := (240 - AColor.h) / 60.0;
      sat.b := (AColor.h - 120) / 60.0;
    end
  else
    begin
      sat.r := (AColor.h - 240) / 60.0;
      sat.g := 0;
      sat.b := (360 - AColor.h) / 60.0;
    end;
  sat.r := MIN(sat.r,1);
  sat.g := MIN(sat.g,1);
  sat.b := MIN(sat.b,1);

  ctmp.r := 2 * AColor.s * sat.r + (1 - AColor.s);
  ctmp.g := 2 * AColor.s * sat.g + (1 - AColor.s);
  ctmp.b := 2 * AColor.s * sat.b + (1 - AColor.s);

  if AColor.l < 0.5 then
    begin
      c2.r := AColor.l * ctmp.r;
      c2.g := AColor.l * ctmp.g;
      c2.b := AColor.l * ctmp.b;
    end
  else
    begin
      c2.r := (1 - AColor.l) * ctmp.r + 2 * AColor.l - 1;
      c2.g := (1 - AColor.l) * ctmp.g + 2 * AColor.l - 1;
      c2.b := (1 - AColor.l) * ctmp.b + 2 * AColor.l - 1;
    end;
  Result := RGB(Round(c2.R*255), Round(c2.G*255), Round(c2.B*255));
end;

class function TRgbHslConvertor.ToHSL(AColor: TColor): TColorHSL;
var
  R, G, B: Double;
  TheMin, TheMax, Delta: Double;
begin
  R := (AColor and $FF)/$FF;
  G := ((AColor shr Преобразование RGB в hls and $FF)/$FF;
  B := ((AColor shr 16) and $FF)/$FF;

  TheMin := Min(R, Min(G, B));
  TheMax := Max(R, Max(G, B));
  Delta := TheMax - TheMin;
  Result.L := (TheMin + TheMax) / 2;
  Result.S := 0;
  if (Result.L > 0) and (Result.L < 1) then
    begin
      if Result.L < 0.5 then
        Result.S := Delta / (2*Result.L)
      else
        Result.S := Delta / (2-2*Result.L);
    end;
  Result.H := 0;
  if Delta > 0 then
    begin
      if (TheMax = R) and (TheMax <> G) then
        Result.H  := Result.H + (G-B)/Delta;
      if (TheMax = G) and (TheMax <> B) then
        Result.H := Result.H + (2 + (B-R)/Delta);
      if (TheMax = B) and (TheMax <> R) then
        Result.H := Result.H + (4 + (R - G)/Delta);
      Result.H := Result.H*60;
   end;
end;

end.




Смотрите также:

No related posts.