Манипулирование цветами в .NET – часть первая

ОГЛАВЛЕНИЕ

Данная статья является первой из серии трех статей, в которой предоставляется введение в пространства цветов и в наиболее распространенные из них.

• Скачать демонстрационный проект (.NET 1.1) - 58.8 Кб
• Скачать исходники C# (.NET 1.1) - 110.0 Кб
• Скачать исходники C# (.NET 2.0) - 111.2 Кб
• Скачать исходники VB (.NET 2.0) - 115.7 Кб

Введение

Почему статья о "цветах"? На самом деле, в .NET, можно использовать только два формата цвета: цветовую модель RGB и цветовую модель HSB. Они инкапсулированы в структуре Color пространства имен System.Drawing.

Ее в основном достаточно для простых применений типа изменения фонового цвета компонента, но недостаточно для разработки графических инструментов (или чего-то, что предполагает преобразование между форматами цвета).

Начался поиск других форматов типа CMYK, когда изучалось, как добавить поддержку отличного режима создания в специальные управляющие элементы.

Но быстро выяснилось, что разрабатываемые утилиты пригодятся в других проектах типа редактора SVG(масштабируемая векторная графика).

Поэтому публикуется серия из 3 статей. Планируемое содержание статей будет следующим:
• Часть 1. Введение в пространства цветов, и в наиболее распространенные из них.
• Часть 2.О специальных управляющих элементах, или как выбрать/определить цвет с пользовательскими компонентами. Будет описано много специальных управляющих элементов.
• Часть 3.Как использовать элементы управления цветом в режиме создания. Это будет руководство по созданию собственных редакторов.

Несколько определений

"Цвет является свойством зрительного восприятия, соответствующим у людей категориям под названием красный, желтый, белый и т. д. Цвет получается из оптического спектра (распределение энергии света как функции от длины волны), взаимодействующего в глазу со спектральными чувствительностями световых рецепторов. Категории цвета и физические характеристики цвета также связаны с объектами, материалами, источниками света и т. д., исходя из их физических свойств, таких как поглощение света, отражение света или спектры излучения". Определение цвета, Википедия.

"Цветометрия является наукой, описывающей цвета в числах, или обеспечивающей подбор физических цветов с помощью различных измерительных приборов. Цветометрия используется в химии и в таких отраслях, как цветная печать, производство ткани, производство краски и в пищевой промыщленности".
Определение цветометрии, Википедия.

Итак, как отображаются цвета в виде чисел? Ответ: цветовые модели.


I – Цветовые модели

RGB (красный, зелёный, синий)

Цветовая модель RGB (красный, зеленый, синий) является наиболее известной и самой используемой повседневно. Она определяет пространство цветов в виде трех компонентов:
Красный – в диапазоне 0-255
Зеленый – в диапазоне 0-255
Синий – в диапазоне 0-255

Цветовая модель RGB является суммируемой, то есть значения красного, зеленого и синего (называемые тремя основными цветами) комбинируются для воспроизводства других цветов.

Например, цвет "красный" представляется как [R=255, G=0, B=0], "фиолетовый" как [R=238, G=130, B=238] и т. д.

Ее типичным графическим представлением является следующее изображение:

В .NET структура Color использует эту модель для обеспечения поддержки цвета посредством свойств R, G и B.

