Generate colour themes for Emacs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

colour.rs 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. // Copyright (C) 2018 Inderjit Gill
  2. // This program is free software: you can redistribute it and/or modify
  3. // it under the terms of the GNU General Public License as published by
  4. // the Free Software Foundation, either version 3 of the License, or
  5. // (at your option) any later version.
  6. // This program is distributed in the hope that it will be useful,
  7. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. // GNU General Public License for more details.
  10. // You should have received a copy of the GNU General Public License
  11. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  12. // |--------+-----------+-------------+-------------|
  13. // | format | element 0 | element 1 | element 2 |
  14. // |--------+-----------+-------------+-------------|
  15. // | RGB | R 0..1 | G 0..1 | B 0..1 |
  16. // | HSL | H 0..360 | S 0..1 | L 0..1 |
  17. // | HSLuv | H 0..360 | S 0..100 | L 0..100 |
  18. // | LAB | L 0..100 | A -128..128 | B -128..128 |
  19. // |--------+-----------+-------------+-------------|
  20. use crate::error::{Error, Result};
  21. use std;
  22. const REF_U: f64 = 0.197_830_006_642_836_807_64;
  23. const REF_V: f64 = 0.468_319_994_938_791_003_70;
  24. // http://www.brucelindbloom.com/index.html?Equations.html
  25. // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
  26. // we're using an sRGB working space with a D65 reference white
  27. // https://uk.mathworks.com/help/images/ref/whitepoint.html
  28. // the D65 whitepoint
  29. const WHITEPOINT_0: f64 = 0.9504;
  30. const WHITEPOINT_1: f64 = 1.0;
  31. const WHITEPOINT_2: f64 = 1.0888;
  32. const CIE_EPSILON: f64 = 0.008_856;
  33. const CIE_KAPPA: f64 = 903.3;
  34. // intent from the CIE
  35. //
  36. // #define CIE_EPSILON (216.0f / 24389.0f)
  37. // #define CIE_KAPPA (24389.0f / 27.0f)
  38. // RGB to XYZ (M)
  39. // 0.4124564 0.3575761 0.1804375
  40. // 0.2126729 0.7151522 0.0721750
  41. // 0.0193339 0.1191920 0.9503041
  42. // XYZ to RBG (M)^-1
  43. // 3.2404542 -1.5371385 -0.4985314
  44. // -0.9692660 1.8760108 0.0415560
  45. // 0.0556434 -0.2040259 1.0572252
  46. #[derive(Debug, PartialEq, Clone, Copy)]
  47. pub enum Format {
  48. RGB,
  49. HSLuv,
  50. HSL,
  51. LAB,
  52. HSV,
  53. }
  54. #[derive(Debug, Clone, Copy)]
  55. pub enum Colour {
  56. RGB(f64, f64, f64, f64),
  57. HSLuv(f64, f64, f64, f64),
  58. HSL(f64, f64, f64, f64),
  59. LAB(f64, f64, f64, f64),
  60. HSV(f64, f64, f64, f64),
  61. XYZ(f64, f64, f64, f64),
  62. LUV(f64, f64, f64, f64),
  63. LCH(f64, f64, f64, f64),
  64. }
  65. impl Colour {
  66. pub fn is_format(&self, format: Format) -> bool {
  67. match format {
  68. Format::RGB => match *self {
  69. Colour::RGB(_, _, _, _) => true,
  70. _ => false,
  71. },
  72. Format::HSLuv => match *self {
  73. Colour::HSLuv(_, _, _, _) => true,
  74. _ => false,
  75. },
  76. Format::HSL => match *self {
  77. Colour::HSL(_, _, _, _) => true,
  78. _ => false,
  79. },
  80. Format::LAB => match *self {
  81. Colour::LAB(_, _, _, _) => true,
  82. _ => false,
  83. },
  84. Format::HSV => match *self {
  85. Colour::HSV(_, _, _, _) => true,
  86. _ => false,
  87. },
  88. }
  89. }
  90. pub fn clone_as(&self, format: Format) -> Result<Colour> {
  91. match *self {
  92. Colour::HSL(h, s, l, alpha) => match format {
  93. Format::HSL => Ok(Colour::HSL(h, s, l, alpha)),
  94. Format::HSLuv => hsluv_from_xyz(xyz_from_rgb(rgb_from_hsl(*self)?)?),
  95. Format::HSV => hsv_from_rgb(rgb_from_hsl(*self)?),
  96. Format::LAB => lab_from_xyz(xyz_from_rgb(rgb_from_hsl(*self)?)?),
  97. Format::RGB => rgb_from_hsl(*self),
  98. },
  99. Colour::HSLuv(h, s, l, alpha) => match format {
  100. Format::HSL => hsl_from_rgb(rgb_from_xyz(xyz_from_hsluv(*self)?)?),
  101. Format::HSLuv => Ok(Colour::HSLuv(h, s, l, alpha)),
  102. Format::HSV => hsv_from_rgb(rgb_from_xyz(xyz_from_hsluv(*self)?)?),
  103. Format::LAB => lab_from_xyz(xyz_from_hsluv(*self)?),
  104. Format::RGB => rgb_from_xyz(xyz_from_hsluv(*self)?),
  105. },
  106. Colour::HSV(h, s, v, alpha) => match format {
  107. Format::HSL => hsl_from_rgb(rgb_from_hsv(*self)?),
  108. Format::HSLuv => hsluv_from_xyz(xyz_from_rgb(rgb_from_hsv(*self)?)?),
  109. Format::HSV => Ok(Colour::HSV(h, s, v, alpha)),
  110. Format::LAB => lab_from_xyz(xyz_from_rgb(rgb_from_hsv(*self)?)?),
  111. Format::RGB => rgb_from_hsv(*self),
  112. },
  113. Colour::LAB(l, a, b, alpha) => match format {
  114. Format::HSL => hsl_from_rgb(rgb_from_xyz(xyz_from_lab(*self)?)?),
  115. Format::HSLuv => hsluv_from_xyz(xyz_from_lab(*self)?),
  116. Format::HSV => hsv_from_rgb(rgb_from_xyz(xyz_from_lab(*self)?)?),
  117. Format::LAB => Ok(Colour::LAB(l, a, b, alpha)),
  118. Format::RGB => rgb_from_xyz(xyz_from_lab(*self)?),
  119. },
  120. Colour::RGB(r, g, b, alpha) => match format {
  121. Format::HSL => hsl_from_rgb(*self),
  122. Format::HSLuv => hsluv_from_xyz(xyz_from_rgb(*self)?),
  123. Format::HSV => hsv_from_rgb(*self),
  124. Format::LAB => lab_from_xyz(xyz_from_rgb(*self)?),
  125. Format::RGB => Ok(Colour::RGB(r, g, b, alpha)),
  126. },
  127. _ => Err(Error::IncorrectColourFormat),
  128. }
  129. }
  130. }
  131. fn colour_to_axis(component: f64) -> f64 {
  132. if component > 0.04045 {
  133. ((component + 0.055) / 1.055).powf(2.4)
  134. } else {
  135. (component / 12.92)
  136. }
  137. }
  138. fn axis_to_colour(a: f64) -> f64 {
  139. if a > 0.003_130_8 {
  140. (1.055 * a.powf(1.0 / 2.4)) - 0.055
  141. } else {
  142. a * 12.92
  143. }
  144. }
  145. fn xyz_from_rgb(rgb: Colour) -> Result<Colour> {
  146. match rgb {
  147. Colour::RGB(r, g, b, alpha) => {
  148. let rr = colour_to_axis(r);
  149. let gg = colour_to_axis(g);
  150. let bb = colour_to_axis(b);
  151. // multiply by matrix
  152. // see http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
  153. // sRGB colour space with D65 reference white
  154. //
  155. let x = (rr * 0.412_390_799_265_959_5)
  156. + (gg * 0.357_584_339_383_877_96)
  157. + (bb * 0.180_480_788_401_834_3);
  158. let y = (rr * 0.212_639_005_871_510_36)
  159. + (gg * 0.715_168_678_767_755_927_46)
  160. + (bb * 0.072_192_315_360_733_715_00);
  161. let z = (rr * 0.019_330_818_715_591_850_69)
  162. + (gg * 0.119_194_779_794_625_987_91)
  163. + (bb * 0.950_532_152_249_660_580_86);
  164. Ok(Colour::XYZ(x, y, z, alpha))
  165. }
  166. _ => Err(Error::IncorrectColourFormat),
  167. }
  168. }
  169. fn rgb_from_xyz(xyz: Colour) -> Result<Colour> {
  170. match xyz {
  171. Colour::XYZ(x, y, z, alpha) => {
  172. let r = (x * 3.240_969_941_904_521_343_77)
  173. + (y * -1.537_383_177_570_093_457_94)
  174. + (z * -0.498_610_760_293_003_283_66);
  175. let g = (x * -0.969_243_636_280_879_826_13)
  176. + (y * 1.875_967_501_507_720_667_72)
  177. + (z * 0.041_555_057_407_175_612_47);
  178. let b = (x * 0.055_630_079_696_993_608_46)
  179. + (y * -0.203_976_958_888_976_564_35)
  180. + (z * 1.056_971_514_242_878_560_72);
  181. let rr = axis_to_colour(r);
  182. let gg = axis_to_colour(g);
  183. let bb = axis_to_colour(b);
  184. Ok(Colour::RGB(rr, gg, bb, alpha))
  185. }
  186. _ => Err(Error::IncorrectColourFormat),
  187. }
  188. }
  189. fn axis_to_lab_component(a: f64) -> f64 {
  190. if a > CIE_EPSILON {
  191. a.cbrt()
  192. } else {
  193. ((CIE_KAPPA * a) + 16.0) / 116.0
  194. }
  195. }
  196. fn lab_from_xyz(xyz: Colour) -> Result<Colour> {
  197. match xyz {
  198. Colour::XYZ(x, y, z, alpha) => {
  199. let xr = x / WHITEPOINT_0;
  200. let yr = y / WHITEPOINT_1;
  201. let zr = z / WHITEPOINT_2;
  202. let fx = axis_to_lab_component(xr);
  203. let fy = axis_to_lab_component(yr);
  204. let fz = axis_to_lab_component(zr);
  205. let l = (116.0 * fy) - 16.0;
  206. let a = 500.0 * (fx - fy);
  207. let b = 200.0 * (fy - fz);
  208. Ok(Colour::LAB(l, a, b, alpha))
  209. }
  210. _ => Err(Error::IncorrectColourFormat),
  211. }
  212. }
  213. fn max_channel(r: f64, g: f64, b: f64) -> i32 {
  214. let hi = if r > g { 0 } else { 1 };
  215. let hival = if r > g { r } else { g };
  216. if b > hival {
  217. 2
  218. } else {
  219. hi
  220. }
  221. }
  222. // TODO: implement a better fmod, this one is not exact
  223. fn fmod(a: f64, b: f64) -> f64 {
  224. a - b * (a / b).floor()
  225. }
  226. // http://www.rapidtables.com/convert/color/rgb-to-hsl.htm
  227. fn hue(colour: Colour, max_chan: i32, chroma: f64) -> Result<f64> {
  228. if chroma == 0.0 {
  229. // return Err(Error::InvalidColourHue)
  230. return Ok(0.0);
  231. }
  232. let mut angle: f64;
  233. match colour {
  234. Colour::RGB(r, g, b, _) => {
  235. angle = match max_chan {
  236. 0 => fmod((g - b) / chroma, 6.0),
  237. 1 => ((b - r) / chroma) + 2.0,
  238. 2 => ((r - g) / chroma) + 4.0,
  239. _ => return Err(Error::InvalidColourChannel),
  240. }
  241. }
  242. _ => return Err(Error::IncorrectColourFormat),
  243. }
  244. angle *= 60.0;
  245. while angle < 0.0 {
  246. angle += 360.0;
  247. }
  248. Ok(angle)
  249. }
  250. // http://www.rapidtables.com/convert/color/rgb-to-hsl.htm
  251. fn hsl_from_rgb(colour: Colour) -> Result<Colour> {
  252. match colour {
  253. Colour::RGB(r, g, b, alpha) => {
  254. let min_val = r.min(g).min(b);
  255. let max_val = r.max(g).max(b);
  256. let max_ch = max_channel(r, g, b);
  257. let delta = max_val - min_val;
  258. let h = hue(colour, max_ch, delta)?;
  259. let lightness = 0.5 * (min_val + max_val);
  260. let saturation: f64 = if delta == 0.0 {
  261. 0.0
  262. } else {
  263. delta / (1.0 - ((2.0 * lightness) - 1.0).abs())
  264. };
  265. Ok(Colour::HSL(h, saturation, lightness, alpha))
  266. }
  267. _ => Err(Error::IncorrectColourFormat),
  268. }
  269. }
  270. fn hsv_from_rgb(colour: Colour) -> Result<Colour> {
  271. match colour {
  272. Colour::RGB(r, g, b, alpha) => {
  273. let min_val = r.min(g).min(b);
  274. let max_val = r.max(g).max(b);
  275. let max_ch = max_channel(r, g, b);
  276. let chroma = max_val - min_val;
  277. let h = hue(colour, max_ch, chroma)?;
  278. // valid_hue: bool = chroma != 0.0;
  279. let saturation: f64 = if chroma == 0.0 { 0.0 } else { chroma / max_val };
  280. // TODO: set valid_hue
  281. // return col.set('valid_hue', valid_hue);
  282. Ok(Colour::HSV(h, saturation, max_val, alpha))
  283. }
  284. _ => Err(Error::IncorrectColourFormat),
  285. }
  286. }
  287. fn rgb_from_chm(chroma: f64, h: f64, m: f64, alpha: f64) -> Colour {
  288. // todo: validhue test
  289. //
  290. // if (c.get('validHue') === undefined) {
  291. // return construct(Format.RGB, [m, m, m, element(c, ALPHA)]);
  292. //}
  293. let hprime = h / 60.0;
  294. let x = chroma * (1.0 - (fmod(hprime, 2.0) - 1.0).abs());
  295. let mut r = 0.0;
  296. let mut g = 0.0;
  297. let mut b = 0.0;
  298. if hprime < 1.0 {
  299. r = chroma;
  300. g = x;
  301. b = 0.0;
  302. } else if hprime < 2.0 {
  303. r = x;
  304. g = chroma;
  305. b = 0.0;
  306. } else if hprime < 3.0 {
  307. r = 0.0;
  308. g = chroma;
  309. b = x;
  310. } else if hprime < 4.0 {
  311. r = 0.0;
  312. g = x;
  313. b = chroma;
  314. } else if hprime < 5.0 {
  315. r = x;
  316. g = 0.0;
  317. b = chroma;
  318. } else if hprime < 6.0 {
  319. r = chroma;
  320. g = 0.0;
  321. b = x;
  322. }
  323. Colour::RGB(r + m, g + m, b + m, alpha)
  324. }
  325. fn rgb_from_hsl(hsl: Colour) -> Result<Colour> {
  326. match hsl {
  327. Colour::HSL(h, s, l, alpha) => {
  328. let chroma = (1.0 - ((2.0 * l) - 1.0).abs()) * s;
  329. let m = l - (0.5 * chroma);
  330. // todo: set validhue
  331. // f64 col = c.set('validHue', true);
  332. Ok(rgb_from_chm(chroma, h, m, alpha))
  333. }
  334. _ => Err(Error::IncorrectColourFormat),
  335. }
  336. }
  337. fn lab_component_to_axis(l: f64) -> f64 {
  338. if l.powf(3.0) > CIE_EPSILON {
  339. l.powf(3.0)
  340. } else {
  341. ((116.0 * l) - 16.0) / CIE_KAPPA
  342. }
  343. }
  344. fn xyz_from_lab(lab: Colour) -> Result<Colour> {
  345. match lab {
  346. Colour::LAB(l, a, b, alpha) => {
  347. let fy = (l + 16.0) / 116.0;
  348. let fz = fy - (b / 200.0);
  349. let fx = (a / 500.0) + fy;
  350. let xr = lab_component_to_axis(fx);
  351. let mut yr;
  352. if l > (CIE_EPSILON * CIE_KAPPA) {
  353. yr = (l + 16.0) / 116.0;
  354. yr = yr * yr * yr;
  355. } else {
  356. yr = l / CIE_KAPPA;
  357. }
  358. let zr = lab_component_to_axis(fz);
  359. Ok(Colour::XYZ(
  360. WHITEPOINT_0 * xr,
  361. WHITEPOINT_1 * yr,
  362. WHITEPOINT_2 * zr,
  363. alpha,
  364. ))
  365. }
  366. _ => Err(Error::IncorrectColourFormat),
  367. }
  368. }
  369. fn rgb_from_hsv(hsv: Colour) -> Result<Colour> {
  370. match hsv {
  371. Colour::HSV(h, s, v, alpha) => {
  372. let chroma = v * s;
  373. let m = v - chroma;
  374. Ok(rgb_from_chm(chroma, h, m, alpha))
  375. }
  376. _ => Err(Error::IncorrectColourFormat),
  377. }
  378. }
  379. // the luv and hsluv code is based on https://github.com/hsluv/hsluv-c
  380. // which uses the MIT License:
  381. // # The MIT License (MIT)
  382. // Copyright © 2015 Alexei Boronine (original idea, JavaScript implementation)
  383. // Copyright © 2015 Roger Tallada (Obj-C implementation)
  384. // Copyright © 2017 Martin Mitáš (C implementation, based on Obj-C
  385. // implementation)
  386. // Permission is hereby granted, free of charge, to any person obtaining a
  387. // copy of this software and associated documentation files (the “Software”),
  388. // to deal in the Software without restriction, including without limitation
  389. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  390. // and/or sell copies of the Software, and to permit persons to whom the
  391. // Software is furnished to do so, subject to the following conditions:
  392. // The above copyright notice and this permission notice shall be included
  393. // in all copies or substantial portions of the Software.
  394. // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
  395. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  396. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  397. // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  398. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  399. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  400. // IN THE SOFTWARE.
  401. #[derive(Debug, Clone, Copy)]
  402. struct Bounds {
  403. a: f64,
  404. b: f64,
  405. }
  406. fn get_bounds(l: f64, bounds: &mut [Bounds]) {
  407. let tl = l + 16.0;
  408. let sub1 = (tl * tl * tl) / 1_560_896.0;
  409. let sub2 = if sub1 > CIE_EPSILON {
  410. sub1
  411. } else {
  412. l / CIE_KAPPA
  413. };
  414. let mut m = [[0f64; 3]; 3];
  415. m[0][0] = 3.240_969_941_904_521_343_77;
  416. m[0][1] = -1.537_383_177_570_093_457_94;
  417. m[0][2] = -0.498_610_760_293_003_283_66;
  418. m[1][0] = -0.969_243_636_280_879_826_13;
  419. m[1][1] = 1.875_967_501_507_720_667_72;
  420. m[1][2] = 0.041_555_057_407_175_612_47;
  421. m[2][0] = 0.055_630_079_696_993_608_46;
  422. m[2][1] = -0.203_976_958_888_976_564_35;
  423. m[2][2] = 1.056_971_514_242_878_560_72;
  424. for channel in 0..3 {
  425. let m1 = m[channel][0];
  426. let m2 = m[channel][1];
  427. let m3 = m[channel][2];
  428. for t in 0..2 {
  429. let top1 = (284_517.0 * m1 - 94_839.0 * m3) * sub2;
  430. let top2 = (838_422.0 * m3 + 769_860.0 * m2 + 731_718.0 * m1) * l * sub2
  431. - 769_860.0 * (t as f64) * l;
  432. let bottom = (632_260.0 * m3 - 126_452.0 * m2) * sub2 + 126_452.0 * (t as f64);
  433. bounds[channel * 2 + t].a = top1 / bottom;
  434. bounds[channel * 2 + t].b = top2 / bottom;
  435. }
  436. }
  437. }
  438. fn ray_length_until_intersect(theta: f64, line: &Bounds) -> f64 {
  439. line.b / (theta.sin() - line.a * theta.cos())
  440. }
  441. fn max_chroma_for_lh(l: f64, h: f64) -> f64 {
  442. let mut min_len = std::f64::MAX;
  443. let hrad = h * 0.017_453_292_519_943_295_77; /* (2 * pi / 260) */
  444. let mut bounds = [Bounds { a: 0.0, b: 0.0 }; 6];
  445. get_bounds(l, &mut bounds);
  446. for b in &bounds {
  447. let l2 = ray_length_until_intersect(hrad, &b);
  448. if l2 >= 0.0 && l2 < min_len {
  449. min_len = l2;
  450. }
  451. }
  452. min_len
  453. }
  454. /* http://en.wikipedia.org/wiki/CIELUV
  455. * In these formulas, Yn refers to the reference white point. We are using
  456. * illuminant D65, so Yn (see refY in Maxima file) equals 1. The formula is
  457. * simplified accordingly.
  458. */
  459. fn y2l(y: f64) -> f64 {
  460. if y <= CIE_EPSILON {
  461. y * CIE_KAPPA
  462. } else {
  463. 116.0 * y.cbrt() - 16.0
  464. }
  465. }
  466. fn l2y(l: f64) -> f64 {
  467. if l <= 8.0 {
  468. l / CIE_KAPPA
  469. } else {
  470. let x = (l + 16.0) / 116.0;
  471. x * x * x
  472. }
  473. }
  474. fn luv_from_xyz(xyz: Colour) -> Result<Colour> {
  475. match xyz {
  476. Colour::XYZ(x, y, z, alpha) => {
  477. let var_u = (4.0 * x) / (x + (15.0 * y) + (3.0 * z));
  478. let var_v = (9.0 * y) / (x + (15.0 * y) + (3.0 * z));
  479. let l = y2l(y);
  480. let u = 13.0 * l * (var_u - REF_U);
  481. let v = 13.0 * l * (var_v - REF_V);
  482. if l < 0.000_000_01 {
  483. Ok(Colour::LUV(l, 0.0, 0.0, alpha))
  484. } else {
  485. Ok(Colour::LUV(l, u, v, alpha))
  486. }
  487. }
  488. _ => Err(Error::IncorrectColourFormat),
  489. }
  490. }
  491. fn xyz_from_luv(luv: Colour) -> Result<Colour> {
  492. match luv {
  493. Colour::LUV(l, u, v, alpha) => {
  494. if l <= 0.000_000_01 {
  495. return Ok(Colour::XYZ(0.0, 0.0, 0.0, alpha));
  496. }
  497. let var_u = u / (13.0 * l) + REF_U;
  498. let var_v = v / (13.0 * l) + REF_V;
  499. let y = l2y(l);
  500. let x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v);
  501. let z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v);
  502. Ok(Colour::XYZ(x, y, z, alpha))
  503. }
  504. _ => Err(Error::IncorrectColourFormat),
  505. }
  506. }
  507. fn lch_from_luv(luv: Colour) -> Result<Colour> {
  508. match luv {
  509. Colour::LUV(l, u, v, alpha) => {
  510. let mut h: f64;
  511. let c = (u * u + v * v).sqrt();
  512. if c < 0.000_000_01 {
  513. h = 0.0;
  514. } else {
  515. h = v.atan2(u) * 57.295_779_513_082_320_876_80; /* (180 / pi) */
  516. if h < 0.0 {
  517. h += 360.0;
  518. }
  519. }
  520. Ok(Colour::LCH(l, c, h, alpha))
  521. }
  522. _ => Err(Error::IncorrectColourFormat),
  523. }
  524. }
  525. fn luv_from_lch(lch: Colour) -> Result<Colour> {
  526. match lch {
  527. Colour::LCH(l, c, h, alpha) => {
  528. let hrad = h * 0.017_453_292_519_943_295_77; /* (pi / 180.0) */
  529. let u = hrad.cos() * c;
  530. let v = hrad.sin() * c;
  531. Ok(Colour::LUV(l, u, v, alpha))
  532. }
  533. _ => Err(Error::IncorrectColourFormat),
  534. }
  535. }
  536. fn lch_from_hsluv(hsluv: Colour) -> Result<Colour> {
  537. match hsluv {
  538. Colour::HSLuv(h, s, l, alpha) => {
  539. let c = if l > 99.999_999_9 || l < 0.000_000_01 {
  540. 0.0
  541. } else {
  542. max_chroma_for_lh(l, h) / 100.0 * s
  543. };
  544. if s < 0.000_000_01 {
  545. Ok(Colour::LCH(l, c, 0.0, alpha))
  546. } else {
  547. Ok(Colour::LCH(l, c, h, alpha))
  548. }
  549. }
  550. _ => Err(Error::IncorrectColourFormat),
  551. }
  552. }
  553. fn hsluv_from_lch(lch: Colour) -> Result<Colour> {
  554. match lch {
  555. Colour::LCH(l, c, h, alpha) => {
  556. let s = if l > 99.999_999_9 || l < 0.000_000_01 {
  557. 0.0
  558. } else {
  559. c / max_chroma_for_lh(l, h) * 100.0
  560. };
  561. if c < 0.000_000_01 {
  562. Ok(Colour::HSLuv(0.0, s, l, alpha))
  563. } else {
  564. Ok(Colour::HSLuv(h, s, l, alpha))
  565. }
  566. }
  567. _ => Err(Error::IncorrectColourFormat),
  568. }
  569. }
  570. fn xyz_from_hsluv(hsluv: Colour) -> Result<Colour> {
  571. xyz_from_luv(luv_from_lch(lch_from_hsluv(hsluv)?)?)
  572. }
  573. fn hsluv_from_xyz(xyz: Colour) -> Result<Colour> {
  574. hsluv_from_lch(lch_from_luv(luv_from_xyz(xyz)?)?)
  575. }
  576. #[cfg(test)]
  577. mod tests {
  578. use super::*;
  579. const TOLERANCE: f64 = 0.02;
  580. fn f64_within(tolerance: f64, a: f64, b: f64, msg: &'static str) {
  581. assert!(
  582. (a - b).abs() < tolerance,
  583. format!("{} expected: {}, actual: {}", msg, b, a)
  584. )
  585. }
  586. fn is_format(expected: Format, actual: Format) {
  587. assert!(
  588. expected == actual,
  589. format!("expected: {:?}, actual: {:?}", expected, actual)
  590. )
  591. }
  592. fn assert_col(col: Colour, format: Format, c0: f64, c1: f64, c2: f64, c3: f64) {
  593. match col {
  594. Colour::HSL(h, s, l, alpha) => {
  595. is_format(format, Format::HSL);
  596. f64_within(TOLERANCE, h, c0, "HSL H");
  597. f64_within(TOLERANCE, s, c1, "HSL_S");
  598. f64_within(TOLERANCE, l, c2, "HSL_L");
  599. f64_within(TOLERANCE, alpha, c3, "HSL_alpha");
  600. }
  601. Colour::HSLuv(h, s, l, alpha) => {
  602. is_format(format, Format::HSLuv);
  603. f64_within(TOLERANCE, h, c0, "HSLuv H");
  604. f64_within(TOLERANCE, s, c1, "HSLuv_S");
  605. f64_within(TOLERANCE, l, c2, "HSLuv_L");
  606. f64_within(TOLERANCE, alpha, c3, "HSLuv_alpha");
  607. }
  608. Colour::HSV(h, s, v, alpha) => {
  609. is_format(format, Format::HSV);
  610. f64_within(TOLERANCE, h, c0, "HSV H");
  611. f64_within(TOLERANCE, s, c1, "HSV_S");
  612. f64_within(TOLERANCE, v, c2, "HSV_V");
  613. f64_within(TOLERANCE, alpha, c3, "HSV_alpha");
  614. }
  615. Colour::LAB(l, a, b, alpha) => {
  616. is_format(format, Format::LAB);
  617. f64_within(TOLERANCE, l, c0, "LAB_L");
  618. f64_within(TOLERANCE, a, c1, "LAB_A");
  619. f64_within(TOLERANCE, b, c2, "LAB_B");
  620. f64_within(TOLERANCE, alpha, c3, "LAB_alpha");
  621. }
  622. Colour::RGB(r, g, b, alpha) => {
  623. is_format(format, Format::RGB);
  624. f64_within(TOLERANCE, r, c0, "RGB R");
  625. f64_within(TOLERANCE, g, c1, "RGB_G");
  626. f64_within(TOLERANCE, b, c2, "RGB_B");
  627. f64_within(TOLERANCE, alpha, c3, "RGB_alpha");
  628. }
  629. _ => assert_eq!(true, false),
  630. }
  631. }
  632. fn assert_colour_match(expected: Colour, col: Colour) {
  633. match expected {
  634. Colour::HSL(h, s, l, alpha) => assert_col(col, Format::HSL, h, s, l, alpha),
  635. Colour::HSLuv(h, s, l, alpha) => assert_col(col, Format::HSLuv, h, s, l, alpha),
  636. Colour::HSV(h, s, v, alpha) => assert_col(col, Format::HSV, h, s, v, alpha),
  637. Colour::LAB(l, a, b, alpha) => assert_col(col, Format::LAB, l, a, b, alpha),
  638. Colour::RGB(r, g, b, alpha) => assert_col(col, Format::RGB, r, g, b, alpha),
  639. _ => assert_eq!(true, false),
  640. }
  641. }
  642. fn assert_colour_rgb_hsl_match(r: f64, g: f64, b: f64, h: f64, s: f64, l: f64) {
  643. let rgb = Colour::RGB(r, g, b, 1.0);
  644. let hsl = Colour::HSL(h, s, l, 1.0);
  645. assert_colour_match(rgb, hsl.clone_as(Format::RGB).unwrap());
  646. assert_colour_match(hsl, rgb.clone_as(Format::HSL).unwrap());
  647. }
  648. #[test]
  649. fn test_colour() {
  650. let rgb = Colour::RGB(0.2, 0.09803921568627451, 0.49019607843137253, 1.0);
  651. let hsl = Colour::HSL(255.6, 0.6666, 0.294, 1.0);
  652. let lab = Colour::LAB(
  653. 19.555676428108306,
  654. 39.130689315704764,
  655. -51.76254071703564,
  656. 1.0,
  657. );
  658. assert_colour_match(rgb, rgb.clone_as(Format::RGB).unwrap());
  659. assert_colour_match(rgb, hsl.clone_as(Format::RGB).unwrap());
  660. assert_colour_match(rgb, lab.clone_as(Format::RGB).unwrap());
  661. assert_colour_match(hsl, rgb.clone_as(Format::HSL).unwrap());
  662. assert_colour_match(hsl, hsl.clone_as(Format::HSL).unwrap());
  663. assert_colour_match(hsl, lab.clone_as(Format::HSL).unwrap());
  664. assert_colour_match(lab, rgb.clone_as(Format::LAB).unwrap());
  665. assert_colour_match(lab, hsl.clone_as(Format::LAB).unwrap());
  666. assert_colour_match(lab, lab.clone_as(Format::LAB).unwrap());
  667. }
  668. #[test]
  669. fn test_colour_2() {
  670. let rgb = Colour::RGB(0.066666, 0.8, 0.86666666, 1.0);
  671. let hsluv = Colour::HSLuv(205.7022764106217, 98.91247496876854, 75.15356872935901, 1.0);
  672. assert_colour_match(rgb, hsluv.clone_as(Format::RGB).unwrap());
  673. assert_colour_match(hsluv, rgb.clone_as(Format::HSLuv).unwrap());
  674. }
  675. #[test]
  676. fn test_colour_3() {
  677. assert_colour_rgb_hsl_match(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
  678. assert_colour_rgb_hsl_match(1.0, 1.0, 1.0, 0.0, 0.0, 1.0);
  679. assert_colour_rgb_hsl_match(1.0, 0.0, 0.0, 0.0, 1.0, 0.5);
  680. assert_colour_rgb_hsl_match(0.0, 1.0, 0.0, 120.0, 1.0, 0.5);
  681. assert_colour_rgb_hsl_match(0.0, 0.0, 1.0, 240.0, 1.0, 0.5);
  682. assert_colour_rgb_hsl_match(1.0, 1.0, 0.0, 60.0, 1.0, 0.5);
  683. assert_colour_rgb_hsl_match(0.0, 1.0, 1.0, 180.0, 1.0, 0.5);
  684. assert_colour_rgb_hsl_match(1.0, 0.0, 1.0, 300.0, 1.0, 0.5);
  685. assert_colour_rgb_hsl_match(0.7529, 0.7529, 0.7529, 0.0, 0.0, 0.75);
  686. assert_colour_rgb_hsl_match(0.5, 0.5, 0.5, 0.0, 0.0, 0.5);
  687. assert_colour_rgb_hsl_match(0.5, 0.0, 0.0, 0.0, 1.0, 0.25);
  688. assert_colour_rgb_hsl_match(0.5, 0.5, 0.0, 60.0, 1.0, 0.25);
  689. assert_colour_rgb_hsl_match(0.0, 0.5, 0.0, 120.0, 1.0, 0.25);
  690. assert_colour_rgb_hsl_match(0.5, 0.0, 0.5, 300.0, 1.0, 0.25);
  691. assert_colour_rgb_hsl_match(0.0, 0.5, 0.5, 180.0, 1.0, 0.25);
  692. assert_colour_rgb_hsl_match(0.0, 0.0, 0.5, 240.0, 1.0, 0.25);
  693. }
  694. }