Skip to content

Commit

Permalink
Added support for OKLab colour spaces
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasp85 committed Jan 20, 2021
1 parent f1bcb56 commit f5ff4ea
Show file tree
Hide file tree
Showing 19 changed files with 329 additions and 33 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Description: The encoding of colour can be handled in many different ways, using
License: MIT + file LICENSE
Encoding: UTF-8
SystemRequirements: C++11
RoxygenNote: 7.0.2
RoxygenNote: 7.1.1
URL: https://farver.data-imaginist.com, https://github.com/thomasp85/farver
BugReports: https://github.com/thomasp85/farver/issues
Suggests:
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# farver (development version)

* Added support for OKLab and OKLch (polar version of OKLab) colour spaces

# farver 2.0.3

* Fixed a bug in colour comparison where the blue channel got ignored
Expand Down
36 changes: 21 additions & 15 deletions R/aaa.R
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
colourspaces <- c(
"cmy", # 0
"cmyk", # 1
"hsl", # 2
"hsb", # 3
"hsv", # 4
"lab", # 5
"hunterlab", # 6
"lch", # 7
"luv", # 8
"rgb", # 9
"xyz", # 10
"yxy", # 11
"hcl" # 11
"cmy", # 1
"cmyk", # 2
"hsl", # 3
"hsb", # 4
"hsv", # 5
"lab", # 6
"hunterlab", # 7
"lch", # 8
"luv", # 9
"rgb", # 10
"xyz", # 11
"yxy", # 12
"hcl", # 13
"oklab", # 14
"oklch" # 15
)
colour_dims <- list(
cmy = c('c', 'm', 'y'),
Expand All @@ -26,7 +28,9 @@ colour_dims <- list(
rgb = c('r', 'g', 'b'),
xyz = c('x', 'y', 'z'),
yxy = c('y1', 'x', 'y2'),
hcl = c('h', 'c', 'l')
hcl = c('h', 'c', 'l'),
oklab = c('l', 'a', 'b'),
oklch = c('l', 'c', 'h')
)
colour_channel_index <- list(
cmy = c('c' = 1L, 'm' = 2L, 'y' = 3L),
Expand All @@ -41,7 +45,9 @@ colour_channel_index <- list(
rgb = c('r' = 1L, 'g' = 2L, 'b' = 3L),
xyz = c('x' = 1L, 'y' = 2L, 'z' = 3L),
yxy = c('y1' = 1L, 'x' = 2L, 'y2' = 3L),
hcl = c('h' = 1L, 'c' = 2L, 'l' = 3L)
hcl = c('h' = 1L, 'c' = 2L, 'l' = 3L),
oklab = c('l' = 1L, 'a' = 2L, 'b' = 3L),
oklch = c('l' = 1L, 'c' = 2L, 'h' = 3L)
)
distances <- c(
"euclidean",
Expand Down
5 changes: 3 additions & 2 deletions R/convert_colour.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
#'
#' @param from,to The input and output colour space. Allowed values are: `"cmy"`,
#' `"cmyk"`, `"hsl"`, `"hsb"`, `"hsv"`, `"lab"` (CIE L*ab), `"hunterlab"`
#' (Hunter Lab), `"lch"` (CIE Lch(ab) / polarLAB), `"luv"`, `"rgb"` (sRGB),
#' `"xyz"`, `"yxy"` (CIE xyY), or `"hcl"` (CIE Lch(uv) / polarLuv)
#' (Hunter Lab), `"oklab"`, `"lch"` (CIE Lch(ab) / polarLAB), `"luv"`,
#' `"rgb"` (sRGB), `"xyz"`, `"yxy"` (CIE xyY), `"hcl"` (CIE Lch(uv) / polarLuv),
#' or `"oklch"` (Polar form of oklab)
#'
#' @param white_from,white_to The white reference of the from and to colour
#' space. Will only have an effect for relative colour spaces such as Lab and
Expand Down
5 changes: 3 additions & 2 deletions R/decode.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
#' assumed 1. If `FALSE` any alpha channel is ignored.
#' @param to The output colour space. Allowed values are: `"cmy"`,
#' `"cmyk"`, `"hsl"`, `"hsb"`, `"hsv"`, `"lab"` (CIE L*ab), `"hunterlab"`
#' (Hunter Lab), `"lch"` (CIE Lch(ab) / polarLAB), `"luv"`, `"rgb"` (sRGB),
#' `"xyz"`, `"yxy"` (CIE xyY), or `"hcl"` (CIE Lch(uv) / polarLuv)
#' (Hunter Lab), `"oklab"`, `"lch"` (CIE Lch(ab) / polarLAB), `"luv"`,
#' `"rgb"` (sRGB), `"xyz"`, `"yxy"` (CIE xyY), `"hcl"` (CIE Lch(uv) / polarLuv),
#' or `"oklch"` (Polar form of oklab)
#' @param white The white reference of the output colour space. Will only have
#' an effect for relative colour spaces such as Lab and luv. Any value accepted
#' by [as_white_ref()] allowed.
Expand Down
5 changes: 3 additions & 2 deletions R/encode.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
#' of rows in `colour`. If `NULL` or a single `NA` it will be ignored.
#' @param from The input colour space. Allowed values are: `"cmy"`,
#' `"cmyk"`, `"hsl"`, `"hsb"`, `"hsv"`, `"lab"` (CIE L*ab), `"hunterlab"`
#' (Hunter Lab), `"lch"` (CIE Lch(ab) / polarLAB), `"luv"`, `"rgb"` (sRGB),
#' `"xyz"`, `"yxy"` (CIE xyY), or `"hcl"` (CIE Lch(uv) / polarLuv)
#' (Hunter Lab), `"oklab"`, `"lch"` (CIE Lch(ab) / polarLAB), `"luv"`,
#' `"rgb"` (sRGB), `"xyz"`, `"yxy"` (CIE xyY), `"hcl"` (CIE Lch(uv) / polarLuv),
#' or `"oklch"` (Polar form of oklab)
#' @param white The white reference of the input colour space. Will only have an
#' effect for relative colour spaces such as Lab and luv. Any value accepted by
#' [as_white_ref()] allowed.
Expand Down
5 changes: 3 additions & 2 deletions R/modify.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
#' @param value The value to modify with
#' @param space The colour space the channel pertains to. Allowed values are:
#' `"cmy"`, `"cmyk"`, `"hsl"`, `"hsb"`, `"hsv"`, `"lab"` (CIE L*ab), `"hunterlab"`
#' (Hunter Lab), `"lch"` (CIE Lch(ab) / polarLAB), `"luv"`, `"rgb"` (sRGB),
#' `"xyz"`, `"yxy"` (CIE xyY), or `"hcl"` (CIE Lch(uv) / polarLuv)
#' (Hunter Lab), `"oklab"` , `"lch"` (CIE Lch(ab) / polarLAB), `"luv"`,
#' `"rgb"` (sRGB), `"xyz"`, `"yxy"` (CIE xyY), `"hcl"` (CIE Lch(uv) / polarLuv),
#' or `"oklch"` (Polar form of oklab)
#' @param white The white reference of the channel colour space. Will only have
#' an effect for relative colour spaces such as Lab and luv. Any value accepted
#' by [as_white_ref()] allowed.
Expand Down
5 changes: 3 additions & 2 deletions man/convert_colour.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions man/decode_colour.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions man/encode_colour.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions man/manip_channel.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions src/ColorSpace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,5 +353,55 @@ namespace ColorSpace {
c = c < 0.0 ? 0.0 : c;
l = l < 0.0 ? 0.0 : (l > 100.0 ? 100.0 : l);
}

OkLab::OkLab() {}
OkLab::OkLab(double l, double a, double b) : l(l), a(a), b(b) {
valid = R_finite(l) && R_finite(a) && R_finite(b);
}
OkLab::OkLab(int l, int a, int b) : l(l), a(a), b(b) {
valid = !(l == R_NaInt || a == R_NaInt || b == R_NaInt);
}
void OkLab::Initialize(Rgb *color) {
OkLabConverter::ToColorSpace(color, this);
}
void OkLab::ToRgb(Rgb *color) {
OkLabConverter::ToColor(color, this);
}
void OkLab::Copy(IColorSpace *color) {
OkLab *lab = static_cast<OkLab*>(color);
lab->l = l;
lab->a = a;
lab->b = b;
lab->valid = valid;
}
void OkLab::Cap() {
if (!valid) return;
l = l < 0.0 ? 0.0 : (l > 1.0 ? 1.0 : l);
}

OkLch::OkLch() {}
OkLch::OkLch(double l, double c, double h) : l(l), c(c), h(h) {
valid = R_finite(l) && R_finite(c) && R_finite(h);
}
OkLch::OkLch(int l, int c, int h) : l(l), c(c), h(h) {
valid = !(l == R_NaInt || c == R_NaInt || h == R_NaInt);
}
void OkLch::Initialize(Rgb *color) {
OkLchConverter::ToColorSpace(color, this);
}
void OkLch::ToRgb(Rgb *color) {
OkLchConverter::ToColor(color, this);
}
void OkLch::Copy(IColorSpace *color) {
OkLch *lch = static_cast<OkLch*>(color);
lch->l = l;
lch->c = c;
lch->h = h;
lch->valid = valid;
}
void OkLch::Cap() {
if (!valid) return;
l = l < 0.0 ? 0.0 : (l > 1.0 ? 1.0 : l);
}
}

26 changes: 26 additions & 0 deletions src/ColorSpace.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,32 @@ namespace ColorSpace {
virtual void Cap();
};

struct OkLab : public IColorSpace {
double l, a, b;

OkLab();
OkLab(double l, double a, double b);
OkLab(int l, int a, int b);

virtual void Initialize(Rgb *color);
virtual void ToRgb(Rgb *color);
virtual void Copy(IColorSpace *color);
virtual void Cap();
};

struct OkLch : public IColorSpace {
double l, c, h;

OkLch();
OkLch(double l, double c, double h);
OkLch(int l, int c, int h);

virtual void Initialize(Rgb *color);
virtual void ToRgb(Rgb *color);
virtual void Copy(IColorSpace *color);
virtual void Cap();
};

}

#endif // COLOR_SPACE_H
Expand Down
94 changes: 94 additions & 0 deletions src/Conversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,5 +563,99 @@ namespace ColorSpace {

LuvConverter::ToColor(color, &luv);
}

// Using values reported at https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
// instead of going through xyz. This ensures any whitepoint is ignored
void OkLabConverter::ToColorSpace(Rgb *color, OkLab *item) {
if (!color->valid) {
item->valid = false;
return;
}
item->valid = true;
double r = color->r / 255.0;
double g = color->g / 255.0;
double b = color->b / 255.0;

r = ((r > 0.04045) ? std::pow((r + 0.055) / 1.055, 2.4) : (r / 12.92));
g = ((g > 0.04045) ? std::pow((g + 0.055) / 1.055, 2.4) : (g / 12.92));
b = ((b > 0.04045) ? std::pow((b + 0.055) / 1.055, 2.4) : (b / 12.92));

double l = 0.4121656120 * r + 0.5362752080 * g + 0.0514575653 * b;
double m = 0.2118591070 * r + 0.6807189584 * g + 0.1074065790 * b;
double s = 0.0883097947 * r + 0.2818474174 * g + 0.6302613616 * b;


l = std::cbrt(l);
m = std::cbrt(m);
s = std::cbrt(s);

item->l = 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s;
item->a = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s;
item->b = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s;
}
void OkLabConverter::ToColor(Rgb *color, OkLab *item) {
if (!item->valid) {
color->valid = false;
return;
}
color->valid = true;
double l = item->l + 0.3963377774 * item->a + 0.2158037573 * item->b;
double m = item->l - 0.1055613458 * item->a - 0.0638541728 * item->b;
double s = item->l - 0.0894841775 * item->a - 1.2914855480 * item->b;

l = l * l * l;
m = m * m * m;
s = s * s * s;

double r = 4.0767245293 * l - 3.3072168827 * m + 0.2307590544 * s;
double g = -1.2681437731 * l + 2.6093323231 * m - 0.3411344290 * s;
double b = -0.0041119885 * l - 0.7034763098 * m + 1.7068625689 * s;

color->r = ((r > 0.0031308) ? (1.055*std::pow(r, 1 / 2.4) - 0.055) : (12.92*r)) * 255.0;
color->g = ((g > 0.0031308) ? (1.055*std::pow(g, 1 / 2.4) - 0.055) : (12.92*g)) * 255.0;
color->b = ((b > 0.0031308) ? (1.055*std::pow(b, 1 / 2.4) - 0.055) : (12.92*b)) * 255.0;
}

void OkLchConverter::ToColorSpace(Rgb *color, OkLch *item) {
if (!color->valid) {
item->valid = false;
return;
}
item->valid = true;
OkLab lab;

OkLabConverter::ToColorSpace(color, &lab);
double l = lab.l;
double c = std::sqrt(lab.a*lab.a + lab.b*lab.b);
double h = std::atan2(lab.b, lab.a);

h = h / M_PI * 180;
if (h < 0) {
h += 360;
}
else if (h >= 360) {
h -= 360;
}

item->l = l;
item->c = c;
item->h = h;
}
void OkLchConverter::ToColor(Rgb *color, OkLch *item) {
if (!item->valid) {
color->valid = false;
return;
}
color->valid = true;
OkLab lab;

item->h = item->h * M_PI / 180;

lab.l = item->l;
lab.a = std::cos(item->h)*item->c;
lab.b = std::sin(item->h)*item->c;

OkLabConverter::ToColor(color, &lab);
}
}

16 changes: 16 additions & 0 deletions src/Conversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace ColorSpace {
struct Hsb;
struct HunterLab;
struct Hcl;
struct OkLab;
struct OkLch;

template <typename TColorSpace>
struct IConverter {
Expand Down Expand Up @@ -116,6 +118,20 @@ namespace ColorSpace {
static void ToColor(Rgb *color, Hcl *item);
};
typedef IConverter<Hcl> HclConverter;

template <>
struct IConverter<OkLab> {
static void ToColorSpace(Rgb *color, OkLab *item);
static void ToColor(Rgb *color, OkLab *item);
};
typedef IConverter<OkLab> OkLabConverter;

template <>
struct IConverter<OkLch> {
static void ToColorSpace(Rgb *color, OkLch *item);
static void ToColor(Rgb *color, OkLch *item);
};
typedef IConverter<OkLch> OkLchConverter;
}

#endif // RGB_CONVERTER_H
Expand Down
Loading

0 comments on commit f5ff4ea

Please sign in to comment.