Console.WriteLine(String.Format("R={0}, G={1}, B={2}",
Color.Red.R, Color.Red.G, Color.Red.B);
Console.WriteLine(String.Format("R={0}, G={1}, B={2}",
Color.Cyan.R, Color.Cyan.G, Color.Cyan.B);
Console.WriteLine(String.Format("R={0}, G={1}, B={2}",
Color.White.R, Color.White.G, Color.White.B);
Console.WriteLine(String.Format("R={0}, G={1}, B={2}",
Color.SteelBlue.R, Color.SteelBlue.G, Color.SteelBlue.B);

// и т.д.

но это не единственное ее применение. Поэтому можно определить специальную структуру RGB для дальнейшего кодирования, как показано ниже:

/// <summary>
/// структура RGB.
/// </summary>
public struct RGB
{
/// <summary>
/// получает пустую структуру RGB;
/// </summary>
public static readonly RGB Empty = new RGB();

private int red;
private int green;
private int blue;

public static bool operator ==(RGB item1, RGB item2)
{
return (
item1.Red == item2.Red
&& item1.Green == item2.Green
&& item1.Blue == item2.Blue
);
}

public static bool operator !=(RGB item1, RGB item2)
{
return (
item1.Red != item2.Red
|| item1.Green != item2.Green
|| item1.Blue != item2.Blue
);
}

/// <summary>
/// получает или устанавливает значение красного.
/// </summary>
public int Red
{
get
{
return red;
}
set
{
red = (value>255)? 255 : ((value<0)?0 : value);
}
}

/// <summary>
/// получает или устанавливает значение зеленого.
/// </summary>
public int Green
{
get
{
return green;
}
set
{
green = (value>255)? 255 : ((value<0)?0 : value);
}
}

/// <summary>
/// получает или устанавливает значение синего.
/// </summary>
public int Blue
{
get
{
return blue;
}
set
{
blue = (value>255)? 255 : ((value<0)?0 : value);
}
}

public RGB(int R, int G, int B)
{
this.red = (R>255)? 255 : ((R<0)?0 : R);
this.green = (G>255)? 255 : ((G<0)?0 : G);
this.blue = (B>255)? 255 : ((B<0)?0 : B);
}

public override bool Equals(Object obj)
{
if(obj==null || GetType()!=obj.GetType()) return false;

return (this == (RGB)obj);
}

public override int GetHashCode()
{
return Red.GetHashCode() ^ Green.GetHashCode() ^ Blue.GetHashCode();
}
}

Пространство цветов HSB

Цветовая модель HSB (тон, насыщенность, яркость) определяет пространство цветов в виде трех составных компонентов:
• Тон: тип цвета (скажем, красный, синий или желтый).
     o Изменяется от 0 до 360° в большинстве приложений (каждое значение соответствует одному цвету: 0 - красный, 45 – оттенок оранжевого, а 55 – оттенок желтого).
• Насыщенность: интенсивность цвета.
     o Изменяется от 0 до 100% (0 значит бесцветный, то есть оттенок серого между черным и белым; 100 значит интенсивный цвет).
     o Также иногда называется "чистота" по аналогии с цветометрическими величинами «условная чистота».
• Яркость (или значение): яркость цвета.
     o Изменяется от 0 до 100% (0 – всегда черный; в зависимости от насыщенности, 100 может быть белым или более или менее насыщенным цветом).

Ее типичным графическим представлением является следующее изображение:

Модель HSB иначе называется моделью HSV (тон, насыщенность, значение). Модель HSV была создана в 1978 г. Элви Рэем Смитом. Это нелинейное преобразование пространства цветов RGB. Иначе говоря, цвет определяется не как простая комбинация (сложение/вычитание) основных цветов, а как математическое преобразование.

Замечание: HSV и HSB тождественны, но HSL отличается.

Структура HSB выглядит так:

/// <summary>
/// структура для определения HSB.
/// </summary>
public struct HSB
{
/// <summary>
/// получает пустую структуру HSB;
/// </summary>
public static readonly HSB Empty = new HSB();

private double hue;
private double saturation;
private double brightness;

public static bool operator ==(HSB item1, HSB item2)
{
return (
item1.Hue == item2.Hue
&& item1.Saturation == item2.Saturation
&& item1.Brightness == item2.Brightness
);
}

public static bool operator !=(HSB item1, HSB item2)
{
return (
item1.Hue != item2.Hue
|| item1.Saturation != item2.Saturation
|| item1.Brightness != item2.Brightness
);
}

/// <summary>
/// получает или устанавливает компонент тона.
/// </summary>
public double Hue
{
get
{
return hue;
}
set
{
hue = (value>360)? 360 : ((value<0)?0:value);
}
}

/// <summary>
/// получает или устанавливает компонент насыщенности.
/// </summary>
public double Saturation
{
get
{
return saturation;
}
set
{
saturation = (value>1)? 1 : ((value<0)?0:value);
}
}

/// <summary>
/// получает или устанавливает компонент яркости.
/// </summary>
public double Brightness
{
get
{
return brightness;
}
set
{
brightness = (value>1)? 1 : ((value<0)? 0 : value);
}
}

/// <summary>
/// создает экземпляр структуры HSB.
/// </summary>
/// <param name="h">значение тона.</param>
/// <param name="s">значение насыщенности.</param>
/// <param name="b">значение яркости.</param>
public HSB(double h, double s, double b)
{
hue = (h>360)? 360 : ((h<0)?0:h);
saturation = (s>1)? 1 : ((s<0)?0:s);
brightness = (b>1)? 1 : ((b<0)?0:b);
}

public override bool Equals(Object obj)
{
if(obj==null || GetType()!=obj.GetType()) return false;

return (this == (HSB)obj);
}

public override int GetHashCode()
{
return Hue.GetHashCode() ^ Saturation.GetHashCode() ^
Brightness.GetHashCode();
}
}

Пространство цветов HSL

Пространство цветов HSL, иначе называемое HLS или HSI, означает:
• Тон: тип цвета (скажем, красный, синий или желтый).
     o Изменяется от 0 до 360° в большинстве приложений (каждое значение соответствует одному цвету: 0 - красный, 45 – оттенок оранжевого, а 55 – оттенок желтого).
• Насыщенность: изменение цвета в зависимости от светлоты.
     o Изменяется от 0 до 100% (от центра оси черного и белого).
• Светлота (также яркость, или светимость, или интенсивность).
     o Изменяется от 0 to 100% (от черного до белого).

Ее типичным графическим представлением является следующее изображение:

HSL похожа на HSB. Главное отличие в том, что HSL симметрична по отношению к светлоте и темноте.

Отсюда следует, что:
• В HSL компонент насыщенности всегда меняется от полностью насыщенного цвета до эквивалентного серого (в HSB, при B в максимуме, он меняется от насыщенного цвета до белого).
• В HSL светлота всегда покрывает весь спектр от черного через выбранный тон до белого (в HSB компонент B проходит лишь половину этого пути – от черного до выбранного тона).

HSL обеспечивает более точное (даже если оно не абсолютное) приближение цвета, чем HSB.
Структура HSL выглядит так:

/// <summary>
/// структура для определения HSL.
/// </summary>
public struct HSL
{
/// <summary>
/// получает пустую структуру HSL;
/// </summary>
public static readonly HSL Empty = new HSL();

private double hue;
private double saturation;
private double luminance;

public static bool operator ==(HSL item1, HSL item2)
{
return (
item1.Hue == item2.Hue
&& item1.Saturation == item2.Saturation
&& item1.Luminance == item2.Luminance
);
}

public static bool operator !=(HSL item1, HSL item2)
{
return (
item1.Hue != item2.Hue
|| item1.Saturation != item2.Saturation
|| item1.Luminance != item2.Luminance
);
}

/// <summary>
/// получает или устанавливает компонент тона.
/// </summary>
public double Hue
{
get
{
return hue;
}
set
{
hue = (value>360)? 360 : ((value<0)?0:value);
}
}

/// <summary>
/// получает или устанавливает компонент насыщенности.
/// </summary>
public double Saturation
{
get
{
return saturation;
}
set
{
saturation = (value>1)? 1 : ((value<0)?0:value);
}
}

/// <summary>
/// получает или устанавливает компонент яркости.
/// </summary>
public double Luminance
{
get
{
return luminance;
}
set
{
luminance = (value>1)? 1 : ((value<0)? 0 : value);
}
}

/// <summary>
/// создает экземпляр структуры HSL.
/// </summary>
/// <param name="h">значение тона.</param>
/// <param name="s">значение насыщенности.</param>
/// <param name="l">значение светлоты.</param>
public HSL(double h, double s, double l)
{
this.hue = (h>360)? 360 : ((h<0)?0:h);
this.saturation = (s>1)? 1 : ((s<0)?0:s);
this.luminance = (l>1)? 1 : ((l<0)?0:l);
}

public override bool Equals(Object obj)
{
if(obj==null || GetType()!=obj.GetType()) return false;

return (this == (HSL)obj);
}

public override int GetHashCode()
{
return Hue.GetHashCode() ^ Saturation.GetHashCode() ^
Luminance.GetHashCode();
}
}

Пространство цветов CMYK

Пространство цветов CMYK, иначе именуемое CMJN, означает:
• Голубой (циан).
     o Изменяется от 0 до 100% в большинстве приложений.
• Пурпурный (магента).
     o Изменяется от 0 до 100% в большинстве приложений.
• Желтый.
     o Изменяется от 0 до 100% в большинстве приложений.
• Черный.
     o Изменяется от 0 до 100% в большинстве приложений.

Это вычитательная цветовая модель, используемая в цветной печати. CMYK работает по принципу оптической иллюзии, основанной на поглощении света.

Принцип заключается в наложении трех изображений; одного для циана, одного для магенты и одного для желтого; это воспроизводит цвета.

Ее типичным графическим представлением является следующее изображение:

Как цветовая модель RGB, CMYK является комбинацией основных цветов (циан, магента, желтый и черный). Это единственное сходство между ними.

CMYK страдает от нехватки оттенков цветов, что вызывает дыры в воспроизводимом ею спектре цветов. Поэтому часто наблюдаются различия при преобразовании цвета из CMYK в RGB.

Зачем использовать эту модель? Почему используется черный? Это служит практической цели. Википедия говорит:
• Чтобы повысить качество печати и уменьшить муаровые узоры,
• Текст обычно печатается черным и содержит мелкие детали (например, засечки); поэтому воспроизведение текста с помощью трех чернил требует сверхточной выверки для каждого трехкомпонентного изображения.
• Комбинация голубого, пурпурного и желтого пигментов не дает (или дает редко) чистый черный.
• Смешение всех трех цветных чернил для получения черного может намочить бумагу, если не используется сухой тонер. Это является проблемой при высокоскоростной печати, когда бумага должна высыхать крайне быстро, чтобы не остались пятна на следующем листе, и низкокачественная бумага типа газетной может порваться при слишком сильном намокании.
• Использование одного блока черных чернил вместо трех блоков триадной краски существенно снижает затраты (черные чернила часто дешевле).

Структура CMYK выглядит так:

/// <summary>
/// структура для определения CMYK.
/// </summary>
public struct CMYK
{
/// <summary>
/// получает пустую структуру CMYK;
/// </summary>
public readonly static CMYK Empty = new CMYK();

private double c;
private double m;
private double y;
private double k;

public static bool operator ==(CMYK item1, CMYK item2)
{
return (
item1.Cyan == item2.Cyan
&& item1.Magenta == item2.Magenta
&& item1.Yellow == item2.Yellow
&& item1.Black == item2.Black
);
}

public static bool operator !=(CMYK item1, CMYK item2)
{
return (
item1.Cyan != item2.Cyan
|| item1.Magenta != item2.Magenta
|| item1.Yellow != item2.Yellow
|| item1.Black != item2.Black
);
}

public double Cyan
{
get
{
return c;
}
set
{
c = value;
c = (c>1)? 1 : ((c<0)? 0 : c);
}
}

public double Magenta
{
get
{
return m;
}
set
{
m = value;
m = (m>1)? 1 : ((m<0)? 0 : m);
}
}

public double Yellow
{
get
{
return y;
}
set
{
y = value;
y = (y>1)? 1 : ((y<0)? 0 : y);
}
}

public double Black
{
get
{
return k;
}
set
{
k = value;
k = (k>1)? 1 : ((k<0)? 0 : k);
}
}

/// <summary>
/// создает экземпляр структуры CMYK.
/// </summary>
public CMYK(double c, double m, double y, double k)
{
this.c = c;
this.m = m;
this.y = y;
this.k = k;
}

public override bool Equals(Object obj)
{
if(obj==null || GetType()!=obj.GetType()) return false;

return (this == (CMYK)obj);
}

public override int GetHashCode()
{
return Cyan.GetHashCode() ^
Magenta.GetHashCode() ^ Yellow.GetHashCode() ^ Black.GetHashCode();
}

}

Пространство цветов YUV

Модель YUV определяет пространство цветов в виде одной компоненты яркости и двух компонент цветности. Цветовая модель YUV применяется в стандартах совмещенных цветов видео PAL, NTSC и SECAM.

YUV моделирует человеческое восприятие цвета точней, чем стандартная модель RGB, используемая в аппаратном обеспечении компьютерной графики.

Пространство цветов YUV означает:
• Y, компонент яркости.
     o Изменяется от 0 до 100% в большинстве приложений.
• U и V – компоненты цветности (компоненты разностей синий-яркость и красный-яркость).
     o Выражены в виде коэффициентов в зависимости от используемой версии YUV.

Графическим представлением является следующее изображение:

Структура YUV следующая:

/// <summary>
/// структура для определения YUV.
/// </summary>
public struct YUV
{
/// <summary>
/// получает пустую структуру YUV.
/// </summary>
public static readonly YUV Empty = new YUV();

private double y;
private double u;
private double v;

public static bool operator ==(YUV item1, YUV item2)
{
return (
item1.Y == item2.Y
&& item1.U == item2.U
&& item1.V == item2.V
);
}

public static bool operator !=(YUV item1, YUV item2)
{
return (
item1.Y != item2.Y
|| item1.U != item2.U
|| item1.V != item2.V
);
}

public double Y
{
get
{
return y;
}
set
{
y = value;
y = (y>1)? 1 : ((y<0)? 0 : y);
}
}

public double U
{
get
{
return u;
}
set
{
u = value;
u = (u>0.436)? 0.436 : ((u<-0.436)? -0.436 : u);
}
}

public double V
{
get
{
return v;
}
set
{
v = value;
v = (v>0.615)? 0.615 : ((v<-0.615)? -0.615 : v);
}
}

/// <summary>
/// создает экземпляр структуры YUV.
/// </summary>
public YUV(double y, double u, double v)
{
this.y = (y>1)? 1 : ((y<0)? 0 : y);
this.u = (u>0.436)? 0.436 : ((u<-0.436)? -0.436 : u);
this.v = (v>0.615)? 0.615 : ((v<-0.615)? -0.615 : v);
}

public override bool Equals(Object obj)
{
if(obj==null || GetType()!=obj.GetType()) return false;

return (this == (YUV)obj);
}

public override int GetHashCode()
{
return Y.GetHashCode() ^ U.GetHashCode() ^ V.GetHashCode();
}

}

Пространство цветов CIE XYZ

В отличие от предыдущих моделей, модель CIE XYZ определяет абсолютное пространство цветов. Оно иначе называется пространством цветов CIE 1931 XYZ и означает:
• X, сравниваемый с красным
     o Изменяется от 0 до 0.9505
• Y, сравниваемый с зеленым
     o Изменяется от 0 до 1.0
• Z, сравниваемый с синим
     o Изменяется от 0 до 1.089

Прежде чем объяснить, почему это пространство цветов было включено в статью, надо знать, что оно является одним из первых стандартов, созданных Международной комиссией по освещению (CIE) в 1931г.. Оно основано на непосредственных измерениях человеческого глаза и служит основой для определения множества других пространств цветов.

Графическим представлением является следующее изображение:

Структура CIE XYZ следующая:

/// <summary>
/// структура для определения CIE XYZ.
/// </summary>
public struct CIEXYZ
{
/// <summary>
/// получает пустую структуру CIEXYZ.
/// </summary>
public static readonly CIEXYZ Empty = new CIEXYZ();
/// <summary>
/// получает структуру CIE D65 (белый).
/// </summary>
public static readonly CIEXYZ D65 = new CIEXYZ(0.9505, 1.0, 1.0890);


private double x;
private double y;
private double z;

public static bool operator ==(CIEXYZ item1, CIEXYZ item2)
{
return (
item1.X == item2.X
&& item1.Y == item2.Y
&& item1.Z == item2.Z
);
}

public static bool operator !=(CIEXYZ item1, CIEXYZ item2)
{
return (
item1.X != item2.X
|| item1.Y != item2.Y
|| item1.Z != item2.Z
);
}

/// <summary>
/// получает или устанавливает компонент X.
/// </summary>
public double X
{
get
{
return this.x;
}
set
{
this.x = (value>0.9505)? 0.9505 : ((value<0)? 0 : value);
}
}

/// <summary>
/// получает или устанавливает компонент Y.
/// </summary>
public double Y
{
get
{
return this.y;
}
set
{
this.y = (value>1.0)? 1.0 : ((value<0)?0 : value);
}
}

/// <summary>
/// получает или устанавливает компонент Z.
/// </summary>
public double Z
{
get
{
return this.z;
}
set
{
this.z = (value>1.089)? 1.089 : ((value<0)? 0 : value);
}
}

public CIEXYZ(double x, double y, double z)
{
this.x = (x>0.9505)? 0.9505 : ((x<0)? 0 : x);
this.y = (y>1.0)? 1.0 : ((y<0)? 0 : y);
this.z = (z>1.089)? 1.089 : ((z<0)? 0 : z);
}

public override bool Equals(Object obj)
{
if(obj==null || GetType()!=obj.GetType()) return false;

return (this == (CIEXYZ)obj);
}

public override int GetHashCode()
{
return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode();
}

}

Так почему была включена эта модель?

Было проведено краткое исследование для включения цветовой модели Cie L*a*b* в эту статью, и оказалось, что требуется преобразование в абсолютное пространство цветов перед преобразованием в L*a*b*. В правиле преобразования используется модель Cie XYZ. Поэтому оно было включено, чтобы всем было понятно, что означают значения XYZ, используемые дальше в статье.

Пространство цветов CIE L*a*b*

Лабораторное пространство цветов является цветоппонентным пространством с измерением L для яркости и a и b для цветоппонентных измерений, основанных на нелинейно сжатых координатах пространства цветов CIE XYZ.

Как сказано в предыдущем определении, пространство цветов CIE L*a*b*, иначе называемое пространством цветов CIE 1976, означает:
• L*, яркость
• a*, красное/зеленое цветоппонентное измерение
• b*, желтое/синее цветоппонентное измерение

Цветовая модель L*a*b* была создана, чтобы служить в качестве аппаратно-независимой модели, используемой как эталон. Она основана непосредственно на пространстве цветов CIE 1931 XYZ для линеаризации воспринимаемости цветовых различий.

Нелинейные соотношения для L*, a* и b* имитируют логарифмическую реакцию глаза, информация о цвете ссылается на цвет белой точки системы.

Структура CIE L*a*b* следующая:

/// <summary>
/// структура для определения CIE L*a*b*.
/// </summary>
public struct CIELab
{
/// <summary>
/// получает пустую структуру CIELab.
/// </summary>
public static readonly CIELab Empty = new CIELab();

private double l;
private double a;
private double b;


public static bool operator ==(CIELab item1, CIELab item2)
{
return (
item1.L == item2.L
&& item1.A == item2.A
&& item1.B == item2.B
);
}

public static bool operator !=(CIELab item1, CIELab item2)
{
return (
item1.L != item2.L
|| item1.A != item2.A
|| item1.B != item2.B
);
}


/// <summary>
/// получает или устанавливает компонент L.
/// </summary>
public double L
{
get
{
return this.l;
}
set
{
this.l = value;
}
}

/// <summary>
/// получает или устанавливает компонент a.
/// </summary>
public double A
{
get
{
return this.a;
}
set
{
this.a = value;
}
}

/// <summary>
/// получает или устанавливает компонент a.
/// </summary>
public double B
{
get
{
return this.b;
}
set
{
this.b = value;
}
}

public CIELab(double l, double a, double b)
{
this.l = l;
this.a = a;
this.b = b;
}

public override bool Equals(Object obj)
{
if(obj==null || GetType()!=obj.GetType()) return false;

return (this == (CIELab)obj);
}

public override int GetHashCode()
{
return L.GetHashCode() ^ a.GetHashCode() ^ b.GetHashCode();
}

}

Есть еще много других форматов типа RYB и CcMmYK, но нет цели создать каркас цветов.


II - Преобразование между моделями

Преобразования RGB

Преобразование цвета RGB в любую другую модель является основой в алгоритмах преобразования. Оно предполагает нормализацию красного, зеленого и синего: теперь значение меняется от [0..255] до [0..1].
a - RGB в HSB

Правило преобразования приведено ниже:

Ниже приведен его эквивалент на C#.

/// <summary>
/// преобразует RGB в HSB.
/// </summary>
public static HSB RGBtoHSB(int red, int green, int blue)
{
// нормализовать значения красного, зеленого и синего
double r = ((double)red/255.0);
double g = ((double)green/255.0);
double b = ((double)blue/255.0);

// начало преобразования
double max = Math.Max(r, Math.Max(g, b));
double min = Math.Min(r, Math.Min(g, b));

double h = 0.0;
if(max==r && g>=b)
{
h = 60 * (g-b)/(max-min);
}
else if(max==r && g < b)
{
h = 60 * (g-b)/(max-min) + 360;
}
else if(max == g)
{
h = 60 * (b-r)/(max-min) + 120;
}
else if(max == b)
{
h = 60 * (r-g)/(max-min) + 240;
}

double s = (max == 0)? 0.0 : (1.0 - (min/max));

return new HSB(h, s, (double)max);
}

b - RGB в HSL

Правило преобразования приведено ниже:

Эквивалент на C# следующий:

/// <summary>
/// преобразует RGB в HSL.
/// </summary>
/// <param name="red">значение красного, должно быть в [0,255].</param>
/// <param name="green">значение зеленого, должно быть в [0,255].</param>
/// <param name="blue">значение синего, должно быть в [0,255].</param>
public static HSL RGBtoHSL(int red, int green, int blue)
{
double h=0, s=0, l=0;

// нормализовать значения красного, зеленого, синего
double r = (double)red/255.0;
double g = (double)green/255.0;
double b = (double)blue/255.0;

double max = Math.Max(r, Math.Max(g, b));
double min = Math.Min(r, Math.Min(g, b));

// тон
if(max == min)
{
h = 0; // неопределённый
}
else if(max==r && g>=b)
{
h = 60.0*(g-b)/(max-min);
}
else if(max==r && g<b)
{
h = 60.0*(g-b)/(max-min) + 360.0;
}
else if(max==g)
{
h = 60.0*(b-r)/(max-min) + 120.0;
}
else if(max==b)
{
h = 60.0*(r-g)/(max-min) + 240.0;
}

// яркость
l = (max+min)/2.0;

// насыщенность
if(l == 0 || max == min)
{
s = 0;
}
else if(0<l && l<=0.5)
{
s = (max-min)/(max+min);
}
else if(l>0.5)
{
s = (max-min)/(2 - (max+min)); //(max-min > 0)?
}

return new HSL(
Double.Parse(String.Format("{0:0.##}", h)),
Double.Parse(String.Format("{0:0.##}", s)),
Double.Parse(String.Format("{0:0.##}", l))
);
}

Замечание: String.Format("{0:0.##}", h) является решением .NET для сохранения того же самого поведения округления. Если не понятно, что имеется в виду, испытайте пример кода ниже:

Console.WriteLine(Math.Round(4.45, 1)); // возвращает 4.4.
Console.WriteLine(Math.Round(4.55, 1)); // возвращает 4.6.

Проблема не заметна? Округление 4.45 должно возвращать 4.5, а не 4.4. Решение - использовать String.Format(), всегда применяющий метод "округлить до четного".

c - RGB в CMYK

Правило преобразования приведено ниже:

Эквивалент на C# следующий:

/// <summary>
/// преобразует RGB в CMYK.
/// </summary>
/// <param name="red">значение красного должно быть в [0, 255]. </param>
/// <param name="green">значение зеленого должно быть в [0, 255].</param>
/// <param name="blue">значение синего должно быть в [0, 255].</param>
public static CMYK RGBtoCMYK(int red, int green, int blue)
{
// нормализует значения красного, зеленого, синего
double c = (double)(255 - red)/255;
double m = (double)(255 - green)/255;
double y = (double)(255 - blue)/255;

double k = (double)Math.Min(c, Math.Min(m, y));

if(k == 1.0)
{
return new CMYK(0,0,0,1);
}
else
{
return new CMYK((c-k)/(1-k), (m-k)/(1-k), (y-k)/(1-k), k);
}
}

d - RGB to YUV (YUV444)

Правило преобразования приведено ниже:

Эквивалент на C# следующий:

/// <summary>
/// преобразует RGB в YUV.
/// </summary>
/// <param name="red">красный должен быть в [0, 255].</param>
/// <param name="green">зеленый должен быть в [0, 255].</param>
/// <param name="blue">синий должен быть в [0, 255].</param>
public static YUV RGBtoYUV(int red, int green, int blue)
{
YUV yuv = new YUV();

// нормализует значения красного, зеленого, синего
double r = (double)red/255.0;
double g = (double)green/255.0;
double b = (double)blue/255.0;

yuv.Y = 0.299*r + 0.587*g + 0.114*b;
yuv.U = -0.14713*r -0.28886*g + 0.436*b;
yuv.V = 0.615*r -0.51499*g -0.10001*b;

return yuv;
}

e - RGB в веб-цвет

Как известно, веб-цвета определяются двумя способами: например, "красный" определяется как rgb(255,0,0) или как #FF0000.

Объяснение второй формы простое:
• Символ "#" говорит, что формат шестнадцатеричный.
• Последние 6 символов определяют 3 пары: одну для красного, одну для зеленого и одну для синего.
• Каждая пара является шестнадцатеричным значением (основание 16) значения, изменяющегося от 0 до 255.

Можно разделить каждый компонент цвета на 16 и заменить числа, превосходящие 9, на их шестнадцатеричное значение (например, 10 = A, 11 = B и т. д.), но лучше использовать возможности String.Format().

/// <summary>
/// преобразует формат цвета RGB в шестнадцатеричный цвет.
/// </summary>
/// <param name="r">значение красного.</param>
/// <param name="g">значение зеленого.</param>
/// <param name="b">значение синего.</param>
public static string RGBToHex(int r, int g, int b)
{
return String.Format("#{0:x2}{1:x2}{2:x2}", r, g, b).ToUpper();
}

f - RGB в XYZ

Правило преобразования приведено ниже:

Эквивалент на C# следующий:

/// <summary>
/// преобразует RGB в CIE XYZ (пространство цветов CIE 1931)
/// </summary>
public static CIEXYZ RGBtoXYZ(int red, int green, int blue)
{
// нормализовать значения красного, зеленого, синего
double rLinear = (double)red/255.0;
double gLinear = (double)green/255.0;
double bLinear = (double)blue/255.0;

// преобразовать в форму sRGB
double r = (rLinear > 0.04045)? Math.Pow((rLinear + 0.055)/(
1 + 0.055), 2.2) : (rLinear/12.92) ;
double g = (gLinear > 0.04045)? Math.Pow((gLinear + 0.055)/(
1 + 0.055), 2.2) : (gLinear/12.92) ;
double b = (bLinear > 0.04045)? Math.Pow((bLinear + 0.055)/(
1 + 0.055), 2.2) : (bLinear/12.92) ;

// преобразует
return new CIEXYZ(
(r*0.4124 + g*0.3576 + b*0.1805),
(r*0.2126 + g*0.7152 + b*0.0722),
(r*0.0193 + g*0.1192 + b*0.9505)
);
}

g - RGB в L*a*b*

Преобразование в цветовую модель CIE L*a*b требует преобразования в CIE XYZ перед получением значений L*a*b*.

/// <summary>
/// преобразует RGB в CIELab.
/// </summary>
public static CIELab RGBtoLab(int red, int green, int blue)
{
return XYZtoLab( RGBtoXYZ(red, green, blue) );
}

Преобразование между XYZ и L*a*b* приведено ниже.


Преобразования HSB

a - HSB в RGB

Правило преобразования приведено ниже:

Эквивалент на C# следующий:

/// <summary>
/// преобразует HSB в RGB.
/// </summary>
public static RGB HSBtoRGB(double h, double s, double b)
{
double r = 0;
double g = 0;
double b = 0;

if(s == 0)
{
r = g = b = b;
}
else
{
// цветовой круг состоит из 6 секторов. Выяснить, в каком секторе
// находится.
double sectorPos = h / 60.0;
int sectorNumber = (int)(Math.Floor(sectorPos));
// получить дробную часть сектора
double fractionalSector = sectorPos - sectorNumber;

// вычислить значения для трех осей цвета.
double p = b * (1.0 - s);
double q = b * (1.0 - (s * fractionalSector));
double t = b * (1.0 - (s * (1 - fractionalSector)));

// присвоить дробные цвета r, g и b на основании сектора
// угол равняется.
switch(sectorNumber)
{
case 0:
r = b;
g = t;
b = p;
break;
case 1:
r = q;
g = b;
b = p;
break;
case 2:
r = p;
g = b;
b = t;
break;
case 3:
r = p;
g = q;
b = b;
break;
case 4:
r = t;
g = p;
b = b;
break;
case 5:
r = b;
g = p;
b = q;
break;
}
}

return new RGB(
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}", r*255.0)) ),
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}", g*255.0)) ),
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}", b*255.0)) )
);
}

b - HSB в HSL

Правило преобразования простое (но не точное): преобразовать в RGB, а затем в HSL.

/// <summary>
/// преобразует HSB в HSL.
/// </summary>
public static HSL HSBtoHSL(double h, double s, double b)
{
RGB rgb = HSBtoRGB(h, s, b);

return RGBtoHSL(rgb.Red, rgb.Green, rgb.Blue);
}

c - HSB в CMYK

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в CMYK.

/// <summary>
/// преобразует HSB в CMYK.
/// </summary>
public static CMYK HSBtoCMYK(double h, double s, double b)
{
RGB rgb = HSBtoRGB(h, s, b);

return RGBtoCMYK(rgb.Red, rgb.Green, rgb.Blue);
}

d - HSB в YUV

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в YUV.

/// <summary>
/// преобразует HSB в CMYK.
/// </summary>
public static YUV HSBtoYUV(double h, double s, double b)
{
RGB rgb = HSBtoRGB(h, s, b);

return RGBtoYUV(rgb.Red, rgb.Green, rgb.Blue);
}

Преобразования HSL

a - HSL в RGB

Правило преобразования приведено ниже:

Для каждого c = R,G,B:

Эквивалент на C# следующий:

/// <summary>
/// преобразует HSL в RGB.
/// </summary>
/// <param name="h">тон, должен быть в [0, 360].</param>
/// <param name="s">насыщенность, должна быть в [0, 1].</param>
/// <param name="l">яркость, должна быть в [0, 1].</param>
public static RGB HSLtoRGB(double h, double s, double l)
{
if(s == 0)
{
// ахроматический цвет (шкала серого)
return new RGB(
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}",
l*255.0)) ),
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}",
l*255.0)) ),
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}",
l*255.0)) )
);
}
else
{
double q = (l<0.5)?(l * (1.0+s)):(l+s - (l*s));
double p = (2.0 * l) - q;

double Hk = h/360.0;
double[] T = new double[3];
T[0] = Hk + (1.0/3.0); // Tr
T[1] = Hk; // Tb
T[2] = Hk - (1.0/3.0); // Tg

for(int i=0; i<3; i++)
{
if(T[i] < 0) T[i] += 1.0;
if(T[i] > 1) T[i] -= 1.0;

if((T[i]*6) < 1)
{
T[i] = p + ((q-p)*6.0*T[i]);
}
else if((T[i]*2.0) < 1) //(1.0/6.0)<=T[i] && T[i]<0.5
{
T[i] = q;
}
else if((T[i]*3.0) < 2) // 0.5<=T[i] && T[i]<(2.0/3.0)
{
T[i] = p + (q-p) * ((2.0/3.0) - T[i]) * 6.0;
}
else T[i] = p;
}

return new RGB(
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}",
T[0]*255.0)) ),
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}",
T[1]*255.0)) ),
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}",
T[2]*255.0)) )
);
}
}

