Files
2025-11-25 17:41:31 +01:00

210 lines
8.1 KiB
C++

/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "material/cam/cam.h"
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "material/cam/hct_solver.h"
#include "material/cam/viewing_conditions.h"
#include "material/utils/utils.h"
namespace material_color_utilities {
Cam CamFromJchAndViewingConditions(double j, double c, double h,
ViewingConditions viewing_conditions);
Cam CamFromUcsAndViewingConditions(
double jstar, double astar, double bstar,
const ViewingConditions &viewing_conditions) {
const double a = astar;
const double b = bstar;
const double m = sqrt(a * a + b * b);
const double m_2 = (exp(m * 0.0228) - 1.0) / 0.0228;
const double c = m_2 / viewing_conditions.fl_root;
double h = atan2(b, a) * (180.0 / kPi);
if (h < 0.0) {
h += 360.0;
}
const double j = jstar / (1 - (jstar - 100) * 0.007);
return CamFromJchAndViewingConditions(j, c, h, viewing_conditions);
}
Cam CamFromIntAndViewingConditions(
Argb argb, const ViewingConditions &viewing_conditions) {
// XYZ from ARGB, inlined.
int red = (argb & 0x00ff0000) >> 16;
int green = (argb & 0x0000ff00) >> 8;
int blue = (argb & 0x000000ff);
double red_l = Linearized(red);
double green_l = Linearized(green);
double blue_l = Linearized(blue);
double x = 0.41233895 * red_l + 0.35762064 * green_l + 0.18051042 * blue_l;
double y = 0.2126 * red_l + 0.7152 * green_l + 0.0722 * blue_l;
double z = 0.01932141 * red_l + 0.11916382 * green_l + 0.95034478 * blue_l;
// Convert XYZ to 'cone'/'rgb' responses
double r_c = 0.401288 * x + 0.650173 * y - 0.051461 * z;
double g_c = -0.250268 * x + 1.204414 * y + 0.045854 * z;
double b_c = -0.002079 * x + 0.048952 * y + 0.953127 * z;
// Discount illuminant.
double r_d = viewing_conditions.rgb_d[0] * r_c;
double g_d = viewing_conditions.rgb_d[1] * g_c;
double b_d = viewing_conditions.rgb_d[2] * b_c;
// Chromatic adaptation.
double r_af = pow(viewing_conditions.fl * fabs(r_d) / 100.0, 0.42);
double g_af = pow(viewing_conditions.fl * fabs(g_d) / 100.0, 0.42);
double b_af = pow(viewing_conditions.fl * fabs(b_d) / 100.0, 0.42);
double r_a = Signum(r_d) * 400.0 * r_af / (r_af + 27.13);
double g_a = Signum(g_d) * 400.0 * g_af / (g_af + 27.13);
double b_a = Signum(b_d) * 400.0 * b_af / (b_af + 27.13);
// Redness-greenness
double a = (11.0 * r_a + -12.0 * g_a + b_a) / 11.0;
double b = (r_a + g_a - 2.0 * b_a) / 9.0;
double u = (20.0 * r_a + 20.0 * g_a + 21.0 * b_a) / 20.0;
double p2 = (40.0 * r_a + 20.0 * g_a + b_a) / 20.0;
double radians = atan2(b, a);
double degrees = radians * 180.0 / kPi;
double hue = SanitizeDegreesDouble(degrees);
double hue_radians = hue * kPi / 180.0;
double ac = p2 * viewing_conditions.nbb;
double j = 100.0 * pow(ac / viewing_conditions.aw,
viewing_conditions.c * viewing_conditions.z);
double q = (4.0 / viewing_conditions.c) * sqrt(j / 100.0) *
(viewing_conditions.aw + 4.0) * viewing_conditions.fl_root;
double hue_prime = hue < 20.14 ? hue + 360 : hue;
double e_hue = 0.25 * (cos(hue_prime * kPi / 180.0 + 2.0) + 3.8);
double p1 =
50000.0 / 13.0 * e_hue * viewing_conditions.n_c * viewing_conditions.ncb;
double t = p1 * sqrt(a * a + b * b) / (u + 0.305);
double alpha =
pow(t, 0.9) *
pow(1.64 - pow(0.29, viewing_conditions.background_y_to_white_point_y),
0.73);
double c = alpha * sqrt(j / 100.0);
double m = c * viewing_conditions.fl_root;
double s = 50.0 * sqrt((alpha * viewing_conditions.c) /
(viewing_conditions.aw + 4.0));
double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
double mstar = 1.0 / 0.0228 * log(1.0 + 0.0228 * m);
double astar = mstar * cos(hue_radians);
double bstar = mstar * sin(hue_radians);
return {hue, c, j, q, m, s, jstar, astar, bstar};
}
Cam CamFromInt(Argb argb) {
return CamFromIntAndViewingConditions(argb, kDefaultViewingConditions);
}
Argb IntFromCamAndViewingConditions(Cam cam,
ViewingConditions viewing_conditions) {
double alpha = (cam.chroma == 0.0 || cam.j == 0.0)
? 0.0
: cam.chroma / sqrt(cam.j / 100.0);
double t = pow(
alpha / pow(1.64 - pow(0.29,
viewing_conditions.background_y_to_white_point_y),
0.73),
1.0 / 0.9);
double h_rad = cam.hue * kPi / 180.0;
double e_hue = 0.25 * (cos(h_rad + 2.0) + 3.8);
double ac =
viewing_conditions.aw *
pow(cam.j / 100.0, 1.0 / viewing_conditions.c / viewing_conditions.z);
double p1 = e_hue * (50000.0 / 13.0) * viewing_conditions.n_c *
viewing_conditions.ncb;
double p2 = ac / viewing_conditions.nbb;
double h_sin = sin(h_rad);
double h_cos = cos(h_rad);
double gamma = 23.0 * (p2 + 0.305) * t /
(23.0 * p1 + 11.0 * t * h_cos + 108.0 * t * h_sin);
double a = gamma * h_cos;
double b = gamma * h_sin;
double r_a = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
double g_a = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
double b_a = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
double r_c_base = fmax(0, (27.13 * fabs(r_a)) / (400.0 - fabs(r_a)));
double r_c =
Signum(r_a) * (100.0 / viewing_conditions.fl) * pow(r_c_base, 1.0 / 0.42);
double g_c_base = fmax(0, (27.13 * fabs(g_a)) / (400.0 - fabs(g_a)));
double g_c =
Signum(g_a) * (100.0 / viewing_conditions.fl) * pow(g_c_base, 1.0 / 0.42);
double b_c_base = fmax(0, (27.13 * fabs(b_a)) / (400.0 - fabs(b_a)));
double b_c =
Signum(b_a) * (100.0 / viewing_conditions.fl) * pow(b_c_base, 1.0 / 0.42);
double r_x = r_c / viewing_conditions.rgb_d[0];
double g_x = g_c / viewing_conditions.rgb_d[1];
double b_x = b_c / viewing_conditions.rgb_d[2];
double x = 1.86206786 * r_x - 1.01125463 * g_x + 0.14918677 * b_x;
double y = 0.38752654 * r_x + 0.62144744 * g_x - 0.00897398 * b_x;
double z = -0.01584150 * r_x - 0.03412294 * g_x + 1.04996444 * b_x;
// intFromXyz
double r_l = 3.2406 * x - 1.5372 * y - 0.4986 * z;
double g_l = -0.9689 * x + 1.8758 * y + 0.0415 * z;
double b_l = 0.0557 * x - 0.2040 * y + 1.0570 * z;
int red = Delinearized(r_l);
int green = Delinearized(g_l);
int blue = Delinearized(b_l);
return ArgbFromRgb(red, green, blue);
}
Argb IntFromCam(Cam cam) {
return IntFromCamAndViewingConditions(cam, kDefaultViewingConditions);
}
Cam CamFromJchAndViewingConditions(double j, double c, double h,
ViewingConditions viewing_conditions) {
double q = (4.0 / viewing_conditions.c) * sqrt(j / 100.0) *
(viewing_conditions.aw + 4.0) * (viewing_conditions.fl_root);
double m = c * viewing_conditions.fl_root;
double alpha = c / sqrt(j / 100.0);
double s = 50.0 * sqrt((alpha * viewing_conditions.c) /
(viewing_conditions.aw + 4.0));
double hue_radians = h * kPi / 180.0;
double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
double mstar = 1.0 / 0.0228 * log(1.0 + 0.0228 * m);
double astar = mstar * cos(hue_radians);
double bstar = mstar * sin(hue_radians);
return {h, c, j, q, m, s, jstar, astar, bstar};
}
double CamDistance(Cam a, Cam b) {
double d_j = a.jstar - b.jstar;
double d_a = a.astar - b.astar;
double d_b = a.bstar - b.bstar;
double d_e_prime = sqrt(d_j * d_j + d_a * d_a + d_b * d_b);
double d_e = 1.41 * pow(d_e_prime, 0.63);
return d_e;
}
Argb IntFromHcl(double hue, double chroma, double lstar) {
return SolveToInt(hue, chroma, lstar);
}
} // namespace material_color_utilities