├── LICENSE ├── README.md ├── color.c ├── color.h ├── colors.js └── index.html /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Cory Nelson 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | colors 2 | ====== 3 | 4 | High-quality colorspace conversions 5 | -------------------------------------------------------------------------------- /color.c: -------------------------------------------------------------------------------- 1 | /* 2 | Color conversions 3 | Copyright (c) 2011, Cory Nelson (phrosty@gmail.com) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | Some notes: 26 | 27 | - Performance is important, but takes a back seat to accuracy. 28 | - Constants are expressed as rationals when possible. When not, they are given to 96 bits of precision. 29 | - Care has been taken to allow emitting of high-accuracy FMA instructions. 30 | - Specialized shortcuts between some colorspaces have been made to improve performance and accuracy. 31 | */ 32 | 33 | #define COLOR_EXPORTS 34 | 35 | #define STRICT 36 | #define NOMINMAX 37 | #define WIN32_LEAN_AND_MEAN 38 | 39 | #include 40 | #include 41 | #include 42 | #include "color.h" 43 | 44 | static double const COLOR_REF_X = 31271.0/32902.0; 45 | static double const COLOR_REF_Xr = 32902.0/31271.0; 46 | 47 | //static double const COLOR_REF_Y = 1.0; 48 | //static double const COLOR_REF_Yr = 1.0; 49 | 50 | static double const COLOR_REF_Z = 35827.0/32902.0; 51 | static double const COLOR_REF_Zr = 32902.0/35827.0; 52 | 53 | static double const COLOR_REF_U13 = 813046.0/316141.0; // X * 4 / (X + Y * 15 + Z * 3) * 13 54 | static double const COLOR_REF_V13 = 1924767.0/316141.0; // Y * 9 / (X + Y * 15 + Z * 3) * 13 55 | 56 | __declspec(align(64)) double const rgb_to_yuv[][8] = 57 | { 58 | // Rec. 601 59 | { 0.299, 0.587, 0.114, -32591.0/221500.0, -63983.0/221500.0, -72201.0/140200.0, -7011.0/70100.0 }, 60 | // Rec. 709 61 | { 0.2126, 0.7152, 0.0722, -115867.0/1159750.0, -194892.0/579875.0, -54981.0/98425.0, -44403.0/787400.0 }, 62 | // SMPTE 240M 63 | { 0.212, 0.701, 0.087, -11554.0/114125.0, -76409.0/228250.0, -86223.0/157600.0, -10701.0/157600.0 }, 64 | // FCC 65 | { 0.3, 0.59, 0.11, -327.0/2225.0, -6431.0/22250.0, -7257.0/14000.0, -1353.0/14000.0 } 66 | }; 67 | 68 | __declspec(align(32)) double const yuv_to_rgb[][4] = 69 | { 70 | // Rec. 601 71 | { 701.0/615.0, -25251.0/63983.0, -209599.0/361005.0, 443.0/218.0 }, 72 | // Rec. 709 73 | { 3937.0/3075.0, -1674679.0/7795680.0, -4185031.0/10996200.0, 4639.0/2180.0 }, 74 | // SMPTE 240M 75 | { 788.0/615.0, -79431.0/305636.0, -167056.0/431115.0, 913.0/436.0 }, 76 | // FCC 77 | { 140.0/123.0, -4895.0/12862.0, -1400.0/2419.0, 445.0/218.0 } 78 | }; 79 | 80 | static void color_RGB8_extract(double *dst, struct color const *src) 81 | { 82 | assert(dst != NULL); 83 | assert(src != NULL); 84 | assert(src->type == COLOR_RGB8); 85 | 86 | dst[0] = src->RGB8.R; 87 | dst[1] = src->RGB8.G; 88 | dst[2] = src->RGB8.B; 89 | } 90 | 91 | static void color_RGB8_to_RGB(struct color *c, uint8_t extra) 92 | { 93 | double R, G, B; 94 | 95 | assert(c != NULL); 96 | assert(c->type == COLOR_RGB8); 97 | 98 | R = c->RGB8.R * (1.0 / 255.0); 99 | G = c->RGB8.G * (1.0 / 255.0); 100 | B = c->RGB8.B * (1.0 / 255.0); 101 | 102 | c->RGB.R = R; 103 | c->RGB.G = G; 104 | c->RGB.B = B; 105 | c->type = COLOR_RGB; 106 | } 107 | 108 | static double rgb8_to_linear(uint8_t c) 109 | { 110 | return c >= 11 ? pow(c * (40.0 / 10761.0) + (11.0 / 211.0), 2.4) : c * (5.0 / 16473.0); 111 | } 112 | 113 | static void color_RGB8_to_LinearRGB(struct color *c, uint8_t extra) 114 | { 115 | double R, G, B; 116 | 117 | assert(c != NULL); 118 | assert(c->type == COLOR_RGB8); 119 | 120 | R = rgb8_to_linear(c->RGB8.R); 121 | G = rgb8_to_linear(c->RGB8.G); 122 | B = rgb8_to_linear(c->RGB8.B); 123 | 124 | c->LinearRGB.R = R; 125 | c->LinearRGB.G = G; 126 | c->LinearRGB.B = B; 127 | c->type = COLOR_LINEAR_RGB; 128 | } 129 | 130 | static void color_RGB_extract(double *dst, struct color const *src) 131 | { 132 | assert(dst != NULL); 133 | assert(src != NULL); 134 | assert(src->type == COLOR_RGB); 135 | 136 | dst[0] = src->RGB.R; 137 | dst[1] = src->RGB.G; 138 | dst[2] = src->RGB.B; 139 | } 140 | 141 | static void color_RGB_to_RGB8(struct color *c, uint8_t extra) 142 | { 143 | double R, G, B; 144 | 145 | assert(c != NULL); 146 | assert(c->type == COLOR_RGB); 147 | 148 | R = c->RGB.R * 255.0 + 0.5; 149 | G = c->RGB.G * 255.0 + 0.5; 150 | B = c->RGB.B * 255.0 + 0.5; 151 | 152 | c->type = COLOR_RGB8; 153 | 154 | c->RGB8.R = R < 0.0 ? 0 : R > 255.0 ? 255 : (uint8_t)R; 155 | c->RGB8.G = G < 0.0 ? 0 : G > 255.0 ? 255 : (uint8_t)G; 156 | c->RGB8.B = B < 0.0 ? 0 : B > 255.0 ? 255 : (uint8_t)B; 157 | } 158 | 159 | static double rgb_to_linear(double c) 160 | { 161 | return c > (0.0031308 * 12.92) ? pow(c * (1.0 / 1.055) + (0.055 / 1.055), 2.4) : c * (1.0 / 12.92); 162 | } 163 | 164 | static void color_RGB_to_LinearRGB(struct color *c, uint8_t extra) 165 | { 166 | double R, G, B; 167 | 168 | assert(c != NULL); 169 | assert(c->type == COLOR_RGB); 170 | 171 | R = rgb_to_linear(c->RGB.R); 172 | G = rgb_to_linear(c->RGB.G); 173 | B = rgb_to_linear(c->RGB.B); 174 | 175 | c->LinearRGB.R = R; 176 | c->LinearRGB.G = G; 177 | c->LinearRGB.B = B; 178 | c->type = COLOR_LINEAR_RGB; 179 | } 180 | 181 | static void color_RGB_to_HSL(struct color *c, uint8_t extra) 182 | { 183 | double R, G, B, min, max, delta, L; 184 | 185 | assert(c != NULL); 186 | assert(c->type == COLOR_RGB); 187 | 188 | R = c->RGB.R; 189 | G = c->RGB.G; 190 | B = c->RGB.B; 191 | 192 | min = R < G ? R : G; 193 | if(B < min) min = B; 194 | 195 | max = R > G ? R : G; 196 | if(B > max) max = B; 197 | 198 | delta = max - min; 199 | 200 | L = (max + min) * 0.5; 201 | 202 | c->HSL.L = L; 203 | c->type = COLOR_HSL; 204 | 205 | if(fabs(delta) > 0.0) 206 | { 207 | c->HSL.S = 208 | L < 0.5 ? delta / (max + min) : 209 | delta / (2.0 - max - min); 210 | 211 | c->HSL.H = 212 | max == R ? (G - B) / delta : 213 | max == G ? (B - R) / delta + 2.0 : 214 | (R - G) / delta + 4.0; 215 | 216 | return; 217 | } 218 | 219 | c->HSL.S = 0.0; 220 | c->HSL.H = 0.0; 221 | } 222 | 223 | static void color_RGB_to_HSV(struct color *c, uint8_t extra) 224 | { 225 | double R, G, B, min, max, delta; 226 | 227 | assert(c != NULL); 228 | assert(c->type == COLOR_RGB); 229 | 230 | R = c->RGB.R; 231 | G = c->RGB.G; 232 | B = c->RGB.B; 233 | 234 | min = R < G ? R : G; 235 | if(B < min) min = B; 236 | 237 | max = R > G ? R : G; 238 | if(B > max) max = B; 239 | 240 | delta = max - min; 241 | 242 | c->HSV.V = max; 243 | c->type = COLOR_HSV; 244 | 245 | if(fabs(delta) > 0.0) 246 | { 247 | c->HSV.S = delta / max; 248 | 249 | c->HSV.H = 250 | max == R ? (G - B) / delta : 251 | max == G ? (B - R) / delta + 2.0 : 252 | (R - G) / delta + 4.0; 253 | 254 | return; 255 | } 256 | 257 | c->HSV.S = 0.0; 258 | c->HSV.H = 0.0; 259 | } 260 | 261 | static void color_RGB_to_YUV(struct color *c, uint8_t extra) 262 | { 263 | double R, G, B; 264 | double const *mat; 265 | 266 | assert(c != NULL); 267 | assert(c->type == COLOR_RGB); 268 | 269 | R = c->RGB.R; 270 | G = c->RGB.G; 271 | B = c->RGB.B; 272 | 273 | mat = rgb_to_yuv[extra & COLOR_YUV_MAT_MASK]; 274 | 275 | c->YUV.Y = R * mat[0] + G * mat[1] + B * mat[2]; 276 | c->YUV.U = R * mat[3] + G * mat[4] + B * 0.436; 277 | c->YUV.V = R * 0.615 + G * mat[5] + B * mat[6]; 278 | 279 | c->type = COLOR_YUV; 280 | c->extra = extra; 281 | } 282 | 283 | static void color_RGB_to_YDbDr(struct color *c, uint8_t extra) 284 | { 285 | double R, G, B; 286 | 287 | assert(c != NULL); 288 | assert(c->type == COLOR_RGB); 289 | 290 | R = c->RGB.R; 291 | G = c->RGB.G; 292 | B = c->RGB.B; 293 | 294 | c->YDbDr.Y = R * (299.0/1000.0) + G * (587.0/1000.0) + B * (57.0/500.0); 295 | c->YDbDr.Db = R * (-398567.0/886000.0) + G * (-782471.0/886000.0) + B * (1333.0/1000.0); 296 | c->YDbDr.Dr = R * (1333.0/1000.0) + G * (-782471.0/701000.0) + B * (-75981.0/350500.0); 297 | c->type = COLOR_YDBDR; 298 | } 299 | 300 | static void color_RGB_to_YIQ(struct color *c, uint8_t extra) 301 | { 302 | double R, G, B; 303 | 304 | assert(c != NULL); 305 | assert(c->type == COLOR_RGB); 306 | 307 | R = c->RGB.R; 308 | G = c->RGB.G; 309 | B = c->RGB.B; 310 | 311 | c->YIQ.Y = R * 0.299 + G * 0.587 + B * 0.114; 312 | c->YIQ.I = R * 0.5957 + G * -0.2744766323826577035751015648 + B * -0.3212233676173422964248984352; 313 | c->YIQ.Q = R * -0.2114956266791979792324116478 + G * 0.5226 + B * -0.3111043733208020207675883522; 314 | c->type = COLOR_YIQ; 315 | } 316 | 317 | static uint8_t linear_to_rgb8(double c) 318 | { 319 | if(c <= 0.0) 320 | { 321 | return 0; 322 | } 323 | 324 | if(c <= 0.0031308) 325 | { 326 | return (uint8_t)(int)(c * 3294.6 + 0.5); 327 | } 328 | 329 | if(c < 1.0) 330 | { 331 | return (uint8_t)(int)(pow(c, 1.0 / 2.4) * 269.025 - (14.025 - 0.5)); 332 | } 333 | 334 | return 255; 335 | } 336 | 337 | static void color_LinearRGB_extract(double *dst, struct color const *src) 338 | { 339 | assert(dst != NULL); 340 | assert(src != NULL); 341 | assert(src->type == COLOR_LINEAR_RGB); 342 | 343 | dst[0] = src->LinearRGB.R; 344 | dst[1] = src->LinearRGB.G; 345 | dst[2] = src->LinearRGB.B; 346 | } 347 | 348 | static void color_LinearRGB_to_RGB8(struct color *c, uint8_t extra) 349 | { 350 | uint8_t R, G, B; 351 | 352 | assert(c != NULL); 353 | assert(c->type == COLOR_LINEAR_RGB); 354 | 355 | R = linear_to_rgb8(c->LinearRGB.R); 356 | G = linear_to_rgb8(c->LinearRGB.G); 357 | B = linear_to_rgb8(c->LinearRGB.B); 358 | 359 | c->RGB8.R = R; 360 | c->RGB8.G = G; 361 | c->RGB8.B = B; 362 | c->type = COLOR_RGB8; 363 | } 364 | 365 | static double linear_to_rgb(double c) 366 | { 367 | return c > 0.0031308 ? pow(c, 1.0 / 2.4) * 1.055 - 0.055 : c * 12.92; 368 | } 369 | 370 | static void color_LinearRGB_to_RGB(struct color *c, uint8_t extra) 371 | { 372 | double R, G, B; 373 | 374 | assert(c != NULL); 375 | assert(c->type == COLOR_LINEAR_RGB); 376 | 377 | R = linear_to_rgb(c->LinearRGB.R); 378 | G = linear_to_rgb(c->LinearRGB.G); 379 | B = linear_to_rgb(c->LinearRGB.B); 380 | 381 | c->RGB.R = R; 382 | c->RGB.G = G; 383 | c->RGB.B = B; 384 | c->type = COLOR_RGB; 385 | } 386 | 387 | static void color_LinearRGB_to_XYZ(struct color *c, uint8_t extra) 388 | { 389 | double R, G, B; 390 | 391 | assert(c != NULL); 392 | assert(c->type == COLOR_LINEAR_RGB); 393 | 394 | R = c->LinearRGB.R; 395 | G = c->LinearRGB.G; 396 | B = c->LinearRGB.B; 397 | 398 | c->XYZ.X = R * (5067776.0/12288897.0) + G * (4394405.0/12288897.0) + B * (4435075.0/24577794.0); 399 | c->XYZ.Y = R * (871024.0/4096299.0) + G * (8788810.0/12288897.0) + B * (887015.0/12288897.0); 400 | c->XYZ.Z = R * (79184.0/4096299.0) + G * (4394405.0/36866691.0) + B * (70074185.0/73733382.0); 401 | c->type = COLOR_XYZ; 402 | } 403 | 404 | static double xyz_to_lab(double c) 405 | { 406 | return c > 216.0 / 24389.0 ? pow(c, 1.0 / 3.0) : c * (841.0/108.0) + (4.0/29.0); 407 | } 408 | 409 | static void color_LinearRGB_to_Lab(struct color *c, uint8_t extra) 410 | { 411 | double R, G, B, X, Y, Z; 412 | 413 | assert(c != NULL); 414 | assert(c->type == COLOR_LINEAR_RGB); 415 | 416 | R = c->LinearRGB.R; 417 | G = c->LinearRGB.G; 418 | B = c->LinearRGB.B; 419 | 420 | // linear sRGB -> normalized XYZ (X,Y,Z are all in 0...1) 421 | 422 | X = xyz_to_lab(R * (10135552.0/23359437.0) + G * (8788810.0/23359437.0) + B * (4435075.0/23359437.0)); 423 | Y = xyz_to_lab(R * (871024.0/4096299.0) + G * (8788810.0/12288897.0) + B * (887015.0/12288897.0)); 424 | Z = xyz_to_lab(R * (158368.0/8920923.0) + G * (8788810.0/80288307.0) + B * (70074185.0/80288307.0)); 425 | 426 | // normalized XYZ -> Lab 427 | 428 | c->Lab.L = Y * 116.0 - 16.0; 429 | c->Lab.a = (X - Y) * 500.0; 430 | c->Lab.b = (Y - Z) * 200.0; 431 | c->type = COLOR_LAB; 432 | } 433 | 434 | static void color_HSL_extract(double *dst, struct color const *src) 435 | { 436 | assert(dst != NULL); 437 | assert(src != NULL); 438 | assert(src->type == COLOR_HSL); 439 | 440 | dst[0] = src->HSL.H; 441 | dst[1] = src->HSL.S; 442 | dst[2] = src->HSL.L; 443 | } 444 | 445 | static void finish_HSL_to_RGB(struct color *c, double h, double C, double m) 446 | { 447 | static const char rgb_tbl[][3] = 448 | { 449 | { 0, 2, 1 }, 450 | { 2, 0, 1 }, 451 | { 1, 0, 2 }, 452 | { 1, 2, 0 }, 453 | { 2, 1, 0 }, 454 | { 0, 1, 2 } 455 | }; 456 | 457 | double absh, h2, vars[3]; 458 | int idx; 459 | 460 | // clamps hue to [0, 2), and returns (1.0 - fabs(hue - 1.0)) 461 | 462 | absh = fabs(h); 463 | 464 | h2 = 465 | absh >= 2.0 ? floor(h * 0.5) * -2.0 + h - 1.0 : 466 | h < 0.0 ? h + 1.0 : 467 | h - 1.0; 468 | h2 = 1.0 - fabs(h2); 469 | 470 | // clamps hue to [0, 6), for indexing into rgb_tbl. 471 | 472 | idx = (int) 473 | (absh >= 6.0 ? floor(h * (1.0 / 6.0)) * -6.0 + h : 474 | h < 0.0 ? h + 6.0 : 475 | h); 476 | 477 | assert(idx >= 0 && idx <= 5); 478 | 479 | // finish HSL->RGB. 480 | 481 | vars[0] = C + m; 482 | vars[1] = m; 483 | vars[2] = C * h2 + m; 484 | 485 | c->RGB.R = vars[rgb_tbl[idx][0]]; 486 | c->RGB.G = vars[rgb_tbl[idx][1]]; 487 | c->RGB.B = vars[rgb_tbl[idx][2]]; 488 | } 489 | 490 | static void color_HSL_to_RGB(struct color *c, uint8_t extra) 491 | { 492 | double H, S, L, C, m; 493 | 494 | assert(c != NULL); 495 | assert(c->type == COLOR_HSL); 496 | 497 | H = c->HSL.H; 498 | S = c->HSL.S; 499 | L = c->HSL.L; 500 | 501 | c->type = COLOR_RGB; 502 | 503 | if(fabs(S) > 0.0) 504 | { 505 | C = (1.0 - fabs(L * 2.0 - 1.0)) * S; 506 | m = C * -0.5 + L; 507 | 508 | finish_HSL_to_RGB(c, H, C, m); 509 | return; 510 | } 511 | 512 | c->RGB.R = L; c->RGB.G = L; c->RGB.B = L; 513 | } 514 | 515 | static void color_HSV_extract(double *dst, struct color const *src) 516 | { 517 | assert(dst != NULL); 518 | assert(src != NULL); 519 | assert(src->type == COLOR_HSV); 520 | 521 | dst[0] = src->HSV.H; 522 | dst[1] = src->HSV.S; 523 | dst[2] = src->HSV.V; 524 | } 525 | 526 | static void color_HSV_to_RGB(struct color *c, uint8_t extra) 527 | { 528 | double H, S, V, C, m; 529 | 530 | assert(c != NULL); 531 | assert(c->type == COLOR_HSV); 532 | 533 | H = c->HSV.H; 534 | S = c->HSV.S; 535 | V = c->HSV.V; 536 | 537 | c->type = COLOR_RGB; 538 | 539 | if(fabs(S) > 0.0) 540 | { 541 | C = V * S; 542 | m = V - C; 543 | 544 | finish_HSL_to_RGB(c, H, C, m); 545 | return; 546 | } 547 | 548 | c->RGB.R = V; c->RGB.G = V; c->RGB.B = V; 549 | } 550 | 551 | static void color_YUV_extract(double *dst, struct color const *src) 552 | { 553 | assert(dst != NULL); 554 | assert(src != NULL); 555 | assert(src->type == COLOR_YUV); 556 | 557 | dst[0] = src->YUV.Y; 558 | dst[1] = src->YUV.U; 559 | dst[2] = src->YUV.V; 560 | } 561 | 562 | static void color_YUV_to_RGB(struct color *c, uint8_t extra) 563 | { 564 | double Y, U, V; 565 | double const *mat; 566 | 567 | assert(c != NULL); 568 | assert(c->type == COLOR_YUV); 569 | 570 | Y = c->YUV.Y; 571 | U = c->YUV.U; 572 | V = c->YUV.V; 573 | 574 | mat = yuv_to_rgb[c->extra & COLOR_YUV_MAT_MASK]; 575 | 576 | c->RGB.R = Y + V * mat[0]; 577 | c->RGB.G = Y + U * mat[1] + V * mat[2]; 578 | c->RGB.B = Y + U * mat[3]; 579 | c->type = COLOR_RGB; 580 | c->extra = 0; 581 | } 582 | 583 | static void color_YUV_to_YUV(struct color *c, uint8_t extra) 584 | { 585 | assert(c != NULL); 586 | assert(c->type == COLOR_YUV); 587 | assert(c->extra != extra); 588 | 589 | color_YUV_to_RGB(c, extra); 590 | color_RGB_to_YUV(c, extra); 591 | 592 | assert(c->extra == extra); 593 | } 594 | 595 | static void color_YUV_to_YCbCr(struct color *c, uint8_t extra) 596 | { 597 | double Y, U, V; 598 | 599 | assert(c != NULL); 600 | assert(c->type == COLOR_YUV); 601 | 602 | if((c->extra & COLOR_YUV_MAT_MASK) != (extra & COLOR_YUV_MAT_MASK)) 603 | { 604 | color_YUV_to_YUV(c, extra); 605 | } 606 | 607 | Y = c->YUV.Y; 608 | U = c->YUV.U; 609 | V = c->YUV.V; 610 | 611 | if(extra & COLOR_YCBCR_FULL_RANGE) 612 | { 613 | Y = Y * 255.0 + 0.5; 614 | U = U * (31875.0/109.0) + 128; 615 | V = V * (8500.0/41.0) + 128; 616 | } 617 | else 618 | { 619 | Y = Y * 219.0 + 16.5; 620 | U = U * (28000.0/109.0) + 144; 621 | V = V * (22400.0/123.0) + 144; 622 | } 623 | 624 | c->YCbCr.Y = Y < 0.0 ? 0 : Y > 255.0 ? 255 : (int)Y; 625 | c->YCbCr.Cb = U < 0.0 ? 0 : U > 255.0 ? 255 : (int)U; 626 | c->YCbCr.Cr = V < 0.0 ? 0 : V > 255.0 ? 255 : (int)V; 627 | 628 | c->type = COLOR_YCBCR; 629 | c->extra = extra; 630 | } 631 | 632 | static void color_YCbCr_extract(double *dst, struct color const *src) 633 | { 634 | assert(dst != NULL); 635 | assert(src != NULL); 636 | assert(src->type == COLOR_YCBCR); 637 | 638 | dst[0] = src->YCbCr.Y; 639 | dst[1] = src->YCbCr.Cb; 640 | dst[2] = src->YCbCr.Cr; 641 | } 642 | 643 | static void color_YCbCr_to_YUV(struct color *c, uint8_t extra) 644 | { 645 | double Y, U, V; 646 | 647 | assert(c != NULL); 648 | assert(c->type == COLOR_YCBCR); 649 | 650 | Y = c->YCbCr.Y; 651 | U = c->YCbCr.Cb; 652 | V = c->YCbCr.Cr; 653 | 654 | if(c->extra & COLOR_YCBCR_FULL_RANGE) 655 | { 656 | Y *= (1.0 / 255.0); 657 | U = U * (109.0/31875.0) - 0.436; 658 | V = V * (41.0/8500.0) - 0.615; 659 | } 660 | else 661 | { 662 | Y = Y * (1.0/219.0) - (16.0/219.0); 663 | U = U * (109.0/28000.0) - 0.558625; 664 | V = V * (123.0/22400.0) - 0.78796875; 665 | } 666 | 667 | c->YUV.Y = Y; 668 | c->YUV.U = U; 669 | c->YUV.V = V; 670 | c->type = COLOR_YUV; 671 | c->extra &= ~(uint8_t)COLOR_YCBCR_FULL_RANGE; 672 | 673 | if((c->extra & COLOR_YUV_MAT_MASK) != (extra & COLOR_YUV_MAT_MASK)) 674 | { 675 | color_YUV_to_YUV(c, extra); 676 | } 677 | } 678 | 679 | static void color_YCbCr_to_YCbCr(struct color *c, uint8_t extra) 680 | { 681 | assert(c != NULL); 682 | assert(c->type == COLOR_YCBCR); 683 | assert(c->extra != extra); 684 | 685 | color_YCbCr_to_YUV(c, extra); 686 | color_YUV_to_YCbCr(c, extra); 687 | 688 | assert(c->extra == extra); 689 | } 690 | 691 | static void color_YDbDr_extract(double *dst, struct color const *src) 692 | { 693 | assert(dst != NULL); 694 | assert(src != NULL); 695 | assert(src->type == COLOR_YDBDR); 696 | 697 | dst[0] = src->YDbDr.Y; 698 | dst[1] = src->YDbDr.Db; 699 | dst[2] = src->YDbDr.Dr; 700 | } 701 | 702 | static void color_YDbDr_to_RGB(struct color *c, uint8_t extra) 703 | { 704 | double Y, Db, Dr; 705 | 706 | assert(c != NULL); 707 | assert(c->type == COLOR_YDBDR); 708 | 709 | Y = c->YDbDr.Y; 710 | Db = c->YDbDr.Db; 711 | Dr = c->YDbDr.Dr; 712 | 713 | c->RGB.R = Y + Dr * (701.0/1333.0); 714 | c->RGB.G = Y + Db * (-101004.0/782471.0) + Dr * (-209599.0/782471.0); 715 | c->RGB.B = Y + Db * (886.0/1333.0); 716 | c->type = COLOR_RGB; 717 | } 718 | 719 | static void color_YDbDr_to_YIQ(struct color *c, uint8_t extra) 720 | { 721 | double Y, Db, Dr; 722 | 723 | assert(c != NULL); 724 | assert(c->type == COLOR_YDBDR); 725 | 726 | Y = c->YDbDr.Y; 727 | Db = c->YDbDr.Db; 728 | Dr = c->YDbDr.Dr; 729 | 730 | c->YUV.Y = Y; 731 | c->YUV.U = Db * -1.780759334211551067290090872e-1 + Dr * 3.867911188667345780375729105e-1; 732 | c->YUV.V = Y * 3.155443620884047221646914261e-30 + Db * -2.742395246410785275938007739e-1 + Dr * -2.512094867865302853146089398e-1; 733 | c->type = COLOR_YIQ; 734 | } 735 | 736 | static void color_YIQ_extract(double *dst, struct color const *src) 737 | { 738 | assert(dst != NULL); 739 | assert(src != NULL); 740 | assert(src->type == COLOR_YIQ); 741 | 742 | dst[0] = src->YIQ.Y; 743 | dst[1] = src->YIQ.I; 744 | dst[2] = src->YIQ.Q; 745 | } 746 | 747 | static void color_YIQ_to_RGB(struct color *c, uint8_t extra) 748 | { 749 | double Y, I, Q; 750 | 751 | assert(c != NULL); 752 | assert(c->type == COLOR_YIQ); 753 | 754 | Y = c->YIQ.Y; 755 | I = c->YIQ.I; 756 | Q = c->YIQ.Q; 757 | 758 | c->RGB.R = Y + I * 9.563000521420394701478042310e-1 + Q * -6.209682015704038246103012680e-1; 759 | c->RGB.G = Y + I * -2.720883840788609953919979558e-1 + Q * 6.473748500336683799608873068e-1; 760 | c->RGB.B = Y + I * -1.107173983650687695430619869e0 + Q * -1.704732848247478907706673421e0; 761 | c->type = COLOR_RGB; 762 | } 763 | 764 | static void color_YIQ_to_YDbDr(struct color *c, uint8_t extra) 765 | { 766 | double Y, I, Q; 767 | 768 | assert(c != NULL); 769 | assert(c->type == COLOR_YIQ); 770 | 771 | Y = c->YIQ.Y; 772 | I = c->YIQ.I; 773 | Q = c->YIQ.Q; 774 | 775 | c->YDbDr.Y = Y + I * 6.310887241768094443293828522e-30; 776 | c->YDbDr.Db = I * -1.665759503618924038384894227e0 + Q * -2.564795583198520749405186986e0; 777 | c->YDbDr.Dr = Y * 1.009741958682895110927012564e-28 + I * 1.818470712561110718554954408e0 + Q * -1.180813998136017543802470171e0; 778 | c->type = COLOR_YDBDR; 779 | } 780 | 781 | static void color_XYZ_extract(double *dst, struct color const *src) 782 | { 783 | assert(dst != NULL); 784 | assert(src != NULL); 785 | assert(src->type == COLOR_XYZ); 786 | 787 | dst[0] = src->XYZ.X; 788 | dst[1] = src->XYZ.Y; 789 | dst[2] = src->XYZ.Z; 790 | } 791 | 792 | static void color_XYZ_to_LinearRGB(struct color *c, uint8_t extra) 793 | { 794 | double X, Y, Z; 795 | 796 | assert(c != NULL); 797 | assert(c->type == COLOR_XYZ); 798 | 799 | X = c->XYZ.X; 800 | Y = c->XYZ.Y; 801 | Z = c->XYZ.Z; 802 | 803 | c->LinearRGB.R = X * (641589.0/197960.0) + Y * (-608687.0/395920.0) + Z * (-49353.0/98980.0); 804 | c->LinearRGB.G = X * (-42591639.0/43944050.0) + Y * (82435961.0/43944050.0) + Z * (1826061.0/43944050.0); 805 | c->LinearRGB.B = X * (49353.0/887015.0) + Y * (-180961.0/887015.0) + Z * (49353.0/46685.0); 806 | c->type = COLOR_LINEAR_RGB; 807 | } 808 | 809 | static void color_XYZ_to_xyY(struct color *c, uint8_t extra) 810 | { 811 | double X, Y, div; 812 | 813 | assert(c != NULL); 814 | assert(c->type == COLOR_XYZ); 815 | 816 | X = c->XYZ.X; 817 | Y = c->XYZ.Y; 818 | 819 | div = X + Y + c->XYZ.Z; 820 | 821 | c->xyY.Y = Y; 822 | c->type = COLOR_XYY; 823 | 824 | if(fabs(div) > 0.0) 825 | { 826 | c->xyY.x = X / div; 827 | c->xyY.y = Y / div; 828 | 829 | return; 830 | } 831 | 832 | c->xyY.x = X; 833 | c->xyY.y = Y; 834 | } 835 | 836 | static void color_XYZ_to_Lab(struct color *c, uint8_t extra) 837 | { 838 | double X, Y, Z; 839 | 840 | assert(c != NULL); 841 | assert(c->type == COLOR_XYZ); 842 | 843 | X = c->XYZ.X; 844 | Y = c->XYZ.Y; 845 | Z = c->XYZ.Z; 846 | 847 | X = X > 3377268.0/401223439.0 ? pow(X * COLOR_REF_Xr, 1.0/3.0) : X * (13835291.0/1688634.0) + (4.0/29.0); 848 | Y = Y > 216.0/24389.0 ? pow(Y, 1.0/3.0) : Y * (841.0/108.0) + (4.0/29.0); 849 | Z = Z > 3869316.0/401223439.0 ? pow(Z * COLOR_REF_Zr, 1.0/3.0) : Z * (13835291.0/1934658.0) + (4.0/29.0); 850 | 851 | c->Lab.L = Y * 116.0 - 16.0; 852 | c->Lab.a = (X - Y) * 500.0; 853 | c->Lab.b = (Y - Z) * 200.0; 854 | 855 | c->type = COLOR_LAB; 856 | } 857 | 858 | static void color_XYZ_to_Luv(struct color *c, uint8_t extra) 859 | { 860 | double X, Y, div, L; 861 | 862 | assert(c != NULL); 863 | assert(c->type == COLOR_XYZ); 864 | 865 | X = c->XYZ.X; 866 | Y = c->XYZ.Y; 867 | 868 | div = X + Y * 15.0 + c->XYZ.Z * 3.0; 869 | L = Y > 216.0/24389.0 ? pow(Y, 1.0 / 3.0) * 116.0 - 16.0 : Y * (24389.0/27.0); 870 | 871 | if(fabs(div) > 0.0) 872 | { 873 | div = 1.0 / div; 874 | X *= div; 875 | Y *= div; 876 | } 877 | 878 | c->Luv.L = L; 879 | c->Luv.u = (X * 52.0 - COLOR_REF_U13) * L; 880 | c->Luv.v = (Y * 117.0 - COLOR_REF_V13) * L; 881 | c->type = COLOR_LUV; 882 | } 883 | 884 | static void color_xyY_extract(double *dst, struct color const *src) 885 | { 886 | assert(dst != NULL); 887 | assert(src != NULL); 888 | assert(src->type == COLOR_XYY); 889 | 890 | dst[0] = src->xyY.x; 891 | dst[1] = src->xyY.y; 892 | dst[2] = src->xyY.Y; 893 | } 894 | 895 | static void color_xyY_to_XYZ(struct color *c, uint8_t extra) 896 | { 897 | double x, y, Y; 898 | 899 | assert(c != NULL); 900 | assert(c->type == COLOR_XYY); 901 | 902 | x = c->xyY.x; 903 | y = c->xyY.y; 904 | Y = c->xyY.Y; 905 | 906 | c->type = COLOR_XYZ; 907 | 908 | if(fabs(y) > 0.0) 909 | { 910 | double mul = Y / y; 911 | 912 | c->XYZ.X = x * mul; 913 | c->XYZ.Y = Y; 914 | c->XYZ.Z = (1.0 - x - y) * mul; 915 | 916 | return; 917 | } 918 | 919 | c->XYZ.X = 0.0; 920 | c->XYZ.Y = 0.0; 921 | c->XYZ.Z = 0.0; 922 | } 923 | 924 | static void color_Lab_extract(double *dst, struct color const *src) 925 | { 926 | assert(dst != NULL); 927 | assert(src != NULL); 928 | assert(src->type == COLOR_LAB); 929 | 930 | dst[0] = src->Lab.L; 931 | dst[1] = src->Lab.a; 932 | dst[2] = src->Lab.b; 933 | } 934 | 935 | static void color_Lab_to_LinearRGB(struct color *c, uint8_t extra) 936 | { 937 | double X, Y, Z; 938 | 939 | assert(c != NULL); 940 | assert(c->type == COLOR_LAB); 941 | 942 | // Lab -> normalized XYZ (X,Y,Z are all in 0...1) 943 | 944 | Y = c->Lab.L * (1.0/116.0) + 16.0/116.0; 945 | X = c->Lab.a * (1.0/500.0) + Y; 946 | Z = c->Lab.b * (-1.0/200.0) + Y; 947 | 948 | X = X > 6.0/29.0 ? X * X * X : X * (108.0/841.0) - 432.0/24389.0; 949 | Y = c->Lab.L > 8.0 ? Y * Y * Y : c->Lab.L * (27.0/24389.0); 950 | Z = Z > 6.0/29.0 ? Z * Z * Z : Z * (108.0/841.0) - 432.0/24389.0; 951 | 952 | // normalized XYZ -> linear sRGB 953 | 954 | c->LinearRGB.R = X * (1219569.0/395920.0) + Y * (-608687.0/395920.0) + Z * (-107481.0/197960.0); 955 | c->LinearRGB.G = X * (-80960619.0/87888100.0) + Y * (82435961.0/43944050.0) + Z * (3976797.0/87888100.0); 956 | c->LinearRGB.B = X * (93813.0/1774030.0) + Y * (-180961.0/887015.0) + Z * (107481.0/93370.0); 957 | c->type = COLOR_LINEAR_RGB; 958 | } 959 | 960 | static void color_Lab_to_XYZ(struct color *c, uint8_t extra) 961 | { 962 | double L, X, Y, Z; 963 | 964 | assert(c != NULL); 965 | assert(c->type == COLOR_LAB); 966 | 967 | L = c->Lab.L; 968 | 969 | Y = L * (1.0/116.0) + 16.0/116.0; 970 | X = c->Lab.a * (1.0/500.0) + Y; 971 | Z = c->Lab.b * (-1.0/200.0) + Y; 972 | 973 | c->XYZ.X = X > 6.0/29.0 ? X * X * X * COLOR_REF_X : X * (1688634.0/13835291.0) - 6754536.0/401223439.0; 974 | c->XYZ.Y = L > 8.0 ? Y * Y * Y : L * (27.0/24389.0); 975 | c->XYZ.Z = Z > 6.0/29.0 ? Z * Z * Z * COLOR_REF_Z : Z * (1934658.0/13835291.0) - 7738632.0/401223439.0; 976 | 977 | c->type = COLOR_XYZ; 978 | } 979 | 980 | static void color_Lab_to_LCHab(struct color *c, uint8_t extra) 981 | { 982 | double L, a, b; 983 | 984 | assert(c != NULL); 985 | assert(c->type == COLOR_LAB); 986 | 987 | L = c->Lab.L; 988 | a = c->Lab.a; 989 | b = c->Lab.b; 990 | 991 | c->LCHab.L = L; 992 | c->LCHab.C = sqrt(a * a + b * b); 993 | c->LCHab.h = atan2(b, a); 994 | c->type = COLOR_LCHAB; 995 | } 996 | 997 | static void color_LCHab_extract(double *dst, struct color const *src) 998 | { 999 | assert(dst != NULL); 1000 | assert(src != NULL); 1001 | assert(src->type == COLOR_LCHAB); 1002 | 1003 | dst[0] = src->LCHab.L; 1004 | dst[1] = src->LCHab.C; 1005 | dst[2] = src->LCHab.h; 1006 | } 1007 | 1008 | static void color_LCHab_to_Lab(struct color *c, uint8_t extra) 1009 | { 1010 | double L, C, h; 1011 | 1012 | assert(c != NULL); 1013 | assert(c->type == COLOR_LCHAB); 1014 | 1015 | L = c->LCHab.L; 1016 | C = c->LCHab.C; 1017 | h = c->LCHab.h; 1018 | 1019 | c->Lab.L = L; 1020 | c->Lab.a = cos(h) * C; 1021 | c->Lab.b = sin(h) * C; 1022 | c->type = COLOR_LAB; 1023 | } 1024 | 1025 | static void color_Luv_extract(double *dst, struct color const *src) 1026 | { 1027 | assert(dst != NULL); 1028 | assert(src != NULL); 1029 | assert(src->type == COLOR_LUV); 1030 | 1031 | dst[0] = src->Luv.L; 1032 | dst[1] = src->Luv.u; 1033 | dst[2] = src->Luv.v; 1034 | } 1035 | 1036 | static void color_Luv_to_XYZ(struct color *c, uint8_t extra) 1037 | { 1038 | double L, u, v, y, a, b, cc, x, z; 1039 | 1040 | assert(c != NULL); 1041 | assert(c->type == COLOR_LUV); 1042 | 1043 | L = c->Luv.L; 1044 | u = c->Luv.u; 1045 | v = c->Luv.v; 1046 | 1047 | if(L > 8.0) 1048 | { 1049 | y = L * (1.0 / 116.0) + 16.0 / 116.0; 1050 | y = y * y * y; 1051 | } 1052 | else 1053 | { 1054 | y = L * (27.0 / 24389.0); 1055 | } 1056 | 1057 | a = L / (L * COLOR_REF_U13 + u) * (52.0 / 3.0) - 1.0 / 3.0; 1058 | b = 5.0 * y; 1059 | cc = (L / (L * COLOR_REF_V13 + v) * 39.0 - 5.0) * y; 1060 | 1061 | x = (cc + b) / (a + 1.0 / 3.0); 1062 | z = x * a - b; 1063 | 1064 | c->XYZ.X = x; 1065 | c->XYZ.Y = y; 1066 | c->XYZ.Z = z; 1067 | c->type = COLOR_XYZ; 1068 | } 1069 | 1070 | static void color_Luv_to_LCHuv(struct color *c, uint8_t extra) 1071 | { 1072 | assert(c != NULL); 1073 | assert(c->type == COLOR_LUV); 1074 | 1075 | c->type = COLOR_LAB; 1076 | color_Lab_to_LCHab(c, extra); 1077 | c->type = COLOR_LCHUV; 1078 | } 1079 | 1080 | static void color_LCHuv_extract(double *dst, struct color const *src) 1081 | { 1082 | assert(dst != NULL); 1083 | assert(src != NULL); 1084 | assert(src->type == COLOR_LCHUV); 1085 | 1086 | dst[0] = src->LCHuv.L; 1087 | dst[1] = src->LCHuv.C; 1088 | dst[2] = src->LCHuv.h; 1089 | } 1090 | 1091 | static void color_LCHuv_to_Luv(struct color *c, uint8_t extra) 1092 | { 1093 | assert(c != NULL); 1094 | assert(c->type == COLOR_LCHUV); 1095 | 1096 | c->type = COLOR_LCHAB; 1097 | color_LCHab_to_Lab(c, extra); 1098 | c->type = COLOR_LUV; 1099 | } 1100 | 1101 | static void color_LCHuv_to_LSHuv(struct color *c, uint8_t extra) 1102 | { 1103 | double L, C, h; 1104 | 1105 | assert(c != NULL); 1106 | assert(c->type == COLOR_LCHUV); 1107 | 1108 | L = c->LCHuv.L; 1109 | C = c->LCHuv.C; 1110 | h = c->LCHuv.h; 1111 | 1112 | c->LSHuv.L = L; 1113 | c->LSHuv.S = C / L; 1114 | c->LSHuv.h = h; 1115 | c->type = COLOR_LSHUV; 1116 | } 1117 | 1118 | static void color_LSHuv_extract(double *dst, struct color const *src) 1119 | { 1120 | assert(dst != NULL); 1121 | assert(src != NULL); 1122 | assert(src->type == COLOR_LSHUV); 1123 | 1124 | dst[0] = src->LSHuv.L; 1125 | dst[1] = src->LSHuv.S; 1126 | dst[2] = src->LSHuv.h; 1127 | } 1128 | 1129 | static void color_LSHuv_to_LCHuv(struct color *c, uint8_t extra) 1130 | { 1131 | double L, S, h; 1132 | 1133 | assert(c != NULL); 1134 | assert(c->type == COLOR_LSHUV); 1135 | 1136 | L = c->LSHuv.L; 1137 | S = c->LSHuv.S; 1138 | h = c->LSHuv.h; 1139 | 1140 | c->LCHuv.L = L; 1141 | c->LCHuv.C = S * L; 1142 | c->LCHuv.h = h; 1143 | c->type = COLOR_LCHUV; 1144 | } 1145 | 1146 | typedef void (*conversion_func)(struct color*, uint8_t); 1147 | 1148 | static struct color_descriptor 1149 | { 1150 | char const *name; 1151 | void (*extract)(double*,struct color const*); 1152 | conversion_func conversions[COLOR_DUMMY_END - 1]; 1153 | uint8_t proxy_conversions[COLOR_DUMMY_END - 1]; 1154 | } const g_descriptors[] = 1155 | { 1156 | { 1157 | // RGB8 1158 | "RGB8", 1159 | color_RGB8_extract, 1160 | { 1161 | NULL, // RGB8 1162 | color_RGB8_to_RGB, // RGB 1163 | color_RGB8_to_LinearRGB // Linear RGB 1164 | }, 1165 | { 1166 | COLOR_NONE, // RGB8 1167 | COLOR_NONE, // RGB 1168 | COLOR_NONE, // Linear RGB 1169 | COLOR_RGB, // HSL 1170 | COLOR_RGB, // HSV 1171 | COLOR_RGB, // YUV 1172 | COLOR_RGB, // YCbCr 1173 | COLOR_RGB, // YDbDr 1174 | COLOR_RGB, // YIQ 1175 | COLOR_LINEAR_RGB, // XYZ 1176 | COLOR_LINEAR_RGB, // xyY 1177 | COLOR_LINEAR_RGB, // Lab 1178 | COLOR_LINEAR_RGB, // Luv 1179 | COLOR_LINEAR_RGB, // LCHab 1180 | COLOR_LINEAR_RGB, // LCHuv 1181 | COLOR_LINEAR_RGB // LSHuv 1182 | } 1183 | }, 1184 | { 1185 | // RGB 1186 | "RGB", 1187 | color_RGB_extract, 1188 | { 1189 | color_RGB_to_RGB8, // RGB8 1190 | NULL, // RGB 1191 | color_RGB_to_LinearRGB, // Linear RGB 1192 | color_RGB_to_HSL, // HSL 1193 | color_RGB_to_HSV, // HSV 1194 | color_RGB_to_YUV, // YUV 1195 | NULL, // YCbCr 1196 | color_RGB_to_YDbDr, // YDbDr 1197 | color_RGB_to_YIQ 1198 | }, 1199 | { 1200 | COLOR_NONE, // RGB8 1201 | COLOR_NONE, // RGB 1202 | COLOR_NONE, // Linear RGB 1203 | COLOR_NONE, // HSL 1204 | COLOR_NONE, // HSV 1205 | COLOR_NONE, // YUV 1206 | COLOR_YUV, // YCbCr 1207 | COLOR_NONE, // YDbDr 1208 | COLOR_NONE, // YIQ 1209 | COLOR_LINEAR_RGB, // XYZ 1210 | COLOR_LINEAR_RGB, // xyY 1211 | COLOR_LINEAR_RGB, // Lab 1212 | COLOR_LINEAR_RGB, // Luv 1213 | COLOR_LINEAR_RGB, // LCHab 1214 | COLOR_LINEAR_RGB, // LCHuv 1215 | COLOR_LINEAR_RGB // LSHuv 1216 | } 1217 | }, 1218 | { 1219 | // Linear RGB 1220 | "Linear RGB", 1221 | color_LinearRGB_extract, 1222 | { 1223 | color_LinearRGB_to_RGB8, // RGB8 1224 | color_LinearRGB_to_RGB, // RGB 1225 | NULL, // Linear RGB 1226 | NULL, // HSL 1227 | NULL, // HSV 1228 | NULL, // YUV 1229 | NULL, // YCbCr 1230 | NULL, // YDbDr 1231 | NULL, // YIQ 1232 | color_LinearRGB_to_XYZ, // XYZ 1233 | NULL, // xyY 1234 | color_LinearRGB_to_Lab, // Lab 1235 | }, 1236 | { 1237 | COLOR_NONE, // RGB8 1238 | COLOR_NONE, // RGB 1239 | COLOR_NONE, // Linear RGB 1240 | COLOR_RGB, // HSL 1241 | COLOR_RGB, // HSV 1242 | COLOR_RGB, // YUV 1243 | COLOR_RGB, // YCbCr 1244 | COLOR_RGB, // YDbDr 1245 | COLOR_RGB, // YIQ 1246 | COLOR_NONE, // XYZ 1247 | COLOR_XYZ, // xyY 1248 | COLOR_NONE, // Lab 1249 | COLOR_XYZ, // Luv 1250 | COLOR_LAB, // LCHab 1251 | COLOR_XYZ, // LCHuv 1252 | COLOR_XYZ // LSHuv 1253 | } 1254 | }, 1255 | { 1256 | // HSL 1257 | "HSL", 1258 | color_HSL_extract, 1259 | { 1260 | NULL, // RGB8 1261 | color_HSL_to_RGB // RGB 1262 | }, 1263 | { 1264 | COLOR_RGB, // RGB8 1265 | COLOR_NONE, // RGB 1266 | COLOR_RGB, // Linear RGB 1267 | COLOR_NONE, // HSL 1268 | COLOR_RGB, // HSV 1269 | COLOR_RGB, // YUV 1270 | COLOR_RGB, // YCbCr 1271 | COLOR_RGB, // YDbDr 1272 | COLOR_RGB, // YIQ 1273 | COLOR_RGB, // XYZ 1274 | COLOR_RGB, // xyY 1275 | COLOR_RGB, // Lab 1276 | COLOR_RGB, // Luv 1277 | COLOR_RGB, // LCHab 1278 | COLOR_RGB, // LCHuv 1279 | COLOR_RGB // LSHuv 1280 | } 1281 | }, 1282 | { 1283 | // HSV 1284 | "HSV", 1285 | color_HSV_extract, 1286 | { 1287 | NULL, // RGB8 1288 | color_HSV_to_RGB // RGB 1289 | }, 1290 | { 1291 | COLOR_RGB, // RGB8 1292 | COLOR_NONE, // RGB 1293 | COLOR_RGB, // Linear RGB 1294 | COLOR_RGB, // HSL 1295 | COLOR_NONE, // HSV 1296 | COLOR_RGB, // YUV 1297 | COLOR_RGB, // YCbCr 1298 | COLOR_RGB, // YDbDr 1299 | COLOR_RGB, // YIQ 1300 | COLOR_RGB, // XYZ 1301 | COLOR_RGB, // xyY 1302 | COLOR_RGB, // Lab 1303 | COLOR_RGB, // Luv 1304 | COLOR_RGB, // LCHab 1305 | COLOR_RGB, // LCHuv 1306 | COLOR_RGB // LSHuv 1307 | } 1308 | }, 1309 | { 1310 | // YUV 1311 | "YUV", 1312 | color_YUV_extract, 1313 | { 1314 | NULL, // RGB8 1315 | color_YUV_to_RGB, // RGB 1316 | NULL, // Linear RGB 1317 | NULL, // HSL 1318 | NULL, // HSV 1319 | color_YUV_to_YUV, // YUV 1320 | color_YUV_to_YCbCr // YCbCr 1321 | }, 1322 | { 1323 | COLOR_RGB, // RGB8 1324 | COLOR_NONE, // RGB 1325 | COLOR_RGB, // Linear RGB 1326 | COLOR_RGB, // HSL 1327 | COLOR_RGB, // HSV 1328 | COLOR_NONE, // YUV 1329 | COLOR_NONE, // YCbCr 1330 | COLOR_RGB, // YDbDr 1331 | COLOR_RGB, // YIQ 1332 | COLOR_RGB, // XYZ 1333 | COLOR_RGB, // xyY 1334 | COLOR_RGB, // Lab 1335 | COLOR_RGB, // Luv 1336 | COLOR_RGB, // LCHab 1337 | COLOR_RGB, // LCHuv 1338 | COLOR_RGB // LSHuv 1339 | } 1340 | }, 1341 | { 1342 | // YCbCr 1343 | "YCbCr", 1344 | color_YCbCr_extract, 1345 | { 1346 | NULL, // RGB8 1347 | NULL, // RGB 1348 | NULL, // Linear RGB 1349 | NULL, // HSL 1350 | NULL, // HSV 1351 | color_YCbCr_to_YUV, // YUV 1352 | color_YCbCr_to_YCbCr, // YCbCr 1353 | }, 1354 | { 1355 | COLOR_YUV, // RGB8 1356 | COLOR_YUV, // RGB 1357 | COLOR_YUV, // Linear RGB 1358 | COLOR_YUV, // HSL 1359 | COLOR_YUV, // HSV 1360 | COLOR_NONE, // YUV 1361 | COLOR_NONE, // YCbCr 1362 | COLOR_YUV, // YDbDr 1363 | COLOR_YUV, // YIQ 1364 | COLOR_YUV, // XYZ 1365 | COLOR_YUV, // xyY 1366 | COLOR_YUV, // Lab 1367 | COLOR_YUV, // Luv 1368 | COLOR_YUV, // LCHab 1369 | COLOR_YUV, // LCHuv 1370 | COLOR_YUV // LSHuv 1371 | } 1372 | }, 1373 | { 1374 | // YDbDr 1375 | "YDbDr", 1376 | color_YDbDr_extract, 1377 | { 1378 | NULL, // RGB8 1379 | color_YDbDr_to_RGB, // RGB 1380 | NULL, // Linear RGB 1381 | NULL, // HSL 1382 | NULL, // HSV 1383 | NULL, // YUV 1384 | NULL, // YCbCr 1385 | NULL, // YDbDr 1386 | color_YDbDr_to_YIQ 1387 | }, 1388 | { 1389 | COLOR_RGB, // RGB8 1390 | COLOR_NONE, // RGB 1391 | COLOR_RGB, // Linear RGB 1392 | COLOR_RGB, // HSL 1393 | COLOR_RGB, // HSV 1394 | COLOR_RGB, // YUV 1395 | COLOR_RGB, // YCbCr 1396 | COLOR_NONE, // YDbDr 1397 | COLOR_NONE, // YIQ 1398 | COLOR_RGB, // XYZ 1399 | COLOR_RGB, // xyY 1400 | COLOR_RGB, // Lab 1401 | COLOR_RGB, // Luv 1402 | COLOR_RGB, // LCHab 1403 | COLOR_RGB, // LCHuv 1404 | COLOR_RGB // LSHuv 1405 | } 1406 | }, 1407 | { 1408 | // YIQ 1409 | "YIQ", 1410 | color_YIQ_extract, 1411 | { 1412 | NULL, // RGB8 1413 | color_YIQ_to_RGB, // RGB 1414 | NULL, // Linear RGB 1415 | NULL, // HSL 1416 | NULL, // HSV 1417 | NULL, // YUV 1418 | NULL, // YCbCr 1419 | color_YIQ_to_YDbDr, // YDbDr 1420 | }, 1421 | { 1422 | COLOR_RGB, // RGB8 1423 | COLOR_NONE, // RGB 1424 | COLOR_RGB, // Linear RGB 1425 | COLOR_RGB, // HSL 1426 | COLOR_RGB, // HSV 1427 | COLOR_RGB, // YUV 1428 | COLOR_RGB, // YCbCr 1429 | COLOR_NONE, // YDbDr 1430 | COLOR_NONE, // YIQ 1431 | COLOR_RGB, // XYZ 1432 | COLOR_RGB, // xyY 1433 | COLOR_RGB, // Lab 1434 | COLOR_RGB, // Luv 1435 | COLOR_RGB, // LCHab 1436 | COLOR_RGB, // LCHuv 1437 | COLOR_RGB // LSHuv 1438 | } 1439 | }, 1440 | { 1441 | // XYZ 1442 | "XYZ", 1443 | color_XYZ_extract, 1444 | { 1445 | NULL, // RGB8 1446 | NULL, // RGB 1447 | color_XYZ_to_LinearRGB, // Linear RGB 1448 | NULL, // HSL 1449 | NULL, // HSV 1450 | NULL, // YUV 1451 | NULL, // YCbCr 1452 | NULL, // YDbDr 1453 | NULL, // YIQ 1454 | NULL, // XYZ 1455 | color_XYZ_to_xyY, // xyY 1456 | color_XYZ_to_Lab, // Lab 1457 | color_XYZ_to_Luv, // Luv 1458 | }, 1459 | { 1460 | COLOR_LINEAR_RGB, // RGB8 1461 | COLOR_LINEAR_RGB, // RGB 1462 | COLOR_NONE, // Linear RGB 1463 | COLOR_LINEAR_RGB, // HSL 1464 | COLOR_LINEAR_RGB, // HSV 1465 | COLOR_LINEAR_RGB, // YUV 1466 | COLOR_LINEAR_RGB, // YCbCr 1467 | COLOR_LINEAR_RGB, // YDbDr 1468 | COLOR_LINEAR_RGB, // YIQ 1469 | COLOR_NONE, // XYZ 1470 | COLOR_NONE, // xyY 1471 | COLOR_NONE, // Lab 1472 | COLOR_NONE, // Luv 1473 | COLOR_LAB, // LCHab 1474 | COLOR_LUV, // LCHuv 1475 | COLOR_LUV // LSHuv 1476 | } 1477 | }, 1478 | { 1479 | // xyY 1480 | "xyY", 1481 | color_xyY_extract, 1482 | { 1483 | NULL, // RGB8 1484 | NULL, // RGB 1485 | NULL, // Linear RGB 1486 | NULL, // HSL 1487 | NULL, // HSV 1488 | NULL, // YUV 1489 | NULL, // YCbCr 1490 | NULL, // YDbDr 1491 | NULL, // YIQ 1492 | color_xyY_to_XYZ, // XYZ 1493 | }, 1494 | { 1495 | COLOR_XYZ, // RGB8 1496 | COLOR_XYZ, // RGB 1497 | COLOR_XYZ, // Linear RGB 1498 | COLOR_XYZ, // HSL 1499 | COLOR_XYZ, // HSV 1500 | COLOR_XYZ, // YUV 1501 | COLOR_XYZ, // YCbCr 1502 | COLOR_XYZ, // YDbDr 1503 | COLOR_XYZ, // YIQ 1504 | COLOR_NONE, // XYZ 1505 | COLOR_NONE, // xyY 1506 | COLOR_XYZ, // Lab 1507 | COLOR_XYZ, // Luv 1508 | COLOR_XYZ, // LCHab 1509 | COLOR_XYZ, // LCHuv 1510 | COLOR_XYZ // LSHuv 1511 | } 1512 | }, 1513 | { 1514 | // Lab 1515 | "Lab", 1516 | color_Lab_extract, 1517 | { 1518 | NULL, // RGB8 1519 | NULL, // RGB 1520 | color_Lab_to_LinearRGB, // Linear RGB 1521 | NULL, // HSL 1522 | NULL, // HSV 1523 | NULL, // YUV 1524 | NULL, // YCbCr 1525 | NULL, // YDbDr 1526 | NULL, // YIQ 1527 | color_Lab_to_XYZ, // XYZ 1528 | NULL, // xyY 1529 | NULL, // Lab 1530 | NULL, // Luv 1531 | color_Lab_to_LCHab // LCHab 1532 | }, 1533 | { 1534 | COLOR_LINEAR_RGB, // RGB8 1535 | COLOR_LINEAR_RGB, // RGB 1536 | COLOR_NONE, // Linear RGB 1537 | COLOR_LINEAR_RGB, // HSL 1538 | COLOR_LINEAR_RGB, // HSV 1539 | COLOR_LINEAR_RGB, // YUV 1540 | COLOR_LINEAR_RGB, // YCbCr 1541 | COLOR_LINEAR_RGB, // YDbDr 1542 | COLOR_LINEAR_RGB, // YIQ 1543 | COLOR_NONE, // XYZ 1544 | COLOR_XYZ, // xyY 1545 | COLOR_NONE, // Lab 1546 | COLOR_XYZ, // Luv 1547 | COLOR_NONE, // LCHab 1548 | COLOR_XYZ, // LCHuv 1549 | COLOR_XYZ // LSHuv 1550 | } 1551 | }, 1552 | { 1553 | // Luv 1554 | "Luv", 1555 | color_Luv_extract, 1556 | { 1557 | NULL, // RGB8 1558 | NULL, // RGB 1559 | NULL, // Linear RGB 1560 | NULL, // HSL 1561 | NULL, // HSV 1562 | NULL, // YUV 1563 | NULL, // YCbCr 1564 | NULL, // YDbDr 1565 | NULL, // YIQ 1566 | color_Luv_to_XYZ, // XYZ 1567 | NULL, // xyY 1568 | NULL, // Lab 1569 | NULL, // Luv 1570 | NULL, // LCHab 1571 | color_Luv_to_LCHuv // LCHuv 1572 | }, 1573 | { 1574 | COLOR_XYZ, // RGB8 1575 | COLOR_XYZ, // RGB 1576 | COLOR_XYZ, // Linear RGB 1577 | COLOR_XYZ, // HSL 1578 | COLOR_XYZ, // HSV 1579 | COLOR_XYZ, // YUV 1580 | COLOR_XYZ, // YCbCr 1581 | COLOR_XYZ, // YDbDr 1582 | COLOR_XYZ, // YIQ 1583 | COLOR_NONE, // XYZ 1584 | COLOR_XYZ, // xyY 1585 | COLOR_XYZ, // Lab 1586 | COLOR_NONE, // Luv 1587 | COLOR_XYZ, // LCHab 1588 | COLOR_NONE, // LCHuv 1589 | COLOR_LCHUV // LSHuv 1590 | } 1591 | }, 1592 | { 1593 | // LCHab 1594 | "LCHab", 1595 | color_LCHab_extract, 1596 | { 1597 | NULL, // RGB8 1598 | NULL, // RGB 1599 | NULL, // Linear RGB 1600 | NULL, // HSL 1601 | NULL, // HSV 1602 | NULL, // YUV 1603 | NULL, // YCbCr 1604 | NULL, // YDbDr 1605 | NULL, // YIQ 1606 | NULL, // XYZ 1607 | NULL, // xyY 1608 | color_LCHab_to_Lab // Lab 1609 | }, 1610 | { 1611 | COLOR_LAB, // RGB8 1612 | COLOR_LAB, // RGB 1613 | COLOR_LAB, // Linear RGB 1614 | COLOR_LAB, // HSL 1615 | COLOR_LAB, // HSV 1616 | COLOR_LAB, // YUV 1617 | COLOR_LAB, // YCbCr 1618 | COLOR_LAB, // YDbDr 1619 | COLOR_LAB, // YIQ 1620 | COLOR_LAB, // XYZ 1621 | COLOR_LAB, // xyY 1622 | COLOR_NONE, // Lab 1623 | COLOR_LAB, // Luv 1624 | COLOR_NONE, // LCHab 1625 | COLOR_LAB, // LCHuv 1626 | COLOR_LAB // LSHuv 1627 | } 1628 | }, 1629 | { 1630 | // LCHuv 1631 | "LCHuv", 1632 | color_LCHuv_extract, 1633 | { 1634 | NULL, // RGB8 1635 | NULL, // RGB 1636 | NULL, // Linear RGB 1637 | NULL, // HSL 1638 | NULL, // HSV 1639 | NULL, // YUV 1640 | NULL, // YCbCr 1641 | NULL, // YDbDr 1642 | NULL, // YIQ 1643 | NULL, // XYZ 1644 | NULL, // xyY 1645 | NULL, // Lab 1646 | color_LCHuv_to_Luv, // Luv 1647 | NULL, // LCHab 1648 | NULL, // LCHuv 1649 | color_LCHuv_to_LSHuv // LSHuv 1650 | }, 1651 | { 1652 | COLOR_LUV, // RGB8 1653 | COLOR_LUV, // RGB 1654 | COLOR_LUV, // Linear RGB 1655 | COLOR_LUV, // HSL 1656 | COLOR_LUV, // HSV 1657 | COLOR_LUV, // YUV 1658 | COLOR_LUV, // YCbCr 1659 | COLOR_LUV, // YDbDr 1660 | COLOR_LUV, // YIQ 1661 | COLOR_LUV, // XYZ 1662 | COLOR_LUV, // xyY 1663 | COLOR_LUV, // Lab 1664 | COLOR_NONE, // Luv 1665 | COLOR_LUV, // LCHab 1666 | COLOR_NONE, // LCHuv 1667 | COLOR_NONE // LSHuv 1668 | } 1669 | }, 1670 | { 1671 | // LSHuv 1672 | "LSHuv", 1673 | color_LSHuv_extract, 1674 | { 1675 | NULL, // RGB8 1676 | NULL, // RGB 1677 | NULL, // Linear RGB 1678 | NULL, // HSL 1679 | NULL, // HSV 1680 | NULL, // YUV 1681 | NULL, // YCbCr 1682 | NULL, // YDbDr 1683 | NULL, // YIQ 1684 | NULL, // XYZ 1685 | NULL, // xyY 1686 | NULL, // Lab 1687 | NULL, // Luv 1688 | NULL, // LCHab 1689 | color_LSHuv_to_LCHuv, // LCHuv 1690 | }, 1691 | { 1692 | COLOR_LCHUV, // RGB8 1693 | COLOR_LCHUV, // RGB 1694 | COLOR_LCHUV, // Linear RGB 1695 | COLOR_LCHUV, // HSL 1696 | COLOR_LCHUV, // HSV 1697 | COLOR_LCHUV, // YUV 1698 | COLOR_LCHUV, // YCbCr 1699 | COLOR_LCHUV, // YDbDr 1700 | COLOR_LCHUV, // YIQ 1701 | COLOR_LCHUV, // XYZ 1702 | COLOR_LCHUV, // xyY 1703 | COLOR_LCHUV, // Lab 1704 | COLOR_LCHUV, // Luv 1705 | COLOR_LCHUV, // LCHab 1706 | COLOR_NONE, // LCHuv 1707 | COLOR_NONE // LSHuv 1708 | } 1709 | } 1710 | }; 1711 | 1712 | void COLOR_CALL color_convert(struct color *c, enum color_type new_type, uint8_t new_extra) 1713 | { 1714 | struct color_descriptor const *desc; 1715 | 1716 | assert(c != NULL); 1717 | assert(new_type > COLOR_NONE); 1718 | assert(new_type < COLOR_DUMMY_END); 1719 | 1720 | while(c->type != new_type || c->extra != new_extra) 1721 | { 1722 | conversion_func func; 1723 | enum color_type tmp_type; 1724 | 1725 | assert(c->type > COLOR_NONE); 1726 | assert(c->type < COLOR_DUMMY_END); 1727 | 1728 | desc = &g_descriptors[c->type - 1]; 1729 | func = desc->conversions[new_type - 1]; 1730 | tmp_type = new_type; 1731 | 1732 | if(!func) 1733 | { 1734 | tmp_type = (enum color_type)desc->proxy_conversions[new_type - 1]; 1735 | 1736 | assert(tmp_type > COLOR_NONE); 1737 | assert(tmp_type < COLOR_DUMMY_END); 1738 | 1739 | func = desc->conversions[tmp_type - 1]; 1740 | assert(func != NULL); 1741 | } 1742 | 1743 | func(c, new_extra); 1744 | 1745 | assert(c->type == tmp_type); 1746 | } 1747 | 1748 | assert(c->type > COLOR_NONE); 1749 | assert(c->type < COLOR_DUMMY_END); 1750 | } 1751 | 1752 | COLOR_EXPORT char const* COLOR_CALL color_name(enum color_type type) 1753 | { 1754 | assert(type > COLOR_NONE); 1755 | assert(type < COLOR_DUMMY_END); 1756 | return g_descriptors[type - 1].name; 1757 | } 1758 | 1759 | COLOR_EXPORT void COLOR_CALL color_extract_components(double *dst, struct color const *src) 1760 | { 1761 | assert(dst != NULL); 1762 | assert(src != NULL); 1763 | assert(src->type > COLOR_NONE); 1764 | assert(src->type < COLOR_DUMMY_END); 1765 | 1766 | g_descriptors[src->type - 1].extract(dst, src); 1767 | } 1768 | -------------------------------------------------------------------------------- /color.h: -------------------------------------------------------------------------------- 1 | /* 2 | Color conversions 3 | Copyright (c) 2011, Cory Nelson (phrosty@gmail.com) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #pragma once 27 | 28 | #include 29 | 30 | #ifdef COLOR_STATIC 31 | #define COLOR_EXPORT 32 | #else 33 | #ifdef COLOR_EXPORTS 34 | #define COLOR_EXPORT __declspec(dllexport) 35 | #else 36 | #define COLOR_EXPORT __declspec(dllimport) 37 | #endif 38 | #endif 39 | 40 | #define COLOR_CALL _cdecl 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | enum color_type 47 | { 48 | COLOR_NONE, 49 | COLOR_RGB8, 50 | COLOR_RGB, 51 | COLOR_LINEAR_RGB, 52 | COLOR_HSL, 53 | COLOR_HSV, 54 | COLOR_YUV, 55 | COLOR_YCBCR, 56 | COLOR_YDBDR, 57 | COLOR_YIQ, 58 | COLOR_XYZ, 59 | COLOR_XYY, 60 | COLOR_LAB, 61 | COLOR_LUV, 62 | COLOR_LCHAB, 63 | COLOR_LCHUV, 64 | COLOR_LSHUV, 65 | COLOR_DUMMY_END 66 | }; 67 | 68 | enum color_extra 69 | { 70 | //COLOR_NONE, 71 | COLOR_YUV_MAT_REC601 = 0, 72 | COLOR_YUV_MAT_REC709 = 1, 73 | COLOR_YUV_MAT_SMPTE240M = 2, 74 | COLOR_YUV_MAT_FCC = 3, 75 | COLOR_YUV_MAT_MASK = 3, 76 | COLOR_YCBCR_FULL_RANGE = 4, 77 | }; 78 | 79 | struct color 80 | { 81 | uint8_t type, extra; 82 | union 83 | { 84 | struct { uint8_t R, G, B; } RGB8; 85 | struct { double R, G, B; } RGB, LinearRGB; 86 | struct { double H, S, L; } HSL; // hue is in [0, 6) 87 | struct { double H, S, V; } HSV; // hue is in [0, 6) 88 | struct { double Y, U, V; } YUV; // Y, U, V are in [0, 1], [-0.436,0.436], [-0.615,0.615] 89 | struct { uint8_t Y, Cb, Cr; } YCbCr; 90 | struct { double Y, Db, Dr; } YDbDr; 91 | struct { double Y, I, Q; } YIQ; 92 | struct { double X, Y, Z; } XYZ; 93 | struct { double x, y, Y; } xyY; 94 | struct { double L, a, b; } Lab; 95 | struct { double L, u, v; } Luv; 96 | struct { double L, C, h; } LCHab, LCHuv; // hue is in [0, pi*2) 97 | struct { double L, S, h; } LSHuv; // hue is in [0, pi*2) 98 | }; 99 | }; 100 | 101 | COLOR_EXPORT void COLOR_CALL color_convert(struct color *c, enum color_type new_type, uint8_t new_extra); 102 | COLOR_EXPORT char const* COLOR_CALL color_name(enum color_type type); 103 | COLOR_EXPORT void COLOR_CALL color_extract_components(double *dst, struct color const *src); 104 | 105 | #ifdef __cplusplus 106 | } 107 | #endif 108 | -------------------------------------------------------------------------------- /colors.js: -------------------------------------------------------------------------------- 1 | /* 2 | Color conversions 3 | Copyright (c) 2011, Cory Nelson (phrosty@gmail.com) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | When possible, constants are given as accurate pre-computed rationals. When not, 26 | they are given at double precision with a comment on how to compute them. 27 | */ 28 | 29 | (function(){ // wrap in a function to capture variables. 30 | 31 | function clamp(x, min, max) 32 | { 33 | if(x < min) return min; 34 | if(x > max) return max; 35 | return x; 36 | } 37 | 38 | function clampdeg(hue) 39 | { 40 | hue %= 360; 41 | 42 | if(hue < 0) 43 | { 44 | hue += 360; 45 | } 46 | 47 | return hue; 48 | } 49 | 50 | function clamprad(hue) 51 | { 52 | hue %= Math.PI * 2; 53 | 54 | if(hue < 0) 55 | { 56 | hue += Math.PI * 2; 57 | } 58 | 59 | return hue; 60 | } 61 | 62 | function ColorLShuv(L, S, h, clamped) 63 | { 64 | this.clamped = clamped || L < 0 || L > 100; // TODO: what is S min/max? 65 | 66 | this.L = clamp(L, 0, 100); 67 | this.S = S; 68 | this.h = clamprad(h); 69 | 70 | this.toLChuv = function() 71 | { 72 | return new ColorLChuv(this.L, this.S * this.L, this.h); 73 | }; 74 | } 75 | 76 | function ColorLChuv(L, C, h, clamped) 77 | { 78 | this.clamped = clamped || L < 0 || L > 100 || C < 0 || C > 7.40066582332174237e2; 79 | 80 | this.L = clamp(L, 0, 100); 81 | this.C = clamp(C, 0, 7.40066582332174237e2); // 240/316141*sqrt(950343809713) 82 | this.h = clamprad(h); 83 | 84 | this.toLShuv = function() 85 | { 86 | return new ColorLShuv(this.L, this.C / this.L, this.h, this.clamped); 87 | }; 88 | 89 | this.toLuv = function() 90 | { 91 | var h = clamprad(this.h); 92 | return new ColorLuv(this.L, Math.cos(h) * this.C, Math.sin(h) * this.C, this.clamped); 93 | }; 94 | } 95 | 96 | function ColorLuv(L, u, v, clamped) 97 | { 98 | this.clamped = clamped || L < 0 || L > 100 || u < -81304600/316141 || v > 54113280/316141; // TODO: what is u max, and v min?? 99 | 100 | this.L = clamp(L, 0, 100); 101 | this.u = (u < -81304600/316141) ? -81304600/316141 : u; 102 | this.v = (v > 54113280/316141) ? 54113280/316141 : v; 103 | 104 | this.toLChuv = function() 105 | { 106 | return new ColorLChuv( 107 | this.L, 108 | Math.sqrt(this.u * this.u + this.v * this.v), 109 | Math.atan2(this.v, this.u), 110 | this.clamped); 111 | }; 112 | 113 | this.toXYZ = function() 114 | { 115 | var rdiv = Color.refX + Color.refY * 15 + Color.refZ * 3; 116 | var ur = Color.refX * 4 / rdiv; 117 | var vr = Color.refY * 9 / rdiv; 118 | 119 | if(L > 8) 120 | { 121 | var Y = (this.L + 16) / 116; 122 | Y = Y * Y * Y; 123 | } 124 | else 125 | { 126 | var Y = this.L * (27/24389); 127 | } 128 | 129 | var a = (this.L * 52 / (this.u + this.L * 13 * ur) - 1) / 3; 130 | var b = -5 * Y; 131 | var d = (this.L * 39 / (this.v + this.L * 13 * vr) - 5) * Y; 132 | 133 | var X = (d - b) / (a + 1/3); 134 | var Z = X * a + b; 135 | 136 | return new ColorXYZ(X, Y, Z, this.clamped); 137 | }; 138 | } 139 | 140 | function ColorLChab(L, C, h, clamped) 141 | { 142 | this.clamped = clamped || L < 0 || L > 100 || C < 0 || C > 4.64238345442629658e2; 143 | 144 | this.L = clamp(L, 0, 100); 145 | this.C = clamp(C, 0, 4.64238345442629658e2); // 2500*sqrt(1/29) 146 | this.h = clamprad(h); 147 | 148 | this.toLab = function() 149 | { 150 | var h = clamprad(this.h); 151 | return new ColorLab(this.L, Math.cos(h) * this.C, Math.sin(h) * this.C, this.clamped); 152 | }; 153 | } 154 | 155 | function ColorLab(L, a, b, clamped) 156 | { 157 | this.clamped = clamped || L < 0 || L > 100 || a < -12500/29 || a > 12500/29 || b < -5000/29 || b > 5000/29; 158 | 159 | this.L = clamp(L, 0, 100); 160 | this.a = clamp(a, -12500/29, 12500/29); 161 | this.b = clamp(b, -5000/29, 5000/29); 162 | 163 | this.toXYZ = function() 164 | { 165 | function toXYZc(c) 166 | { 167 | var c3 = c * c * c; 168 | 169 | if(c3 > 216 / 24389) return c3; 170 | return c * (108/841) - (432/24389); 171 | } 172 | 173 | var Y = (this.L + 16) / 116; 174 | 175 | return new ColorXYZ( 176 | toXYZc(Y + this.a / 500) * Color.refX, 177 | toXYZc(Y) * Color.refY, 178 | toXYZc(Y - this.b / 200) * Color.refZ, 179 | this.clamped); 180 | }; 181 | 182 | this.toLChab = function() 183 | { 184 | return new ColorLChab( 185 | this.L, 186 | Math.sqrt(this.a * this.a + this.b * this.b), 187 | Math.atan2(this.b, this.a), 188 | this.clamped); 189 | }; 190 | } 191 | 192 | function ColorxyY(x, y, Y, clamped) 193 | { 194 | this.clamped = clamped || x < 0 || x > 1 || y < 0 || y > 1 || Y < 0 || Y > 1; 195 | 196 | this.x = clamp(x, 0, 1); 197 | this.y = clamp(y, 0, 1); 198 | this.Y = clamp(Y, 0, 1); 199 | 200 | this.toXYZ = function() 201 | { 202 | if(Math.abs(this.y) != 0) 203 | { 204 | var mul = this.Y / this.y; 205 | return new ColorXYZ(this.x * mul, this.Y, (1 - this.x - this.y) * mul); 206 | } 207 | else 208 | { 209 | return new ColorXYZ(0, 0, 0); 210 | } 211 | }; 212 | } 213 | 214 | function ColorXYZ(X, Y, Z, clamped) 215 | { 216 | this.clamped = clamped || X < 0 || X > Color.refX || Y < 0 || Y > Color.refY || Z < 0 || Z > Color.refZ; 217 | 218 | this.X = clamp(X, 0, Color.refX); 219 | this.Y = clamp(Y, 0, Color.refY); 220 | this.Z = clamp(Z, 0, Color.refZ); 221 | 222 | this.toLinearRGB = function() 223 | { 224 | return new ColorLinearRGB( 225 | this.X * (641589/197960) + this.Y * (-608687/395920) + this.Z * (-49353/98980), 226 | this.X * (-42591639/43944050) + this.Y * (82435961/43944050) + this.Z * (1826061/43944050), 227 | this.X * (49353/887015) + this.Y * (-180961/887015) + this.Z * (49353/46685), 228 | this.clamped); 229 | }; 230 | 231 | this.toxyY = function() 232 | { 233 | var div = this.X + this.Y + this.Z; 234 | 235 | if(Math.abs(div) == 0) 236 | { 237 | div = 1; 238 | } 239 | 240 | return new ColorxyY(this.X / div, this.Y / div, this.Y, this.clamped); 241 | }; 242 | 243 | this.toLab = function() 244 | { 245 | function toLabc(c) 246 | { 247 | if (c > 216 / 24389) return Math.pow(c, 1 / 3); 248 | return c * (841 / 108) + (4 / 49); 249 | } 250 | 251 | var X = toLabc(this.X / Color.refX); 252 | var Y = toLabc(this.Y / Color.refY); 253 | var Z = toLabc(this.Z / Color.refZ); 254 | 255 | return new ColorLab(116 * Y - 16, 500 * (X - Y), 200 * (Y - Z), this.clamped); 256 | }; 257 | 258 | this.toLuv = function() 259 | { 260 | var rdiv = Color.refX + Color.refY * 15 + Color.refZ * 3; 261 | var ur = Color.refX * 4 / rdiv; 262 | var vr = Color.refY * 9 / rdiv; 263 | 264 | var div = this.X + this.Y * 15 + this.Z * 3; 265 | 266 | if(Math.abs(div) == 0) 267 | { 268 | div = 1; 269 | } 270 | 271 | var u = this.X * 4 / div; 272 | var v = this.Y * 9 / div; 273 | var yr = this.Y / Color.refY; 274 | 275 | if(yr > 216/24389) 276 | { 277 | var L = Math.pow(yr, 1 / 3) * 116 - 16; 278 | } 279 | else 280 | { 281 | var L = yr * (24389/27); 282 | } 283 | 284 | return new ColorLuv(L, L * 13 * (u - ur), L * 13 * (v - vr), this.clamped); 285 | }; 286 | } 287 | 288 | function ColorLinearRGB(R, G, B, clamped) 289 | { 290 | this.clamped = clamped || R < 0 || R > 1 || G < 0 || G > 1 || B < 0 || B > 1; 291 | this.R = clamp(R, 0, 1); 292 | this.G = clamp(G, 0, 1); 293 | this.B = clamp(B, 0, 1); 294 | 295 | this.toRGB = function() 296 | { 297 | function toRGBc(c) 298 | { 299 | if(c > 0.0031308) return Math.pow(c, 1 / 2.4) * 1.055 - 0.055; 300 | return c * 12.92; 301 | } 302 | 303 | return new ColorRGB(toRGBc(this.R), toRGBc(this.G), toRGBc(this.B), this.clamped); 304 | }; 305 | 306 | this.toXYZ = function() 307 | { 308 | return new ColorXYZ( 309 | this.R * (5067776/12288897) + this.G * (4394405/12288897) + this.B * (4435075/24577794), 310 | this.R * (871024/4096299) + this.G * (8788810/12288897) + this.B * (887015/12288897), 311 | this.R * (79184/4096299) + this.G * (4394405/36866691) + this.B * (70074185/73733382), 312 | this.clamped); 313 | }; 314 | } 315 | 316 | function ColorHSV(H, S, V, clamped) 317 | { 318 | this.clamped = clamped || S < 0 || S > 1 || V < 0 || V > 1; 319 | this.H = clampdeg(H); 320 | this.S = clamp(S, 0, 1); 321 | this.V = clamp(V, 0, 1); 322 | 323 | this.toRGB = function() 324 | { 325 | if(this.S <= 0) 326 | { 327 | return new ColorRGB(this.V, this.V, this.V, this.clamped); 328 | } 329 | 330 | var H = clampdeg(this.H) / 60; 331 | var C = this.V * this.S; 332 | var m = this.V - C; 333 | var X = C * (1 - Math.abs(H % 2 - 1)) + m; 334 | 335 | C += m; 336 | 337 | if(H >= 5) return new ColorRGB(C, m, X, this.clamped); 338 | if(H >= 4) return new ColorRGB(X, m, C, this.clamped); 339 | if(H >= 3) return new ColorRGB(m, X, C, this.clamped); 340 | if(H >= 2) return new ColorRGB(m, C, X, this.clamped); 341 | if(H >= 1) return new ColorRGB(X, C, m, this.clamped); 342 | return new ColorRGB(C, X, m, this.clamped); 343 | }; 344 | } 345 | 346 | function ColorHSL(H, S, L, clamped) 347 | { 348 | this.clamped = clamped || S < 0 || S > 1 || L < 0 || L > 1; 349 | this.H = clampdeg(H); 350 | this.S = clamp(S, 0, 1); 351 | this.L = clamp(L, 0, 1); 352 | 353 | this.toRGB = function() 354 | { 355 | if(this.S <= 0) 356 | { 357 | return new ColorRGB(this.S, this.S, this.S, this.clamped); 358 | } 359 | 360 | var H = clampdeg(this.H) / 60; 361 | var C = (1 - Math.abs(this.L * 2 - 1)) * this.S; 362 | var m = this.L - C * 0.5; 363 | var X = C * (1 - Math.abs(H % 2 - 1)) + m; 364 | 365 | C += m; 366 | 367 | if(H >= 5) return new ColorRGB(C, m, X, this.clamped); 368 | if(H >= 4) return new ColorRGB(X, m, C, this.clamped); 369 | if(H >= 3) return new ColorRGB(m, X, C, this.clamped); 370 | if(H >= 2) return new ColorRGB(m, C, X, this.clamped); 371 | if(H >= 1) return new ColorRGB(X, C, m, this.clamped); 372 | return new ColorRGB(C, X, m, this.clamped); 373 | }; 374 | } 375 | 376 | function ColorRGB(R, G, B, clamped) 377 | { 378 | this.clamped = clamped || R < 0 || R > 1 || G < 0 || G > 1 || B < 0 || B > 1; 379 | this.R = clamp(R, 0, 1); 380 | this.G = clamp(G, 0, 1); 381 | this.B = clamp(B, 0, 1); 382 | 383 | this.toHSV = function() 384 | { 385 | var min = Math.min(this.R, this.G, this.B); 386 | var max = Math.max(this.R, this.G, this.B); 387 | var delta = max - min; 388 | 389 | if(Math.abs(delta) != 0) 390 | { 391 | var S = delta / max; 392 | 393 | if(max == this.R) var H = (this.G - this.B) / delta; 394 | else if(max == this.G) var H = (this.B - this.R) / delta + 2; 395 | else var H = (this.R - this.G) / delta + 4; 396 | } 397 | else 398 | { 399 | var H = 0; 400 | var S = 0; 401 | } 402 | 403 | return new ColorHSV(H * 60, S, max, this.clamped); 404 | }; 405 | 406 | this.toHSL = function() 407 | { 408 | var min = Math.min(this.R, this.G, this.B); 409 | var max = Math.max(this.R, this.G, this.B); 410 | var delta = max - min; 411 | 412 | var L = (max + min) * 0.5; 413 | 414 | if(Math.abs(delta) != 0) 415 | { 416 | if(L < 0.5) var S = delta / (max + min); 417 | else var S = delta / (2 - max - min); 418 | 419 | if(max == this.R) var H = (this.G - this.B) / delta; 420 | else if(max == this.G) var H = (this.B - this.R) / delta + 2; 421 | else var H = (this.R - this.G) / delta + 4; 422 | } 423 | else 424 | { 425 | var H = 0; 426 | var S = 0; 427 | } 428 | 429 | return new ColorHSL(H * 60, S, L, this.clamped); 430 | }; 431 | 432 | this.toLinearRGB = function() 433 | { 434 | function toLinearRGBc(c) 435 | { 436 | if(c > 0.04045) return Math.pow((c + 0.055) / 1.055, 2.4); 437 | return c / 12.92; 438 | } 439 | 440 | return new ColorLinearRGB(toLinearRGBc(this.R), toLinearRGBc(this.G), toLinearRGBc(this.B), this.clamped); 441 | }; 442 | 443 | this.toYUV = function() 444 | { 445 | var mat = Color.yuvmatrix; 446 | 447 | var y = this.R * mat.rScale + this.G * mat.gScale + this.B * mat.bScale; 448 | var u = (this.B - y) / (1 - mat.bScale) * 0.5 + 0.5; 449 | var v = (this.R - y) / (1 - mat.rScale) * 0.5 + 0.5; 450 | 451 | return new ColorYUV(y, u, v, this.clamped); 452 | }; 453 | 454 | this.toYIQ = function() 455 | { 456 | return new ColorYIQ( 457 | this.R * 0.299 + this.G * 0.587 + this.B * 0.114, 458 | this.R * 0.5 + this.G * -0.23038159508364756 + this.B * -0.26961840491635244 + 0.5, 459 | this.R * -0.202349432337541121 + this.G * 0.5 + this.B * -0.297650567662458879 + 0.5, 460 | this.clamped); 461 | }; 462 | } 463 | 464 | function ColorYUV(Y, U, V, clamped) 465 | { 466 | this.clamped = clamped || Y < 0 || Y > 1 || U < 0 || V > 1 || V < 0 || V > 1; 467 | this.Y = clamp(Y, 0, 1); 468 | this.U = clamp(U, 0, 1); 469 | this.V = clamp(V, 0, 1); 470 | 471 | this.toRGB = function() 472 | { 473 | var mat = Color.yuvmatrix; 474 | 475 | var u = (this.U - 0.5) / 0.5 * (1 - mat.bScale); 476 | var v = (this.V - 0.5) / 0.5 * (1 - mat.rScale); 477 | 478 | var r = v + this.Y; 479 | var b = u + this.Y; 480 | var g = (this.Y - r * mat.rScale - b * mat.bScale) / mat.gScale; 481 | 482 | return new ColorRGB(r, g, b, this.clamped); 483 | }; 484 | } 485 | 486 | function ColorYIQ(Y, I, Q, clamped) 487 | { 488 | this.clamped = clamped || Y < 0 || Y > 1 || I < 0 || I > 1 || Q < 0 || Q > 1; 489 | this.Y = clamp(Y, 0, 1); 490 | this.I = clamp(I, 0, 1); 491 | this.Q = clamp(Q, 0, 1); 492 | 493 | this.toRGB = function() 494 | { 495 | var i = this.I - 0.5; 496 | var q = this.Q - 0.5; 497 | 498 | return new ColorRGB( 499 | this.Y + i * 1.13933588212202582 - q * 0.649035964281386078, 500 | this.Y - i * 0.32416610079155499 + q * 0.676636193255190191, 501 | this.Y - i * 1.31908708412142932 - q * 1.78178677298826495, 502 | this.clamped); 503 | }; 504 | } 505 | 506 | // CIE Delta E 1976 507 | // JND: ~2.3 508 | function deltaE1976(lab1, lab2) 509 | { 510 | var delta_L = lab1.L - lab2.L; 511 | var delta_a = lab1.a - lab2.a; 512 | var delta_b = lab1.b - lab2.b; 513 | 514 | return Math.sqrt(delta_L * delta_L + delta_a * delta_a + delta_b * delta_b); 515 | } 516 | 517 | // CIE Delta E 1994 518 | function deltaE1994(lab1, lab2, type) 519 | { 520 | var C1 = Math.sqrt(lab1.a * lab1.a + lab1.b * lab1.b); 521 | var C2 = Math.sqrt(lab2.a * lab2.a + lab2.b * lab2.b); 522 | 523 | var delta_L = lab1.L - lab2.L; 524 | var delta_C = C1 - C2; 525 | var delta_a = lab1.a - lab2.a; 526 | var delta_b = lab1.b - lab2.b; 527 | var delta_H = Math.sqrt(delta_a * delta_a + delta_b * delta_b - delta_C * delta_C); 528 | 529 | if(type == 'graphic arts') 530 | { 531 | delta_C /= C1 * 0.045 + 1; 532 | delta_H /= C1 * 0.015 + 1; 533 | } 534 | else if(type == 'textiles') 535 | { 536 | delta_L *= 0.5; 537 | delta_C /= C1 * 0.048 + 1; 538 | delta_H /= C1 * 0.014 + 1; 539 | } 540 | 541 | return Math.sqrt(delta_L * delta_L + delta_C * delta_C + delta_H * delta_H); 542 | } 543 | 544 | // CIE Delta E 2000 545 | // Note: maximum is about 158 for colors in the sRGB gamut. 546 | function deltaE2000(lch1, lch2) 547 | { 548 | var avg_L = (lch1.L + lch2.L) * 0.5; 549 | var delta_L = lch2.L - lch1.L; 550 | 551 | var avg_C = (lch1.C + lch2.C) * 0.5; 552 | var delta_C = lch1.C - lch2.C; 553 | 554 | var avg_H = (lch1.h + lch2.h) * 0.5; 555 | 556 | if(Math.abs(lch1.h - lch2.h) > Math.PI) 557 | { 558 | avg_H += Math.PI; 559 | } 560 | 561 | var delta_H = lch2.h - lch1.h; 562 | 563 | if(Math.abs(delta_H) > Math.PI) 564 | { 565 | if(lch2.h <= lch1.h) delta_H += Math.PI * 2; 566 | else delta_H -= Math.PI * 2; 567 | } 568 | 569 | delta_H = Math.sqrt(lch1.C * lch2.C) * Math.sin(delta_H) * 2; 570 | 571 | var T = 1 572 | - 0.17 * Math.cos(avg_H - Math.PI / 6) 573 | + 0.24 * Math.cos(avg_H * 2) 574 | + 0.32 * Math.cos(avg_H * 3 + Math.PI / 30) 575 | - 0.20 * Math.cos(avg_H * 4 - Math.PI * 7/20); 576 | 577 | var SL = avg_L - 50; 578 | SL *= SL; 579 | SL = SL * 0.015 / Math.sqrt(SL + 20) + 1; 580 | 581 | var SC = avg_C * 0.045 + 1; 582 | 583 | var SH = avg_C * T * 0.015 + 1; 584 | 585 | var delta_Theta = avg_H / 25 - Math.PI * 11/180; 586 | delta_Theta = Math.exp(delta_Theta * -delta_Theta) * (Math.PI / 6); 587 | 588 | var RT = Math.pow(avg_C, 7); 589 | RT = Math.sqrt(RT / (RT + 6103515625)) * Math.sin(delta_Theta) * -2; // 6103515625 = 25^7 590 | 591 | delta_L /= SL; 592 | delta_C /= SC; 593 | delta_H /= SH; 594 | 595 | return Math.sqrt(delta_L * delta_L + delta_C * delta_C + delta_H * delta_H + RT * delta_C * delta_H); 596 | } 597 | 598 | var Color = 599 | { 600 | refX: 31271/32902, // normalized standard observer D65. 601 | refY: 1, 602 | refZ: 35827/32902, 603 | yuvmatrices: 604 | { 605 | 'bt601': 606 | { 607 | name: 'ITU-R BT.601 (DVD, JPEG, Youtube)', 608 | rScale: 0.299, 609 | gScale: 0.587, 610 | bScale: 0.114 611 | }, 612 | 'bt709': 613 | { 614 | name: 'ITU-R BT.709 (HDTV)', 615 | rScale: 0.2125, 616 | gScale: 0.7154, 617 | bScale: 0.0721 618 | }, 619 | 'bt2020': 620 | { 621 | name: 'ITU-R BT.2020 (UHDTV)', 622 | rScale: 0.2627, 623 | gScale: 0.678, 624 | bScale: 0.0593 625 | }, 626 | 'smpte240m': 627 | { 628 | name: 'SMPTE 240M (very old HDTV)', 629 | rScale: 0.212, 630 | gScale: 0.701, 631 | bScale: 0.087 632 | }, 633 | 'fcc': 634 | { 635 | name: 'FCC', 636 | rScale: 0.3, 637 | gScale: 0.59, 638 | bScale: 0.11 639 | } 640 | }, 641 | colorspaces: 642 | { 643 | 'rgb': 644 | { 645 | name: 'RGB', 646 | components: ['Red', 'Green', 'Blue'], 647 | componentInfo: 648 | [ 649 | { minimum: 0, maximum: 1, scale: 255 }, 650 | { minimum: 0, maximum: 1, scale: 255 }, 651 | { minimum: 0, maximum: 1, scale: 255 } 652 | ], 653 | toColor: function(x) { return new ColorRGB(x[0], x[1], x[2]); }, 654 | toGeneric: function(x) { return [ x.R, x.G, x.B ]; }, 655 | conversions: 656 | { 657 | 'hsl': function(x) { return x.toHSL(); }, 658 | 'hsv': function(x) { return x.toHSV(); }, 659 | 'yuv': function(x) { return x.toYUV(); }, 660 | 'yiq': function(x) { return x.toYIQ(); }, 661 | 'linear_rgb': function(x) { return x.toLinearRGB(); } 662 | } 663 | }, 664 | 'linear_rgb': 665 | { 666 | name: 'Linear RGB', 667 | components: ['Red', 'Green', 'Blue'], 668 | componentInfo: 669 | [ 670 | { minimum: 0, maximum: 1, scale: 1023 }, 671 | { minimum: 0, maximum: 1, scale: 1023 }, 672 | { minimum: 0, maximum: 1, scale: 1023 } 673 | ], 674 | toColor: function(x) { return new ColorLinearRGB(x[0], x[1], x[2]); }, 675 | toGeneric: function(x) { return [ x.R, x.G, x.B ]; }, 676 | conversions: 677 | { 678 | 'rgb': function(x) { return x.toRGB(); }, 679 | 'xyz': function(x) { return x.toXYZ(); }, 680 | } 681 | }, 682 | 'hsl': 683 | { 684 | name: 'HSL', 685 | components: ['Hue', 'Saturation', 'Lightness'], 686 | componentInfo: 687 | [ 688 | { minimum: 0, maximum: 359, scale: 1 }, 689 | { minimum: 0, maximum: 1, scale: 255 }, 690 | { minimum: 0, maximum: 1, scale: 255 } 691 | ], 692 | toColor: function(x) { return new ColorHSL(x[0], x[1], x[2]); }, 693 | toGeneric: function(x) { return [ x.H, x.S, x.L ]; }, 694 | conversions: 695 | { 696 | 'rgb': function(x) { return x.toRGB(); } 697 | } 698 | }, 699 | 'hsv': 700 | { 701 | name: 'HSV', 702 | components: ['Hue', 'Saturation', 'Value'], 703 | componentInfo: 704 | [ 705 | { minimum: 0, maximum: 359, scale: 1 }, 706 | { minimum: 0, maximum: 1, scale: 255 }, 707 | { minimum: 0, maximum: 1, scale: 255 } 708 | ], 709 | toColor: function(x) { return new ColorHSV(x[0], x[1], x[2]); }, 710 | toGeneric: function(x) { return [ x.H, x.S, x.V ]; }, 711 | conversions: 712 | { 713 | 'rgb': function(x) { return x.toRGB(); } 714 | } 715 | }, 716 | 'yuv': 717 | { 718 | name: 'Y′UV', 719 | components: ['Luma', 'Chroma U', 'Chroma V'], 720 | componentInfo: 721 | [ 722 | { minimum: 0, maximum: 1, scale: 255 }, 723 | { minimum: 0, maximum: 1, scale: 255 }, 724 | { minimum: 0, maximum: 1, scale: 255 } 725 | ], 726 | toColor: function(x) { return new ColorYUV(x[0], x[1], x[2]); }, 727 | toGeneric: function(x) { return [ x.Y, x.U, x.V ]; }, 728 | conversions: 729 | { 730 | 'rgb': function(x) { return x.toRGB(); } 731 | } 732 | }, 733 | 'yiq': 734 | { 735 | name: 'Y′IQ', 736 | components: ['Luma', 'Chroma I', 'Chroma Q'], 737 | componentInfo: 738 | [ 739 | { minimum: 0, maximum: 1, scale: 255 }, 740 | { minimum: 0, maximum: 1, scale: 255 }, 741 | { minimum: 0, maximum: 1, scale: 255 } 742 | ], 743 | toColor: function(x) { return new ColorYIQ(x[0], x[1], x[2]); }, 744 | toGeneric: function(x) { return [ x.Y, x.I, x.Q ]; }, 745 | conversions: 746 | { 747 | 'rgb': function(x) { return x.toRGB(); } 748 | } 749 | }, 750 | 'xyy': 751 | { 752 | name: 'CIE xyY', 753 | components: ['x', 'y', 'Y'], 754 | componentInfo: 755 | [ 756 | { minimum: 0, maximum: 1, scale: 100 }, 757 | { minimum: 0, maximum: 1, scale: 100 }, 758 | { minimum: 0, maximum: 1, scale: 100 } 759 | ], 760 | toColor: function(x) { return new ColorxyY(x[0], x[1], x[2]); }, 761 | toGeneric: function(x) { return [ x.x, x.y, x.Y ]; }, 762 | conversions: 763 | { 764 | 'xyz': function(x) { return x.toXYZ(); } 765 | } 766 | }, 767 | 'xyz': 768 | { 769 | name: 'CIE XYZ', 770 | components: ['X', 'Y', 'Z'], 771 | componentInfo: 772 | [ 773 | { minimum: 0, maximum: 0.9505, scale: 100 }, 774 | { minimum: 0, maximum: 1, scale: 100 }, 775 | { minimum: 0, maximum: 1.089, scale: 100 } 776 | ], 777 | toColor: function(x) { return new ColorXYZ(x[0], x[1], x[2]); }, 778 | toGeneric: function(x) { return [ x.X, x.Y, x.Z ]; }, 779 | conversions: 780 | { 781 | 'linear_rgb': function(x) { return x.toLinearRGB(); }, 782 | 'lab': function(x) { return x.toLab(); }, 783 | 'luv': function(x) { return x.toLuv(); }, 784 | 'xyy': function(x) { return x.toxyY(); } 785 | } 786 | }, 787 | 'lab': 788 | { 789 | name: 'CIE L*a*b*', 790 | components: ['Lightness', 'a', 'b'], 791 | componentInfo: 792 | [ 793 | { minimum: 0, maximum: 100, scale: 1 }, 794 | { minimum: -12500/29, maximum: 12500/29, scale: 1 }, 795 | { minimum: -5000/29, maximum: 5000/29, scale: 1 } 796 | ], 797 | toColor: function(x) { return new ColorLab(x[0], x[1], x[2]); }, 798 | toGeneric: function(x) { return [ x.L, x.a, x.b ]; }, 799 | conversions: 800 | { 801 | 'xyz': function(x) { return x.toXYZ(); }, 802 | 'LChab': function(x) { return x.toLChab(); } 803 | } 804 | }, 805 | 'LChab': 806 | { 807 | name: function(c) 808 | { 809 | c.appendChild(document.createTextNode('CIE L*C*h')); 810 | 811 | var sub = document.createElement('sub'); 812 | sub.appendChild(document.createTextNode('ab')); 813 | c.appendChild(sub); 814 | }, 815 | components: ['Lightness', 'Chroma', 'Hue'], 816 | componentInfo: 817 | [ 818 | { minimum: 0, maximum: 100, scale: 1 }, 819 | { minimum: 0, maximum: 4.64238345442629658e2, scale: 1 }, 820 | { minimum: 0, maximum: Math.PI*2, scale: 180/Math.PI } 821 | ], 822 | toColor: function(x) { return new ColorLChab(x[0], x[1], x[2]); }, 823 | toGeneric: function(x) { return [ x.L, x.C, x.h ]; }, 824 | conversions: 825 | { 826 | 'lab': function(x) { return x.toLab(); } 827 | } 828 | }, 829 | 'luv': 830 | { 831 | name: 'CIE L*u*v*', 832 | components: ['Lightness', 'u', 'v'], 833 | componentInfo: 834 | [ 835 | { minimum: 0, maximum: 100, scale: 1 }, 836 | { minimum: -81304600/316141, maximum: 720, scale: 1 }, 837 | { minimum: -160, maximum: 54113280/316141, scale: 1 } 838 | ], 839 | toColor: function(x) { return new ColorLuv(x[0], x[1], x[2]); }, 840 | toGeneric: function(x) { return [ x.L, x.u, x.v ]; }, 841 | conversions: 842 | { 843 | 'xyz': function(x) { return x.toXYZ(); }, 844 | 'LChuv': function(x) { return x.toLChuv(); } 845 | } 846 | }, 847 | 'LChuv': 848 | { 849 | name: function(c) 850 | { 851 | c.appendChild(document.createTextNode('CIE L*C*h')); 852 | 853 | var sub = document.createElement('sub'); 854 | sub.appendChild(document.createTextNode('uv')); 855 | c.appendChild(sub); 856 | }, 857 | components: ['Lightness', 'Chroma', 'Hue'], 858 | componentInfo: 859 | [ 860 | { minimum: 0, maximum: 100, scale: 1 }, 861 | { minimum: 0, maximum: 7.40066582332174237e2, scale: 1 }, 862 | { minimum: 0, maximum: Math.PI*2, scale: 180/Math.PI } 863 | ], 864 | toColor: function(x) { return new ColorLChuv(x[0], x[1], x[2]); }, 865 | toGeneric: function(x) { return [ x.L, x.C, x.h ]; }, 866 | conversions: 867 | { 868 | 'luv': function(x) { return x.toLuv(); }, 869 | 'lshuv': function(x) { return x.toLShuv(); } 870 | } 871 | }, 872 | 'lshuv': 873 | { 874 | name: function(c) 875 | { 876 | c.appendChild(document.createTextNode('CIE L*Sh')); 877 | 878 | var sub = document.createElement('sub'); 879 | sub.appendChild(document.createTextNode('uv')); 880 | c.appendChild(sub); 881 | }, 882 | components: ['Lightness', 'Saturation', 'Hue'], 883 | componentInfo: 884 | [ 885 | { minimum: 0, maximum: 100, scale: 1 }, 886 | { minimum: 0, maximum: 4.5, scale: 25 }, 887 | { minimum: 0, maximum: Math.PI*2, scale: 180/Math.PI } 888 | ], 889 | toColor: function(x) { return new ColorLShuv(x[0], x[1], x[2]); }, 890 | toGeneric: function(x) { return [ x.L, x.S, x.h ]; }, 891 | conversions: 892 | { 893 | 'LChuv': function(x) { return x.toLChuv(); } 894 | } 895 | } 896 | } 897 | }; 898 | 899 | function updateColors(firstid, firstcolor, starting) 900 | { 901 | var queue = new Array(); queue.push(firstid); 902 | 903 | var colors = {}; 904 | colors[firstid] = firstcolor; 905 | 906 | while(queue.length > 0) 907 | { 908 | var id = queue.pop(); 909 | var cs = Color.colorspaces[id]; 910 | 911 | for(nextid in cs.conversions) 912 | { 913 | if(!colors[nextid]) 914 | { 915 | colors[nextid] = cs.conversions[nextid](colors[id]); 916 | queue.push(nextid); 917 | } 918 | } 919 | } 920 | 921 | // update the text inputs. 922 | 923 | for(id in colors) 924 | { 925 | var cs = Color.colorspaces[id]; 926 | 927 | var generic = cs.toGeneric(colors[id]); 928 | 929 | for(idx in cs.componentInfo) 930 | { 931 | var v = generic[idx] * cs.componentInfo[idx].scale; 932 | 933 | cs.sliders[idx].text.value = v.toFixed(); 934 | 935 | if(starting || id != firstid) 936 | { 937 | $(cs.sliders[idx].slider).slider('option', 'value', v); 938 | } 939 | } 940 | 941 | $(cs.clamped).css('visibility', colors[id].clamped ? 'visible' : 'hidden'); 942 | } 943 | 944 | var rgb = colors.rgb; 945 | $(Color.colorBar).css('background-color', 'RGB(' + (rgb.R * 100) + '%,' + (rgb.G * 100) + '%,' + (rgb.B * 100) + '%)'); 946 | } 947 | 948 | function setupColor(table, id, cs, first) 949 | { 950 | var row = table.insertRow(-1); 951 | 952 | if(!first) 953 | { 954 | row.className = 'color'; 955 | } 956 | 957 | var col = row.insertCell(-1); 958 | col.rowSpan = cs.components.length; 959 | col.className = 'colorname'; 960 | 961 | if(typeof(cs.name) == 'string') 962 | { 963 | col.appendChild(document.createTextNode(cs.name)); 964 | } 965 | else 966 | { 967 | cs.name(col); 968 | } 969 | 970 | var sliders = {}; 971 | 972 | cs.getColor = function(c) 973 | { 974 | if(c == undefined) c = {}; 975 | 976 | for(idx in sliders) 977 | { 978 | if(c[idx] == undefined) 979 | { 980 | c[idx] = $(sliders[idx].slider).slider('option', 'value'); 981 | } 982 | 983 | c[idx] = c[idx] / cs.componentInfo[idx].scale; 984 | } 985 | 986 | return cs.toColor(c); 987 | }; 988 | 989 | function update(c) 990 | { 991 | updateColors(id, cs.getColor(c), false); 992 | } 993 | 994 | function addSlider(idx) 995 | { 996 | col = row.insertCell(-1); 997 | col.appendChild(document.createTextNode(cs.components[idx])); 998 | 999 | var div = document.createElement('div'); 1000 | div.className = 'slider'; 1001 | $(div).slider({ 1002 | value: 0, 1003 | min: cs.componentInfo[idx].minimum * cs.componentInfo[idx].scale, 1004 | max: cs.componentInfo[idx].maximum * cs.componentInfo[idx].scale, 1005 | slide: function(evt, ui) { var c = {}; c[idx] = ui.value; update(c); } 1006 | }); 1007 | col = row.insertCell(-1); 1008 | col.appendChild(div); 1009 | 1010 | var input = document.createElement('input'); 1011 | input.type = 'text'; 1012 | input.readOnly = true; 1013 | col = row.insertCell(-1); 1014 | col.appendChild(input); 1015 | 1016 | sliders[idx] = { slider: div, text: input }; 1017 | } 1018 | 1019 | addSlider(0); 1020 | 1021 | if(first) 1022 | { 1023 | var total = 0; 1024 | 1025 | for(idx in Color.colorspaces) 1026 | { 1027 | total += Color.colorspaces[idx].components.length; 1028 | } 1029 | 1030 | Color.colorBar = row.insertCell(-1); 1031 | Color.colorBar.rowSpan = total; 1032 | Color.colorBar.id = 'colorbar'; 1033 | } 1034 | 1035 | cs.clamped = row.insertCell(-1); 1036 | cs.clamped.className = 'clamped'; 1037 | cs.clamped.appendChild(document.createTextNode('clamped')); 1038 | cs.clamped.rowSpan = cs.components.length; 1039 | 1040 | for(var i = 1; i < cs.components.length; ++i) 1041 | { 1042 | row = table.insertRow(-1); 1043 | addSlider(i); 1044 | } 1045 | 1046 | cs.sliders = sliders; 1047 | } 1048 | 1049 | // initialize the table. 1050 | 1051 | var table = document.getElementById('colors'); 1052 | var first = true; 1053 | 1054 | for(cs in Color.colorspaces) 1055 | { 1056 | setupColor(table, cs, Color.colorspaces[cs], first); 1057 | first = false; 1058 | } 1059 | 1060 | // initialize YUV coefficients select. 1061 | 1062 | var yuvselect = document.getElementById('yuvmatrix'); 1063 | for(id in Color.yuvmatrices) 1064 | { 1065 | var option = document.createElement('option'); 1066 | option.value = id; 1067 | option.selected = (id == 'bt709'); 1068 | 1069 | option.appendChild(document.createTextNode(Color.yuvmatrices[id].name)); 1070 | yuvselect.appendChild(option); 1071 | } 1072 | 1073 | $(yuvselect).change(function() 1074 | { 1075 | var next = Color.yuvmatrices[yuvselect.value]; 1076 | 1077 | if(!next) return; 1078 | 1079 | var rgb = Color.colorspaces['yuv'].getColor().toRGB(); 1080 | 1081 | Color.yuvmatrix = next; 1082 | updateColors('rgb', rgb, false); 1083 | }); 1084 | 1085 | // Delta E events. 1086 | 1087 | var del1 = document.getElementById('del1'); 1088 | var dea1 = document.getElementById('dea1'); 1089 | var deb1 = document.getElementById('deb1'); 1090 | 1091 | var del2 = document.getElementById('del2'); 1092 | var dea2 = document.getElementById('dea2'); 1093 | var deb2 = document.getElementById('deb2'); 1094 | 1095 | var de1976 = document.getElementById('de1976'); 1096 | var de1994ga = document.getElementById('de1994ga'); 1097 | var de1994t = document.getElementById('de1994t'); 1098 | var de2000 = document.getElementById('de2000'); 1099 | 1100 | function deChanged() 1101 | { 1102 | var L1 = parseInt(del1.value); 1103 | var a1 = parseInt(dea1.value); 1104 | var b1 = parseInt(deb1.value); 1105 | var L2 = parseInt(del2.value); 1106 | var a2 = parseInt(dea2.value); 1107 | var b2 = parseInt(deb2.value); 1108 | 1109 | if(L1 == NaN || a1 == NaN || b1 == NaN || L2 == NaN || a2 == NaN || b2 == NaN) 1110 | { 1111 | return; 1112 | } 1113 | 1114 | var lab1 = new ColorLab(L1, a1, b1); 1115 | var lch1 = lab1.toLChab(); 1116 | 1117 | var lab2 = new ColorLab(L2, a2, b2); 1118 | var lch2 = lab2.toLChab(); 1119 | 1120 | de1976.value = deltaE1976(lab1, lab2).toFixed(2); 1121 | de1994ga.value = deltaE1994(lab1, lab2, 'graphic arts').toFixed(2); 1122 | de1994t.value = deltaE1994(lab1, lab2, 'textiles').toFixed(2); 1123 | de2000.value = deltaE2000(lch1, lch2).toFixed(2); 1124 | } 1125 | 1126 | var deltaeinputs = [del1,dea1,deb1,del2,dea2,deb2]; 1127 | 1128 | for(i in deltaeinputs) 1129 | { 1130 | $(deltaeinputs[i]).change(deChanged); 1131 | $(deltaeinputs[i]).keyup(deChanged); 1132 | } 1133 | 1134 | // initialize data. 1135 | 1136 | Color.yuvmatrix = Color.yuvmatrices['bt709']; 1137 | updateColors('rgb', new ColorRGB(0.47, 0.76, 0.91), true); 1138 | 1139 | del1.value = 75; 1140 | dea1.value = -13; 1141 | deb1.value = -26; 1142 | 1143 | del2.value = 70; 1144 | dea2.value = -16; 1145 | deb2.value = -28; 1146 | 1147 | deChanged(); 1148 | 1149 | })(); 1150 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Color Tools 6 | 7 | 22 | 23 | 24 |

Color conversions

25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 39 | 42 | 43 | 44 |
45 |
46 |

Note: a clamped value means that the selected color is not representable within the gamut of that particular colorspace.

47 |

If sRGB is clamped, the color bar on the right is only showing the nearest approximation and not the true color.

48 | 49 |

Color difference

50 | 51 |

These attempt to measure the perceptual difference between two colors.

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
CIE L*a*b* 1
CIE L*a*b* 2
97 | 98 |
99 | © 2011 Cory Nelson
100 | The code is under the 2-clause BSD License. 101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | --------------------------------------------------------------------------------