b - HSL в HSB

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в HSB.

/// <summary>
/// преобразует HSL в HSB.
/// </summary>
public static HSB HSLtoHSB(double h, double s, double l)
{
RGB rgb = HSLtoRGB(h, s, l);

return RGBtoHSB(rgb.Red, rgb.Green, rgb.Blue);
}

c - HSL в CMYK

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в CMYK.

/// <summary>
/// преобразует HSL в CMYK.
/// </summary>
public static CMYK HSLtoCMYK(double h, double s, double l)
{
RGB rgb = HSLtoRGB(h, s, l);

return RGBtoCMYK(rgb.Red, rgb.Green, rgb.Blue);
}

d - HSL в YUV

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в CMYK.

/// <summary>
/// преобразует HSL в YUV.
/// </summary>
public static YUV HSLtoYUV(double h, double s, double l)
{
RGB rgb = HSLtoRGB(h, s, l);

return RGBtoYUV(rgb.Red, rgb.Green, rgb.Blue);
}

Преобразования CMYK

a - CMYK в RGB

Правило преобразования приведено ниже:

Эквивалент на C# следующий:

/// <summary>
/// преобразует CMYK в RGB.
/// </summary>
public static Color CMYKtoRGB(double c, double m, double y, double k)
{
int red = Convert.ToInt32((1-c) * (1-k) * 255.0);
int green = Convert.ToInt32((1-m) * (1-k) * 255.0);
int blue = Convert.ToInt32((1-y) * (1-k) * 255.0);

return Color.FromArgb(red, green, blue);
}

b - CMYK в HSL

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в HSL.

/// <summary>
/// преобразует CMYK в HSL.
/// </summary>
public static HSL CMYKtoHSL(double c, double m, double y, double k)
{
RGB rgb = CMYKtoRGB(c, m, y, k);

return RGBtoHSL(rgb.Red, rgb.Green, rgb.Blue);
}

c - CMYK в HSB

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в HSB.

/// <summary>
/// преобразует CMYK в HSB.
/// </summary>
public static HSB CMYKtoHSB(double c, double m, double y, double k)
{
RGB rgb = CMYKtoRGB(c, m, y, k);

return RGBtoHSB(rgb.Red, rgb.Green, rgb.Blue);
}

d - CMYK в YUV

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в YUV.

/// <summary>
/// преобразует CMYK в YUV.
/// </summary>
public static YUV CMYKtoYUV(double c, double m, double y, double k)
{
RGB rgb = CMYKtoRGB(c, m, y, k);

return RGBtoYUV(rgb.Red, rgb.Green, rgb.Blue);
}

Преобразования YUV

a - YUV в RGB

Правило преобразования приведено ниже:

Эквивалент на C# следующий:

/// <summary>
/// преобразует YUV в RGB.
/// </summary>
/// <param name="y">Y должен быть в [0, 1].</param>
/// <param name="u">U должен быть в [-0.436, +0.436].</param>
/// <param name="v">V должен быть в [-0.615, +0.615].</param>
public static RGB YUVtoRGB(double y, double u, double v)
{
RGB rgb = new RGB();

rgb.Red = Convert.ToInt32((y + 1.139837398373983740*v)*255);
rgb.Green = Convert.ToInt32((
y - 0.3946517043589703515*u - 0.5805986066674976801*v)*255);
rgb.Blue = Convert.ToInt32((y + 2.032110091743119266*u)*255);

return rgb;
}

b - YUV в HSL

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в HSL.

/// <summary>
/// преобразует YUV в HSL.
/// </summary>
/// <param name="y">Y должен быть в [0, 1].</param>
/// <param name="u">U должен быть в [-0.436, +0.436].</param>
/// <param name="v">V должен быть в [-0.615, +0.615].</param>
public static HSL YUVtoHSL(double y, double u, double v)
{
RGB rgb = YUVtoRGB(y, u, v);

return RGBtoHSL(rgb.Red, rgb.Green, rgb.Blue);
}

c - YUV в HSB

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в HSB.

/// <summary>
/// преобразует YUV в HSB.
/// </summary>
/// <param name="y">Y должен быть в [0, 1].</param>
/// <param name="u">U должен быть в [-0.436, +0.436].</param>
/// <param name="v">V должен быть в [-0.615, +0.615].</param>
public static HSB YUVtoHSB(double y, double u, double v)
{
RGB rgb = YUVtoRGB(y, u, v);

return RGBtoHSB(rgb.Red, rgb.Green, rgb.Blue);
}

d - YUV в CMYK

Ничего нового: правило преобразования заключается в преобразовании в RGB, а затем в CMYK.

/// <summary>
/// преобразует YUV в CMYK.
/// </summary>
/// <param name="y">Y должен быть в [0, 1].</param>
/// <param name="u">U должен быть в [-0.436, +0.436].</param>
/// <param name="v">V должен быть в [-0.615, +0.615].</param>
public static CMYK YUVtoCMYK(double y, double u, double v)
{
RGB rgb = YUVtoRGB(y, u, v);

return RGBtoCMYK(rgb.Red, rgb.Green, rgb.Blue);
}

Преобразования XYZ

a - XYZ в RGB

Правило преобразования приведено ниже:

a = 0.055

затем

Эквивалент на C# следующий:

/// <summary>
/// преобразует CIEXYZ в структуру RGB.
/// </summary>
public static RGB XYZtoRGB(double x, double y, double z)
{
double[] Clinear = new double[3];
Clinear[0] = x*3.2410 - y*1.5374 - z*0.4986; // красный
Clinear[1] = -x*0.9692 + y*1.8760 - z*0.0416; // зеленый
Clinear[2] = x*0.0556 - y*0.2040 + z*1.0570; // синий

for(int i=0; i<3; i++)
{
Clinear[i] = (Clinear[i]<=0.0031308)? 12.92*Clinear[i] : (
1+0.055)* Math.Pow(Clinear[i], (1.0/2.4)) - 0.055;
}

return new RGB(
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}",
Clinear[0]*255.0)) ),
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}",
Clinear[1]*255.0)) ),
Convert.ToInt32( Double.Parse(String.Format("{0:0.00}",
Clinear[2]*255.0)) )
);
}

b - XYZ в L*a*b*

Правило преобразования приведено ниже:

Xn, Yn и Zn – цветовые координаты CIE XYZ эталонной белой точки.

Эквивалент на C# следующий:

/// <summary>
/// функция преобразования XYZ в L*a*b*.
/// </summary>
private static double Fxyz(double t)
{
return ((t > 0.008856)? Math.Pow(t, (1.0/3.0)) : (7.787*t + 16.0/116.0));
}

/// <summary>
/// преобразует CIEXYZ в CIELab.
/// </summary>
public static CIELab XYZtoLab(double x, double y, double z)
{
CIELab lab = CIELab.Empty;

lab.L = 116.0 * Fxyz( y/CIEXYZ.D65.Y ) -16;
lab.A = 500.0 * (Fxyz( x/CIEXYZ.D65.X ) - Fxyz( y/CIEXYZ.D65.Y) );
lab.B = 200.0 * (Fxyz( y/CIEXYZ.D65.Y ) - Fxyz( z/CIEXYZ.D65.Z) );

return lab;
}

Преобразования L*a*b*

a - L*a*b* в XYZ

Правило преобразования приведено ниже:

Эквивалент на C# следующий:

/// <summary>
/// преобразует CIELab в CIEXYZ.
/// </summary>
public static CIEXYZ LabtoXYZ(double l, double a, double b)
{
double delta = 6.0/29.0;

double fy = (l+16)/116.0;
double fx = fy + (a/500.0);
double fz = fy - (b/200.0);

return new CIEXYZ(
(fx > delta)? CIEXYZ.D65.X * (fx*fx*fx) : (fx - 16.0/116.0)*3*(
delta*delta)*CIEXYZ.D65.X,
(fy > delta)? CIEXYZ.D65.Y * (fy*fy*fy) : (fy - 16.0/116.0)*3*(
delta*delta)*CIEXYZ.D65.Y,
(fz > delta)? CIEXYZ.D65.Z * (fz*fz*fz) : (fz - 16.0/116.0)*3*(
delta*delta)*CIEXYZ.D65.Z
);
}

b - L*a*b* в RGB

Ничего нового: правило преобразования заключается в преобразовании в XYZ, а затем в RGB:

/// <summary>
/// преобразует CIELab в RGB.
/// </summary>
public static RGB LabtoRGB(double l, double a, double b)
{
return XYZtoRGB( LabtoXYZ(l, a, b) );
}

Использование кода

После изложения алгоритмов преобразования, возможно, больше рассказать нечего.

На самом деле, в ColorSpaceHelper есть много других полезных методов, в том числе:
• Реализация усредненного цвета (ColorSpaceHelper.GetColorDistance()).
• Генерация цветового круга (ColorSpaceHelper.GetWheelColors()) с поддержкой 32бит (альфа).
• Генерация цвета спектра света для (ColorSpaceHelper.GetSpectrumColors())с поддержкой 32бит (альфа).
• Преобразование в и из веб-цветов (ColorSpaceHelper.HexToColor()).
• Преобразование в и из System.Drawing.Color и других структур.

Интересные особенности

Как сказано ранее, есть много форматов. Статья дает пример полезности применения распространенных цветовых моделей (RGB,HSL, HSB, etc.) для проектов.

Ниже приведено предварительное изображение того, что можно сделать (и что будет показано в следующей статье) с помощью ColorSpaceHelper: