Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
IXtreme | ebfb6e239a | ||
IXtreme | 2869533db9 |
19
Makefile
19
Makefile
|
@ -18,7 +18,7 @@ LIB_DIR = lib
|
||||||
|
|
||||||
STATIC_INCLUDE_DIR = include
|
STATIC_INCLUDE_DIR = include
|
||||||
|
|
||||||
CPPFLAGS = -Isrc/imgui -g -D ASSET_DIR=\"$(ASSET_PREFIX)\"
|
CPPFLAGS = -Isrc/imgui -Isrc/material-colors -g -D ASSET_DIR=\"$(ASSET_PREFIX)\"
|
||||||
CFLAGS = -g -D ASSET_DIR=\"$(ASSET_PREFIX)\"
|
CFLAGS = -g -D ASSET_DIR=\"$(ASSET_PREFIX)\"
|
||||||
LDFLAGS = -g
|
LDFLAGS = -g
|
||||||
#global LDLIBS
|
#global LDLIBS
|
||||||
|
@ -51,6 +51,23 @@ C_SRC += $(wildcard $(SRC_DIR)/*.c) #GET LIST OF ALL C FILES
|
||||||
CPP_SRC += $(wildcard $(SRC_DIR)/*.cpp) #GET LIST OF ALL CPP FILES
|
CPP_SRC += $(wildcard $(SRC_DIR)/*.cpp) #GET LIST OF ALL CPP FILES
|
||||||
CPP_SRC += $(wildcard $(SRC_DIR)/rlImGui/*.cpp) #GET LIST OF ALL CPP FILES
|
CPP_SRC += $(wildcard $(SRC_DIR)/rlImGui/*.cpp) #GET LIST OF ALL CPP FILES
|
||||||
|
|
||||||
|
|
||||||
|
# BUILD MATERIAL_COLORS SYSETM
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/quantize/celebi.cpp)
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/quantize/wsmeans.cpp)
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/quantize/wu.cpp)
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/quantize/lab.cpp)
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/utils/utils.cpp)
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/cam/cam.cpp)
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/cam/hct.cpp)
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/cam/hct_solver.cpp)
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/cam/viewing_conditions.cpp)
|
||||||
|
CPP_SRC += $(wildcard $(SRC_DIR)/material-colors/cpp/score/score.cpp)
|
||||||
|
EXTRA_DIRS += $(OBJ_DIR)/material-colors/cpp/quantize
|
||||||
|
EXTRA_DIRS += $(OBJ_DIR)/material-colors/cpp/utils
|
||||||
|
EXTRA_DIRS += $(OBJ_DIR)/material-colors/cpp/score
|
||||||
|
EXTRA_DIRS += $(OBJ_DIR)/material-colors/cpp/cam
|
||||||
|
|
||||||
ifeq ($(IMGUI_MODE), BUILD)
|
ifeq ($(IMGUI_MODE), BUILD)
|
||||||
CPP_SRC += $(wildcard $(SRC_DIR)/imgui/*.cpp) #GET LIST OF ALL CPP FILES
|
CPP_SRC += $(wildcard $(SRC_DIR)/imgui/*.cpp) #GET LIST OF ALL CPP FILES
|
||||||
EXTRA_DIRS += $(OBJ_DIR)/imgui
|
EXTRA_DIRS += $(OBJ_DIR)/imgui
|
||||||
|
|
62
src/main.cpp
62
src/main.cpp
|
@ -7,8 +7,8 @@
|
||||||
#include <sdbus-c++/Types.h>
|
#include <sdbus-c++/Types.h>
|
||||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
#include "rlgl.h"
|
|
||||||
#include "raymath.h"
|
#include "raymath.h"
|
||||||
|
#include "rlgl.h"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -233,13 +233,17 @@ float ScaleToFit(Vector2 src, Vector2 dst) {
|
||||||
// return Vector2Scale(src, ratio);
|
// return Vector2Scale(src, ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenCircleOverlay(RenderTexture tex, bool trans) {
|
void GenCircleOverlay(MprisPlayer *current_player, RenderTexture tex, bool trans, bool accent) {
|
||||||
BeginTextureMode(tex);
|
BeginTextureMode(tex);
|
||||||
if (trans) {
|
if (trans) {
|
||||||
ClearBackground(WHITE);
|
ClearBackground(WHITE);
|
||||||
|
} else {
|
||||||
|
if (accent) {
|
||||||
|
ClearBackground(current_player->GetPrimaryColor());
|
||||||
} else {
|
} else {
|
||||||
ClearBackground(BLACK);
|
ClearBackground(BLACK);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
BeginBlendMode(BLEND_SUBTRACT_COLORS);
|
BeginBlendMode(BLEND_SUBTRACT_COLORS);
|
||||||
if (trans) {
|
if (trans) {
|
||||||
DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, std::min(GetScreenWidth() / 2, GetScreenHeight() / 2), WHITE);
|
DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, std::min(GetScreenWidth() / 2, GetScreenHeight() / 2), WHITE);
|
||||||
|
@ -258,7 +262,7 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
// SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE);
|
// SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE);
|
||||||
// SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE);
|
// SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE);
|
||||||
SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE);
|
SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT);
|
||||||
InitWindow(screenWidth, screenHeight, "raylib-Extras [ImGui] example - Docking");
|
InitWindow(screenWidth, screenHeight, "raylib-Extras [ImGui] example - Docking");
|
||||||
SetTargetFPS(244);
|
SetTargetFPS(244);
|
||||||
rlImGuiSetup(true);
|
rlImGuiSetup(true);
|
||||||
|
@ -284,17 +288,28 @@ int main(int argc, char *argv[]) {
|
||||||
// Main game loop
|
// Main game loop
|
||||||
bool goodstate = false;
|
bool goodstate = false;
|
||||||
bool trans = false;
|
bool trans = false;
|
||||||
|
bool accent = true;
|
||||||
|
|
||||||
|
long imagecount = -1;
|
||||||
|
|
||||||
|
bool showMetricsWindow = false;
|
||||||
|
|
||||||
RenderTexture recordoverlay = LoadRenderTexture(screenWidth, screenHeight);
|
RenderTexture recordoverlay = LoadRenderTexture(screenWidth, screenHeight);
|
||||||
GenCircleOverlay(recordoverlay, trans);
|
GenCircleOverlay(¤t_player, recordoverlay, trans, accent);
|
||||||
|
|
||||||
while (!WindowShouldClose() && run) // Detect window close button or ESC key, or a quit from the menu
|
while (!WindowShouldClose() && run) // Detect window close button or ESC key, or a quit from the menu
|
||||||
{
|
{
|
||||||
if (IsWindowResized()) {
|
if (IsWindowResized()) {
|
||||||
UnloadRenderTexture(recordoverlay);
|
UnloadRenderTexture(recordoverlay);
|
||||||
recordoverlay = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
|
recordoverlay = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
|
||||||
GenCircleOverlay(recordoverlay, trans);
|
GenCircleOverlay(¤t_player, recordoverlay, trans, accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (current_player.imagecount > imagecount) {
|
||||||
|
imagecount = current_player.imagecount;
|
||||||
|
GenCircleOverlay(¤t_player, recordoverlay, trans, accent);
|
||||||
|
}
|
||||||
|
|
||||||
if (GetTime() > lasttime + 3) {
|
if (GetTime() > lasttime + 3) {
|
||||||
lasttime = GetTime();
|
lasttime = GetTime();
|
||||||
current_player.Refresh();
|
current_player.Refresh();
|
||||||
|
@ -353,7 +368,7 @@ int main(int argc, char *argv[]) {
|
||||||
} else if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && CheckCollisionPointCircle(GetMousePosition(), back, 0.05 * GetScreenWidth())) {
|
} else if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && CheckCollisionPointCircle(GetMousePosition(), back, 0.05 * GetScreenWidth())) {
|
||||||
current_player.Prev();
|
current_player.Prev();
|
||||||
current_player.playstate = current_player.playstate == "Playing" ? "Paused" : "Playing";
|
current_player.playstate = current_player.playstate == "Playing" ? "Paused" : "Playing";
|
||||||
} else if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
|
} else if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && disc.enabled) {
|
||||||
float initmouserot;
|
float initmouserot;
|
||||||
Vector2 initmousepos;
|
Vector2 initmousepos;
|
||||||
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
||||||
|
@ -402,6 +417,7 @@ int main(int argc, char *argv[]) {
|
||||||
// start ImGui content
|
// start ImGui content
|
||||||
|
|
||||||
if (showIMgui) {
|
if (showIMgui) {
|
||||||
|
disc.enabled = false;
|
||||||
rlImGuiBegin();
|
rlImGuiBegin();
|
||||||
|
|
||||||
// if you want windows to dock to the viewport, call this.
|
// if you want windows to dock to the viewport, call this.
|
||||||
|
@ -421,6 +437,9 @@ int main(int argc, char *argv[]) {
|
||||||
current_player = MprisPlayer(item);
|
current_player = MprisPlayer(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ImGui::MenuItem("Refresh")) {
|
||||||
|
serviceList = GetMprisServices();
|
||||||
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,11 +451,26 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Trans Mode !!", nullptr, trans)) {
|
if (ImGui::MenuItem("Trans Mode !!", nullptr, trans)) {
|
||||||
trans = !trans;
|
trans = !trans;
|
||||||
GenCircleOverlay(recordoverlay, trans);
|
GenCircleOverlay(¤t_player, recordoverlay, trans, accent);
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Accent Mode !!", nullptr, accent)) {
|
||||||
|
accent = !accent;
|
||||||
|
GenCircleOverlay(¤t_player, recordoverlay, trans, accent);
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
if (ImGui::BeginMenu("Tools")) {
|
||||||
|
if (ImGui::MenuItem("Debug Window", nullptr, showMetricsWindow)) {
|
||||||
|
showMetricsWindow = !showMetricsWindow;
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::EndMainMenuBar();
|
ImGui::EndMainMenuBar();
|
||||||
|
|
||||||
|
if (showMetricsWindow) {
|
||||||
|
ImGui::ShowMetricsWindow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// show some windows
|
// show some windows
|
||||||
|
@ -466,6 +500,18 @@ int main(int argc, char *argv[]) {
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::LabelText("Disc Info", "P: %f, V: %f, A: %f", disc.pos, disc.velo, disc.accel);
|
ImGui::LabelText("Disc Info", "P: %f, V: %f, A: %f", disc.pos, disc.velo, disc.accel);
|
||||||
rlImGuiImageSize(¤t_player.tex.tex, 300, 300);
|
rlImGuiImageSize(¤t_player.tex.tex, 300, 300);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
int inc = 0;
|
||||||
|
for (auto obj : current_player.accent_colors) {
|
||||||
|
|
||||||
|
float col[3] = {0, 0, 0};
|
||||||
|
col[0] = ((float)obj.r) / 255;
|
||||||
|
col[1] = ((float)obj.g) / 255;
|
||||||
|
col[2] = ((float)obj.b) / 255;
|
||||||
|
ImGui::ColorEdit3(("lf" + std::to_string(inc)).c_str(), col);
|
||||||
|
inc++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
|
@ -474,6 +520,8 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
// end ImGui Content
|
// end ImGui Content
|
||||||
rlImGuiEnd();
|
rlImGuiEnd();
|
||||||
|
} else {
|
||||||
|
disc.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
EndDrawing();
|
EndDrawing();
|
||||||
|
|
71
src/material-colors/cpp/blend/blend.cpp
Normal file
71
src/material-colors/cpp/blend/blend.cpp
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/blend/blend.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "cpp/cam/cam.h"
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/cam/viewing_conditions.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
Argb BlendHarmonize(const Argb design_color, const Argb key_color) {
|
||||||
|
Hct from_hct(design_color);
|
||||||
|
Hct to_hct(key_color);
|
||||||
|
double difference_degrees = DiffDegrees(from_hct.get_hue(), to_hct.get_hue());
|
||||||
|
double rotation_degrees = std::min(difference_degrees * 0.5, 15.0);
|
||||||
|
double output_hue = SanitizeDegreesDouble(
|
||||||
|
from_hct.get_hue() +
|
||||||
|
rotation_degrees *
|
||||||
|
RotationDirection(from_hct.get_hue(), to_hct.get_hue()));
|
||||||
|
from_hct.set_hue(output_hue);
|
||||||
|
return from_hct.ToInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb BlendHctHue(const Argb from, const Argb to, const double amount) {
|
||||||
|
int ucs = BlendCam16Ucs(from, to, amount);
|
||||||
|
Hct ucs_hct(ucs);
|
||||||
|
Hct from_hct(from);
|
||||||
|
from_hct.set_hue(ucs_hct.get_hue());
|
||||||
|
return from_hct.ToInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb BlendCam16Ucs(const Argb from, const Argb to, const double amount) {
|
||||||
|
Cam from_cam = CamFromInt(from);
|
||||||
|
Cam to_cam = CamFromInt(to);
|
||||||
|
|
||||||
|
const double a_j = from_cam.jstar;
|
||||||
|
const double a_a = from_cam.astar;
|
||||||
|
const double a_b = from_cam.bstar;
|
||||||
|
|
||||||
|
const double b_j = to_cam.jstar;
|
||||||
|
const double b_a = to_cam.astar;
|
||||||
|
const double b_b = to_cam.bstar;
|
||||||
|
|
||||||
|
const double jstar = a_j + (b_j - a_j) * amount;
|
||||||
|
const double astar = a_a + (b_a - a_a) * amount;
|
||||||
|
const double bstar = a_b + (b_b - a_b) * amount;
|
||||||
|
|
||||||
|
const Cam blended = CamFromUcsAndViewingConditions(jstar, astar, bstar,
|
||||||
|
kDefaultViewingConditions);
|
||||||
|
return IntFromCam(blended);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
31
src/material-colors/cpp/blend/blend.h
Normal file
31
src/material-colors/cpp/blend/blend.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_BLEND_BLEND_H_
|
||||||
|
#define CPP_BLEND_BLEND_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
Argb BlendHarmonize(const Argb design_color, const Argb key_color);
|
||||||
|
Argb BlendHctHue(const Argb from, const Argb to, const double amount);
|
||||||
|
Argb BlendCam16Ucs(const Argb from, const Argb to, const double amount);
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
#endif // CPP_BLEND_BLEND_H_
|
31
src/material-colors/cpp/blend/blend_test.cpp
Normal file
31
src/material-colors/cpp/blend/blend_test.cpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/blend/blend.h"
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
TEST(BlendTest, RedToBlue) {
|
||||||
|
int blended = BlendHctHue(0xffff0000, 0xff0000ff, 0.8);
|
||||||
|
EXPECT_EQ(HexFromArgb(blended), "ff905eff");
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
263
src/material-colors/cpp/cam/cam.cpp
Normal file
263
src/material-colors/cpp/cam/cam.cpp
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/cam/cam.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct_solver.h"
|
||||||
|
#include "cpp/cam/viewing_conditions.h"
|
||||||
|
#include "cpp/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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cam CamFromXyzAndViewingConditions(
|
||||||
|
double x, double y, double z, const ViewingConditions &viewing_conditions) {
|
||||||
|
// 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};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
53
src/material-colors/cpp/cam/cam.h
Normal file
53
src/material-colors/cpp/cam/cam.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_CAM_CAM_H_
|
||||||
|
#define CPP_CAM_CAM_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/viewing_conditions.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct Cam {
|
||||||
|
double hue = 0.0;
|
||||||
|
double chroma = 0.0;
|
||||||
|
double j = 0.0;
|
||||||
|
double q = 0.0;
|
||||||
|
double m = 0.0;
|
||||||
|
double s = 0.0;
|
||||||
|
|
||||||
|
double jstar = 0.0;
|
||||||
|
double astar = 0.0;
|
||||||
|
double bstar = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Cam CamFromInt(Argb argb);
|
||||||
|
Cam CamFromIntAndViewingConditions(Argb argb,
|
||||||
|
const ViewingConditions &viewing_conditions);
|
||||||
|
Argb IntFromHcl(double hue, double chroma, double lstar);
|
||||||
|
Argb IntFromCam(Cam cam);
|
||||||
|
Cam CamFromUcsAndViewingConditions(double jstar, double astar, double bstar,
|
||||||
|
const ViewingConditions &viewing_conditions);
|
||||||
|
/**
|
||||||
|
* Given color expressed in the XYZ color space and viewed
|
||||||
|
* in [viewingConditions], converts the color to CAM16.
|
||||||
|
*/
|
||||||
|
Cam CamFromXyzAndViewingConditions(double x, double y, double z,
|
||||||
|
const ViewingConditions &viewing_conditions);
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
#endif // CPP_CAM_CAM_H_
|
109
src/material-colors/cpp/cam/cam_test.cpp
Normal file
109
src/material-colors/cpp/cam/cam_test.cpp
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/cam/cam.h"
|
||||||
|
|
||||||
|
#include "testing/base/public/gmock.h"
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using testing::DoubleNear;
|
||||||
|
|
||||||
|
using testing::Eq;
|
||||||
|
|
||||||
|
Argb RED = 0xffff0000;
|
||||||
|
Argb GREEN = 0xff00ff00;
|
||||||
|
Argb BLUE = 0xff0000ff;
|
||||||
|
Argb WHITE = 0xffffffff;
|
||||||
|
Argb BLACK = 0xff000000;
|
||||||
|
|
||||||
|
TEST(CamTest, Red) {
|
||||||
|
Cam cam = CamFromInt(RED);
|
||||||
|
|
||||||
|
EXPECT_THAT(cam.hue, DoubleNear(27.408, 0.001));
|
||||||
|
EXPECT_THAT(cam.chroma, DoubleNear(113.357, 0.001));
|
||||||
|
EXPECT_THAT(cam.j, DoubleNear(46.445, 0.001));
|
||||||
|
EXPECT_THAT(cam.m, DoubleNear(89.494, 0.001));
|
||||||
|
EXPECT_THAT(cam.s, DoubleNear(91.889, 0.001));
|
||||||
|
EXPECT_THAT(cam.q, DoubleNear(105.988, 0.001));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CamTest, Green) {
|
||||||
|
Cam cam = CamFromInt(GREEN);
|
||||||
|
|
||||||
|
EXPECT_THAT(cam.hue, DoubleNear(142.139, 0.001));
|
||||||
|
EXPECT_THAT(cam.chroma, DoubleNear(108.410, 0.001));
|
||||||
|
EXPECT_THAT(cam.j, DoubleNear(79.331, 0.001));
|
||||||
|
EXPECT_THAT(cam.m, DoubleNear(85.587, 0.001));
|
||||||
|
EXPECT_THAT(cam.s, DoubleNear(78.604, 0.001));
|
||||||
|
EXPECT_THAT(cam.q, DoubleNear(138.520, 0.001));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CamTest, Blue) {
|
||||||
|
Cam cam = CamFromInt(BLUE);
|
||||||
|
|
||||||
|
EXPECT_THAT(cam.hue, DoubleNear(282.788, 0.001));
|
||||||
|
EXPECT_THAT(cam.chroma, DoubleNear(87.230, 0.001));
|
||||||
|
EXPECT_THAT(cam.j, DoubleNear(25.465, 0.001));
|
||||||
|
EXPECT_THAT(cam.m, DoubleNear(68.867, 0.001));
|
||||||
|
EXPECT_THAT(cam.s, DoubleNear(93.674, 0.001));
|
||||||
|
EXPECT_THAT(cam.q, DoubleNear(78.481, 0.001));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CamTest, White) {
|
||||||
|
Cam cam = CamFromInt(WHITE);
|
||||||
|
|
||||||
|
EXPECT_THAT(cam.hue, DoubleNear(209.492, 0.001));
|
||||||
|
EXPECT_THAT(cam.chroma, DoubleNear(2.869, 0.001));
|
||||||
|
EXPECT_THAT(cam.j, DoubleNear(100.0, 0.001));
|
||||||
|
EXPECT_THAT(cam.m, DoubleNear(2.265, 0.001));
|
||||||
|
EXPECT_THAT(cam.s, DoubleNear(12.068, 0.001));
|
||||||
|
EXPECT_THAT(cam.q, DoubleNear(155.521, 0.001));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CamTest, Black) {
|
||||||
|
Cam cam = CamFromInt(BLACK);
|
||||||
|
|
||||||
|
EXPECT_THAT(cam.hue, DoubleNear(0.0, 0.001));
|
||||||
|
EXPECT_THAT(cam.chroma, DoubleNear(0.0, 0.001));
|
||||||
|
EXPECT_THAT(cam.j, DoubleNear(0.0, 0.001));
|
||||||
|
EXPECT_THAT(cam.m, DoubleNear(0.0, 0.001));
|
||||||
|
EXPECT_THAT(cam.s, DoubleNear(0.0, 0.001));
|
||||||
|
EXPECT_THAT(cam.q, DoubleNear(0.0, 0.001));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CamTest, RedRoundTrip) {
|
||||||
|
Cam cam = CamFromInt(RED);
|
||||||
|
Argb argb = IntFromCam(cam);
|
||||||
|
EXPECT_THAT(argb, Eq(RED));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CamTest, GreenRoundTrip) {
|
||||||
|
Cam cam = CamFromInt(GREEN);
|
||||||
|
Argb argb = IntFromCam(cam);
|
||||||
|
EXPECT_THAT(argb, Eq(GREEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CamTest, BlueRoundTrip) {
|
||||||
|
Cam cam = CamFromInt(BLUE);
|
||||||
|
Argb argb = IntFromCam(cam);
|
||||||
|
EXPECT_THAT(argb, Eq(BLUE));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
57
src/material-colors/cpp/cam/hct.cpp
Normal file
57
src/material-colors/cpp/cam/hct.cpp
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/cam/hct.h"
|
||||||
|
|
||||||
|
#include "cpp/cam/hct_solver.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
Hct::Hct(double hue, double chroma, double tone) {
|
||||||
|
SetInternalState(SolveToInt(hue, chroma, tone));
|
||||||
|
}
|
||||||
|
|
||||||
|
Hct::Hct(Argb argb) { SetInternalState(argb); }
|
||||||
|
|
||||||
|
double Hct::get_hue() const { return hue_; }
|
||||||
|
|
||||||
|
double Hct::get_chroma() const { return chroma_; }
|
||||||
|
|
||||||
|
double Hct::get_tone() const { return tone_; }
|
||||||
|
|
||||||
|
Argb Hct::ToInt() const { return argb_; }
|
||||||
|
|
||||||
|
void Hct::set_hue(double new_hue) {
|
||||||
|
SetInternalState(SolveToInt(new_hue, chroma_, tone_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hct::set_chroma(double new_chroma) {
|
||||||
|
SetInternalState(SolveToInt(hue_, new_chroma, tone_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hct::set_tone(double new_tone) {
|
||||||
|
SetInternalState(SolveToInt(hue_, chroma_, new_tone));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hct::SetInternalState(Argb argb) {
|
||||||
|
argb_ = argb;
|
||||||
|
Cam cam = CamFromInt(argb);
|
||||||
|
hue_ = cam.hue;
|
||||||
|
chroma_ = cam.chroma;
|
||||||
|
tone_ = LstarFromArgb(argb);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
137
src/material-colors/cpp/cam/hct.h
Normal file
137
src/material-colors/cpp/cam/hct.h
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_CAM_HCT_H_
|
||||||
|
#define CPP_CAM_HCT_H_
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HCT: hue, chroma, and tone.
|
||||||
|
*
|
||||||
|
* A color system built using CAM16 hue and chroma, and L* (lightness) from
|
||||||
|
* the L*a*b* color space, providing a perceptually accurate
|
||||||
|
* color measurement system that can also accurately render what colors
|
||||||
|
* will appear as in different lighting environments.
|
||||||
|
*
|
||||||
|
* Using L* creates a link between the color system, contrast, and thus
|
||||||
|
* accessibility. Contrast ratio depends on relative luminance, or Y in the XYZ
|
||||||
|
* color space. L*, or perceptual luminance can be calculated from Y.
|
||||||
|
*
|
||||||
|
* Unlike Y, L* is linear to human perception, allowing trivial creation of
|
||||||
|
* accurate color tones.
|
||||||
|
*
|
||||||
|
* Unlike contrast ratio, measuring contrast in L* is linear, and simple to
|
||||||
|
* calculate. A difference of 40 in HCT tone guarantees a contrast ratio >= 3.0,
|
||||||
|
* and a difference of 50 guarantees a contrast ratio >= 4.5.
|
||||||
|
*/
|
||||||
|
class Hct {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Creates an HCT color from hue, chroma, and tone.
|
||||||
|
*
|
||||||
|
* @param hue 0 <= hue < 360; invalid values are corrected.
|
||||||
|
* @param chroma >= 0; the maximum value of chroma depends on the hue
|
||||||
|
* and tone. May be lower than the requested chroma.
|
||||||
|
* @param tone 0 <= tone <= 100; invalid values are corrected.
|
||||||
|
* @return HCT representation of a color in default viewing conditions.
|
||||||
|
*/
|
||||||
|
Hct(double hue, double chroma, double tone);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an HCT color from a color.
|
||||||
|
*
|
||||||
|
* @param argb ARGB representation of a color.
|
||||||
|
* @return HCT representation of a color in default viewing conditions
|
||||||
|
*/
|
||||||
|
explicit Hct(Argb argb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the hue of the color.
|
||||||
|
*
|
||||||
|
* @return hue of the color, in degrees.
|
||||||
|
*/
|
||||||
|
double get_hue() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the chroma of the color.
|
||||||
|
*
|
||||||
|
* @return chroma of the color.
|
||||||
|
*/
|
||||||
|
double get_chroma() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tone of the color.
|
||||||
|
*
|
||||||
|
* @return tone of the color, satisfying 0 <= tone <= 100.
|
||||||
|
*/
|
||||||
|
double get_tone() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color in ARGB format.
|
||||||
|
*
|
||||||
|
* @return an integer, representing the color in ARGB format.
|
||||||
|
*/
|
||||||
|
Argb ToInt() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the hue of this color. Chroma may decrease because chroma has a
|
||||||
|
* different maximum for any given hue and tone.
|
||||||
|
*
|
||||||
|
* @param new_hue 0 <= new_hue < 360; invalid values are corrected.
|
||||||
|
*/
|
||||||
|
void set_hue(double new_hue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the chroma of this color. Chroma may decrease because chroma has a
|
||||||
|
* different maximum for any given hue and tone.
|
||||||
|
*
|
||||||
|
* @param new_chroma 0 <= new_chroma < ?
|
||||||
|
*/
|
||||||
|
void set_chroma(double new_chroma);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tone of this color. Chroma may decrease because chroma has a
|
||||||
|
* different maximum for any given hue and tone.
|
||||||
|
*
|
||||||
|
* @param new_tone 0 <= new_tone <= 100; invalid valids are corrected.
|
||||||
|
*/
|
||||||
|
void set_tone(double new_tone);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For using HCT as a key in a ordered map.
|
||||||
|
*/
|
||||||
|
bool operator<(const Hct& a) const { return hue_ < a.hue_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Sets the Hct object to represent an sRGB color.
|
||||||
|
*
|
||||||
|
* @param argb the new color as an integer in ARGB format.
|
||||||
|
*/
|
||||||
|
void SetInternalState(Argb argb);
|
||||||
|
|
||||||
|
double hue_ = 0.0;
|
||||||
|
double chroma_ = 0.0;
|
||||||
|
double tone_ = 0.0;
|
||||||
|
Argb argb_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_CAM_HCT_H_
|
526
src/material-colors/cpp/cam/hct_solver.cpp
Normal file
526
src/material-colors/cpp/cam/hct_solver.cpp
Normal file
|
@ -0,0 +1,526 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/cam/hct_solver.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "cpp/cam/viewing_conditions.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
constexpr double kScaledDiscountFromLinrgb[3][3] = {
|
||||||
|
{
|
||||||
|
0.001200833568784504,
|
||||||
|
0.002389694492170889,
|
||||||
|
0.0002795742885861124,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0.0005891086651375999,
|
||||||
|
0.0029785502573438758,
|
||||||
|
0.0003270666104008398,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0.00010146692491640572,
|
||||||
|
0.0005364214359186694,
|
||||||
|
0.0032979401770712076,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr double kLinrgbFromScaledDiscount[3][3] = {
|
||||||
|
{
|
||||||
|
1373.2198709594231,
|
||||||
|
-1100.4251190754821,
|
||||||
|
-7.278681089101213,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
-271.815969077903,
|
||||||
|
559.6580465940733,
|
||||||
|
-32.46047482791194,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
1.9622899599665666,
|
||||||
|
-57.173814538844006,
|
||||||
|
308.7233197812385,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr double kYFromLinrgb[3] = {0.2126, 0.7152, 0.0722};
|
||||||
|
|
||||||
|
constexpr double kCriticalPlanes[255] = {
|
||||||
|
0.015176349177441876, 0.045529047532325624, 0.07588174588720938,
|
||||||
|
0.10623444424209313, 0.13658714259697685, 0.16693984095186062,
|
||||||
|
0.19729253930674434, 0.2276452376616281, 0.2579979360165119,
|
||||||
|
0.28835063437139563, 0.3188300904430532, 0.350925934958123,
|
||||||
|
0.3848314933096426, 0.42057480301049466, 0.458183274052838,
|
||||||
|
0.4976837250274023, 0.5391024159806381, 0.5824650784040898,
|
||||||
|
0.6277969426914107, 0.6751227633498623, 0.7244668422128921,
|
||||||
|
0.775853049866786, 0.829304845476233, 0.8848452951698498,
|
||||||
|
0.942497089126609, 1.0022825574869039, 1.0642236851973577,
|
||||||
|
1.1283421258858297, 1.1946592148522128, 1.2631959812511864,
|
||||||
|
1.3339731595349034, 1.407011200216447, 1.4823302800086415,
|
||||||
|
1.5599503113873272, 1.6398909516233677, 1.7221716113234105,
|
||||||
|
1.8068114625156377, 1.8938294463134073, 1.9832442801866852,
|
||||||
|
2.075074464868551, 2.1693382909216234, 2.2660538449872063,
|
||||||
|
2.36523901573795, 2.4669114995532007, 2.5710888059345764,
|
||||||
|
2.6777882626779785, 2.7870270208169257, 2.898822059350997,
|
||||||
|
3.0131901897720907, 3.1301480604002863, 3.2497121605402226,
|
||||||
|
3.3718988244681087, 3.4967242352587946, 3.624204428461639,
|
||||||
|
3.754355295633311, 3.887192587735158, 4.022731918402185,
|
||||||
|
4.160988767090289, 4.301978482107941, 4.445716283538092,
|
||||||
|
4.592217266055746, 4.741496401646282, 4.893568542229298,
|
||||||
|
5.048448422192488, 5.20615066083972, 5.3666897647573375,
|
||||||
|
5.5300801301023865, 5.696336044816294, 5.865471690767354,
|
||||||
|
6.037501145825082, 6.212438385869475, 6.390297286737924,
|
||||||
|
6.571091626112461, 6.7548350853498045, 6.941541251256611,
|
||||||
|
7.131223617812143, 7.323895587840543, 7.5195704746346665,
|
||||||
|
7.7182615035334345, 7.919981813454504, 8.124744458384042,
|
||||||
|
8.332562408825165, 8.543448553206703, 8.757415699253682,
|
||||||
|
8.974476575321063, 9.194643831691977, 9.417930041841839,
|
||||||
|
9.644347703669503, 9.873909240696694, 10.106627003236781,
|
||||||
|
10.342513269534024, 10.58158024687427, 10.8238400726681,
|
||||||
|
11.069304815507364, 11.317986476196008, 11.569896988756009,
|
||||||
|
11.825048221409341, 12.083451977536606, 12.345119996613247,
|
||||||
|
12.610063955123938, 12.878295467455942, 13.149826086772048,
|
||||||
|
13.42466730586372, 13.702830557985108, 13.984327217668513,
|
||||||
|
14.269168601521828, 14.55736596900856, 14.848930523210871,
|
||||||
|
15.143873411576273, 15.44220572664832, 15.743938506781891,
|
||||||
|
16.04908273684337, 16.35764934889634, 16.66964922287304,
|
||||||
|
16.985093187232053, 17.30399201960269, 17.62635644741625,
|
||||||
|
17.95219714852476, 18.281524751807332, 18.614349837764564,
|
||||||
|
18.95068293910138, 19.290534541298456, 19.633915083172692,
|
||||||
|
19.98083495742689, 20.331304511189067, 20.685334046541502,
|
||||||
|
21.042933821039977, 21.404114048223256, 21.76888489811322,
|
||||||
|
22.137256497705877, 22.50923893145328, 22.884842241736916,
|
||||||
|
23.264076429332462, 23.6469514538663, 24.033477234264016,
|
||||||
|
24.42366364919083, 24.817520537484558, 25.21505769858089,
|
||||||
|
25.61628489293138, 26.021211842414342, 26.429848230738664,
|
||||||
|
26.842203703840827, 27.258287870275353, 27.678110301598522,
|
||||||
|
28.10168053274597, 28.529008062403893, 28.96010235337422,
|
||||||
|
29.39497283293396, 29.83362889318845, 30.276079891419332,
|
||||||
|
30.722335150426627, 31.172403958865512, 31.62629557157785,
|
||||||
|
32.08401920991837, 32.54558406207592, 33.010999283389665,
|
||||||
|
33.4802739966603, 33.953417292456834, 34.430438229418264,
|
||||||
|
34.911345834551085, 35.39614910352207, 35.88485700094671,
|
||||||
|
36.37747846067349, 36.87402238606382, 37.37449765026789,
|
||||||
|
37.87891309649659, 38.38727753828926, 38.89959975977785,
|
||||||
|
39.41588851594697, 39.93615253289054, 40.460400508064545,
|
||||||
|
40.98864111053629, 41.520882981230194, 42.05713473317016,
|
||||||
|
42.597404951718396, 43.141702194811224, 43.6900349931913,
|
||||||
|
44.24241185063697, 44.798841244188324, 45.35933162437017,
|
||||||
|
45.92389141541209, 46.49252901546552, 47.065252796817916,
|
||||||
|
47.64207110610409, 48.22299226451468, 48.808024568002054,
|
||||||
|
49.3971762874833, 49.9904556690408, 50.587870934119984,
|
||||||
|
51.189430279724725, 51.79514187861014, 52.40501387947288,
|
||||||
|
53.0190544071392, 53.637271562750364, 54.259673423945976,
|
||||||
|
54.88626804504493, 55.517063457223934, 56.15206766869424,
|
||||||
|
56.79128866487574, 57.43473440856916, 58.08241284012621,
|
||||||
|
58.734331877617365, 59.39049941699807, 60.05092333227251,
|
||||||
|
60.715611475655585, 61.38457167773311, 62.057811747619894,
|
||||||
|
62.7353394731159, 63.417162620860914, 64.10328893648692,
|
||||||
|
64.79372614476921, 65.48848194977529, 66.18756403501224,
|
||||||
|
66.89098006357258, 67.59873767827808, 68.31084450182222,
|
||||||
|
69.02730813691093, 69.74813616640164, 70.47333615344107,
|
||||||
|
71.20291564160104, 71.93688215501312, 72.67524319850172,
|
||||||
|
73.41800625771542, 74.16517879925733, 74.9167682708136,
|
||||||
|
75.67278210128072, 76.43322770089146, 77.1981124613393,
|
||||||
|
77.96744375590167, 78.74122893956174, 79.51947534912904,
|
||||||
|
80.30219030335869, 81.08938110306934, 81.88105503125999,
|
||||||
|
82.67721935322541, 83.4778813166706, 84.28304815182372,
|
||||||
|
85.09272707154808, 85.90692527145302, 86.72564993000343,
|
||||||
|
87.54890820862819, 88.3767072518277, 89.2090541872801,
|
||||||
|
90.04595612594655, 90.88742016217518, 91.73345337380438,
|
||||||
|
92.58406282226491, 93.43925555268066, 94.29903859396902,
|
||||||
|
95.16341895893969, 96.03240364439274, 96.9059996312159,
|
||||||
|
97.78421388448044, 98.6670533535366, 99.55452497210776,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes a small enough angle in radians.
|
||||||
|
*
|
||||||
|
* @param angle An angle in radians; must not deviate too much from 0.
|
||||||
|
* @return A coterminal angle between 0 and 2pi.
|
||||||
|
*/
|
||||||
|
double SanitizeRadians(double angle) { return fmod(angle + kPi * 8, kPi * 2); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delinearizes an RGB component, returning a floating-point number.
|
||||||
|
*
|
||||||
|
* @param rgb_component 0.0 <= rgb_component <= 100.0, represents linear R/G/B
|
||||||
|
* channel
|
||||||
|
* @return 0.0 <= output <= 255.0, color channel converted to regular RGB space
|
||||||
|
*/
|
||||||
|
double TrueDelinearized(double rgb_component) {
|
||||||
|
double normalized = rgb_component / 100.0;
|
||||||
|
double delinearized = 0.0;
|
||||||
|
if (normalized <= 0.0031308) {
|
||||||
|
delinearized = normalized * 12.92;
|
||||||
|
} else {
|
||||||
|
delinearized = 1.055 * pow(normalized, 1.0 / 2.4) - 0.055;
|
||||||
|
}
|
||||||
|
return delinearized * 255.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ChromaticAdaptation(double component) {
|
||||||
|
double af = pow(abs(component), 0.42);
|
||||||
|
return Signum(component) * 400.0 * af / (af + 27.13);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the hue of a linear RGB color in CAM16.
|
||||||
|
*
|
||||||
|
* @param linrgb The linear RGB coordinates of a color.
|
||||||
|
* @return The hue of the color in CAM16, in radians.
|
||||||
|
*/
|
||||||
|
double HueOf(Vec3 linrgb) {
|
||||||
|
Vec3 scaledDiscount = MatrixMultiply(linrgb, kScaledDiscountFromLinrgb);
|
||||||
|
double r_a = ChromaticAdaptation(scaledDiscount.a);
|
||||||
|
double g_a = ChromaticAdaptation(scaledDiscount.b);
|
||||||
|
double b_a = ChromaticAdaptation(scaledDiscount.c);
|
||||||
|
// redness-greenness
|
||||||
|
double a = (11.0 * r_a + -12.0 * g_a + b_a) / 11.0;
|
||||||
|
// yellowness-blueness
|
||||||
|
double b = (r_a + g_a - 2.0 * b_a) / 9.0;
|
||||||
|
return atan2(b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AreInCyclicOrder(double a, double b, double c) {
|
||||||
|
double delta_a_b = SanitizeRadians(b - a);
|
||||||
|
double delta_a_c = SanitizeRadians(c - a);
|
||||||
|
return delta_a_b < delta_a_c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solves the lerp equation.
|
||||||
|
*
|
||||||
|
* @param source The starting number.
|
||||||
|
* @param mid The number in the middle.
|
||||||
|
* @param target The ending number.
|
||||||
|
* @return A number t such that lerp(source, target, t) = mid.
|
||||||
|
*/
|
||||||
|
double Intercept(double source, double mid, double target) {
|
||||||
|
return (mid - source) / (target - source);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 LerpPoint(Vec3 source, double t, Vec3 target) {
|
||||||
|
return (Vec3){
|
||||||
|
source.a + (target.a - source.a) * t,
|
||||||
|
source.b + (target.b - source.b) * t,
|
||||||
|
source.c + (target.c - source.c) * t,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
double GetAxis(Vec3 vector, int axis) {
|
||||||
|
switch (axis) {
|
||||||
|
case 0:
|
||||||
|
return vector.a;
|
||||||
|
case 1:
|
||||||
|
return vector.b;
|
||||||
|
case 2:
|
||||||
|
return vector.c;
|
||||||
|
default:
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intersects a segment with a plane.
|
||||||
|
*
|
||||||
|
* @param source The coordinates of point A.
|
||||||
|
* @param coordinate The R-, G-, or B-coordinate of the plane.
|
||||||
|
* @param target The coordinates of point B.
|
||||||
|
* @param axis The axis the plane is perpendicular with. (0: R, 1: G, 2: B)
|
||||||
|
* @return The intersection point of the segment AB with the plane R=coordinate,
|
||||||
|
* G=coordinate, or B=coordinate
|
||||||
|
*/
|
||||||
|
Vec3 SetCoordinate(Vec3 source, double coordinate, Vec3 target, int axis) {
|
||||||
|
double t =
|
||||||
|
Intercept(GetAxis(source, axis), coordinate, GetAxis(target, axis));
|
||||||
|
return LerpPoint(source, t, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsBounded(double x) { return 0.0 <= x && x <= 100.0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the nth possible vertex of the polygonal intersection.
|
||||||
|
*
|
||||||
|
* @param y The Y value of the plane.
|
||||||
|
* @param n The zero-based index of the point. 0 <= n <= 11.
|
||||||
|
* @return The nth possible vertex of the polygonal intersection of the y plane
|
||||||
|
* and the RGB cube, in linear RGB coordinates, if it exists. If this possible
|
||||||
|
* vertex lies outside of the cube,
|
||||||
|
* [-1.0, -1.0, -1.0] is returned.
|
||||||
|
*/
|
||||||
|
Vec3 NthVertex(double y, int n) {
|
||||||
|
double k_r = kYFromLinrgb[0];
|
||||||
|
double k_g = kYFromLinrgb[1];
|
||||||
|
double k_b = kYFromLinrgb[2];
|
||||||
|
double coord_a = n % 4 <= 1 ? 0.0 : 100.0;
|
||||||
|
double coord_b = n % 2 == 0 ? 0.0 : 100.0;
|
||||||
|
if (n < 4) {
|
||||||
|
double g = coord_a;
|
||||||
|
double b = coord_b;
|
||||||
|
double r = (y - g * k_g - b * k_b) / k_r;
|
||||||
|
if (IsBounded(r)) {
|
||||||
|
return (Vec3){r, g, b};
|
||||||
|
} else {
|
||||||
|
return (Vec3){-1.0, -1.0, -1.0};
|
||||||
|
}
|
||||||
|
} else if (n < 8) {
|
||||||
|
double b = coord_a;
|
||||||
|
double r = coord_b;
|
||||||
|
double g = (y - r * k_r - b * k_b) / k_g;
|
||||||
|
if (IsBounded(g)) {
|
||||||
|
return (Vec3){r, g, b};
|
||||||
|
} else {
|
||||||
|
return (Vec3){-1.0, -1.0, -1.0};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
double r = coord_a;
|
||||||
|
double g = coord_b;
|
||||||
|
double b = (y - r * k_r - g * k_g) / k_b;
|
||||||
|
if (IsBounded(b)) {
|
||||||
|
return (Vec3){r, g, b};
|
||||||
|
} else {
|
||||||
|
return (Vec3){-1.0, -1.0, -1.0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the segment containing the desired color.
|
||||||
|
*
|
||||||
|
* @param y The Y value of the color.
|
||||||
|
* @param target_hue The hue of the color.
|
||||||
|
* @return A list of two sets of linear RGB coordinates, each corresponding to
|
||||||
|
* an endpoint of the segment containing the desired color.
|
||||||
|
*/
|
||||||
|
void BisectToSegment(double y, double target_hue, Vec3 out[2]) {
|
||||||
|
Vec3 left = (Vec3){-1.0, -1.0, -1.0};
|
||||||
|
Vec3 right = left;
|
||||||
|
double left_hue = 0.0;
|
||||||
|
double right_hue = 0.0;
|
||||||
|
bool initialized = false;
|
||||||
|
bool uncut = true;
|
||||||
|
for (int n = 0; n < 12; n++) {
|
||||||
|
Vec3 mid = NthVertex(y, n);
|
||||||
|
if (mid.a < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
double mid_hue = HueOf(mid);
|
||||||
|
if (!initialized) {
|
||||||
|
left = mid;
|
||||||
|
right = mid;
|
||||||
|
left_hue = mid_hue;
|
||||||
|
right_hue = mid_hue;
|
||||||
|
initialized = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (uncut || AreInCyclicOrder(left_hue, mid_hue, right_hue)) {
|
||||||
|
uncut = false;
|
||||||
|
if (AreInCyclicOrder(left_hue, target_hue, mid_hue)) {
|
||||||
|
right = mid;
|
||||||
|
right_hue = mid_hue;
|
||||||
|
} else {
|
||||||
|
left = mid;
|
||||||
|
left_hue = mid_hue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out[0] = left;
|
||||||
|
out[1] = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 Midpoint(Vec3 a, Vec3 b) {
|
||||||
|
return (Vec3){
|
||||||
|
(a.a + b.a) / 2,
|
||||||
|
(a.b + b.b) / 2,
|
||||||
|
(a.c + b.c) / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
int CriticalPlaneBelow(double x) { return (int)floor(x - 0.5); }
|
||||||
|
|
||||||
|
int CriticalPlaneAbove(double x) { return (int)ceil(x - 0.5); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a color with the given Y and hue on the boundary of the cube.
|
||||||
|
*
|
||||||
|
* @param y The Y value of the color.
|
||||||
|
* @param target_hue The hue of the color.
|
||||||
|
* @return The desired color, in linear RGB coordinates.
|
||||||
|
*/
|
||||||
|
Vec3 BisectToLimit(double y, double target_hue) {
|
||||||
|
Vec3 segment[2];
|
||||||
|
BisectToSegment(y, target_hue, segment);
|
||||||
|
Vec3 left = segment[0];
|
||||||
|
double left_hue = HueOf(left);
|
||||||
|
Vec3 right = segment[1];
|
||||||
|
for (int axis = 0; axis < 3; axis++) {
|
||||||
|
if (GetAxis(left, axis) != GetAxis(right, axis)) {
|
||||||
|
int l_plane = -1;
|
||||||
|
int r_plane = 255;
|
||||||
|
if (GetAxis(left, axis) < GetAxis(right, axis)) {
|
||||||
|
l_plane = CriticalPlaneBelow(TrueDelinearized(GetAxis(left, axis)));
|
||||||
|
r_plane = CriticalPlaneAbove(TrueDelinearized(GetAxis(right, axis)));
|
||||||
|
} else {
|
||||||
|
l_plane = CriticalPlaneAbove(TrueDelinearized(GetAxis(left, axis)));
|
||||||
|
r_plane = CriticalPlaneBelow(TrueDelinearized(GetAxis(right, axis)));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (abs(r_plane - l_plane) <= 1) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
int m_plane = (int)floor((l_plane + r_plane) / 2.0);
|
||||||
|
double mid_plane_coordinate = kCriticalPlanes[m_plane];
|
||||||
|
Vec3 mid = SetCoordinate(left, mid_plane_coordinate, right, axis);
|
||||||
|
double mid_hue = HueOf(mid);
|
||||||
|
if (AreInCyclicOrder(left_hue, target_hue, mid_hue)) {
|
||||||
|
right = mid;
|
||||||
|
r_plane = m_plane;
|
||||||
|
} else {
|
||||||
|
left = mid;
|
||||||
|
left_hue = mid_hue;
|
||||||
|
l_plane = m_plane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Midpoint(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
double InverseChromaticAdaptation(double adapted) {
|
||||||
|
double adapted_abs = abs(adapted);
|
||||||
|
double base = fmax(0, 27.13 * adapted_abs / (400.0 - adapted_abs));
|
||||||
|
return Signum(adapted) * pow(base, 1.0 / 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a color with the given hue, chroma, and Y.
|
||||||
|
*
|
||||||
|
* @param hue_radians The desired hue in radians.
|
||||||
|
* @param chroma The desired chroma.
|
||||||
|
* @param y The desired Y.
|
||||||
|
* @return The desired color as a hexadecimal integer, if found; 0 otherwise.
|
||||||
|
*/
|
||||||
|
Argb FindResultByJ(double hue_radians, double chroma, double y) {
|
||||||
|
// Initial estimate of j.
|
||||||
|
double j = sqrt(y) * 11.0;
|
||||||
|
// ===========================================================
|
||||||
|
// Operations inlined from Cam16 to avoid repeated calculation
|
||||||
|
// ===========================================================
|
||||||
|
ViewingConditions viewing_conditions = kDefaultViewingConditions;
|
||||||
|
double t_inner_coeff =
|
||||||
|
1 /
|
||||||
|
pow(1.64 - pow(0.29, viewing_conditions.background_y_to_white_point_y),
|
||||||
|
0.73);
|
||||||
|
double e_hue = 0.25 * (cos(hue_radians + 2.0) + 3.8);
|
||||||
|
double p1 = e_hue * (50000.0 / 13.0) * viewing_conditions.n_c *
|
||||||
|
viewing_conditions.ncb;
|
||||||
|
double h_sin = sin(hue_radians);
|
||||||
|
double h_cos = cos(hue_radians);
|
||||||
|
for (int iteration_round = 0; iteration_round < 5; iteration_round++) {
|
||||||
|
// ===========================================================
|
||||||
|
// Operations inlined from Cam16 to avoid repeated calculation
|
||||||
|
// ===========================================================
|
||||||
|
double j_normalized = j / 100.0;
|
||||||
|
double alpha =
|
||||||
|
chroma == 0.0 || j == 0.0 ? 0.0 : chroma / sqrt(j_normalized);
|
||||||
|
double t = pow(alpha * t_inner_coeff, 1.0 / 0.9);
|
||||||
|
double ac =
|
||||||
|
viewing_conditions.aw *
|
||||||
|
pow(j_normalized, 1.0 / viewing_conditions.c / viewing_conditions.z);
|
||||||
|
double p2 = ac / viewing_conditions.nbb;
|
||||||
|
double gamma = 23.0 * (p2 + 0.305) * t /
|
||||||
|
(23.0 * p1 + 11 * 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_scaled = InverseChromaticAdaptation(r_a);
|
||||||
|
double g_c_scaled = InverseChromaticAdaptation(g_a);
|
||||||
|
double b_c_scaled = InverseChromaticAdaptation(b_a);
|
||||||
|
Vec3 scaled = (Vec3){r_c_scaled, g_c_scaled, b_c_scaled};
|
||||||
|
Vec3 linrgb = MatrixMultiply(scaled, kLinrgbFromScaledDiscount);
|
||||||
|
// ===========================================================
|
||||||
|
// Operations inlined from Cam16 to avoid repeated calculation
|
||||||
|
// ===========================================================
|
||||||
|
if (linrgb.a < 0 || linrgb.b < 0 || linrgb.c < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
double k_r = kYFromLinrgb[0];
|
||||||
|
double k_g = kYFromLinrgb[1];
|
||||||
|
double k_b = kYFromLinrgb[2];
|
||||||
|
double fnj = k_r * linrgb.a + k_g * linrgb.b + k_b * linrgb.c;
|
||||||
|
if (fnj <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (iteration_round == 4 || abs(fnj - y) < 0.002) {
|
||||||
|
if (linrgb.a > 100.01 || linrgb.b > 100.01 || linrgb.c > 100.01) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return ArgbFromLinrgb(linrgb);
|
||||||
|
}
|
||||||
|
// Iterates with Newton method,
|
||||||
|
// Using 2 * fn(j) / j as the approximation of fn'(j)
|
||||||
|
j = j - (fnj - y) * j / (2 * fnj);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an sRGB color with the given hue, chroma, and L*, if possible.
|
||||||
|
*
|
||||||
|
* @param hue_degrees The desired hue, in degrees.
|
||||||
|
* @param chroma The desired chroma.
|
||||||
|
* @param lstar The desired L*.
|
||||||
|
* @return A hexadecimal representing the sRGB color. The color has sufficiently
|
||||||
|
* close hue, chroma, and L* to the desired values, if possible; otherwise, the
|
||||||
|
* hue and L* will be sufficiently close, and chroma will be maximized.
|
||||||
|
*/
|
||||||
|
Argb SolveToInt(double hue_degrees, double chroma, double lstar) {
|
||||||
|
if (chroma < 0.0001 || lstar < 0.0001 || lstar > 99.9999) {
|
||||||
|
return IntFromLstar(lstar);
|
||||||
|
}
|
||||||
|
hue_degrees = SanitizeDegreesDouble(hue_degrees);
|
||||||
|
double hue_radians = hue_degrees / 180 * kPi;
|
||||||
|
double y = YFromLstar(lstar);
|
||||||
|
Argb exact_answer = FindResultByJ(hue_radians, chroma, y);
|
||||||
|
if (exact_answer != 0) {
|
||||||
|
return exact_answer;
|
||||||
|
}
|
||||||
|
Vec3 linrgb = BisectToLimit(y, hue_radians);
|
||||||
|
return ArgbFromLinrgb(linrgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an sRGB color with the given hue, chroma, and L*, if possible.
|
||||||
|
*
|
||||||
|
* @param hue_degrees The desired hue, in degrees.
|
||||||
|
* @param chroma The desired chroma.
|
||||||
|
* @param lstar The desired L*.
|
||||||
|
* @return An CAM16 object representing the sRGB color. The color has
|
||||||
|
* sufficiently close hue, chroma, and L* to the desired values, if possible;
|
||||||
|
* otherwise, the hue and L* will be sufficiently close, and chroma will be
|
||||||
|
* maximized.
|
||||||
|
*/
|
||||||
|
Cam SolveToCam(double hue_degrees, double chroma, double lstar) {
|
||||||
|
return CamFromInt(SolveToInt(hue_degrees, chroma, lstar));
|
||||||
|
}
|
||||||
|
} // namespace material_color_utilities
|
28
src/material-colors/cpp/cam/hct_solver.h
Normal file
28
src/material-colors/cpp/cam/hct_solver.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_CAM_HCT_SOLVER_H_
|
||||||
|
#define CPP_CAM_HCT_SOLVER_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/cam.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
Argb SolveToInt(double hue_degrees, double chroma, double lstar);
|
||||||
|
Cam SolveToCam(double hue_degrees, double chroma, double lstar);
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
#endif // CPP_CAM_HCT_SOLVER_H_
|
74
src/material-colors/cpp/cam/hct_solver_test.cpp
Normal file
74
src/material-colors/cpp/cam/hct_solver_test.cpp
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/cam/hct_solver.h"
|
||||||
|
|
||||||
|
#include "testing/base/public/gmock.h"
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
#include "cpp/cam/cam.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using testing::Eq;
|
||||||
|
|
||||||
|
TEST(HctSolverTest, Red) {
|
||||||
|
// Compute HCT
|
||||||
|
Argb color = 0xFFFE0315;
|
||||||
|
Cam cam = CamFromInt(color);
|
||||||
|
double tone = LstarFromArgb(color);
|
||||||
|
|
||||||
|
// Compute input
|
||||||
|
Argb recovered = SolveToInt(cam.hue, cam.chroma, tone);
|
||||||
|
EXPECT_THAT(recovered, Eq(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HctSolverTest, Green) {
|
||||||
|
// Compute HCT
|
||||||
|
Argb color = 0xFF15FE03;
|
||||||
|
Cam cam = CamFromInt(color);
|
||||||
|
double tone = LstarFromArgb(color);
|
||||||
|
|
||||||
|
// Compute input
|
||||||
|
Argb recovered = SolveToInt(cam.hue, cam.chroma, tone);
|
||||||
|
EXPECT_THAT(recovered, Eq(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HctSolverTest, Blue) {
|
||||||
|
// Compute HCT
|
||||||
|
Argb color = 0xFF0315FE;
|
||||||
|
Cam cam = CamFromInt(color);
|
||||||
|
double tone = LstarFromArgb(color);
|
||||||
|
|
||||||
|
// Compute input
|
||||||
|
Argb recovered = SolveToInt(cam.hue, cam.chroma, tone);
|
||||||
|
EXPECT_THAT(recovered, Eq(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HctSolverTest, Exhaustive) {
|
||||||
|
for (int colorIndex = 0; colorIndex <= 0xFFFFFF; colorIndex++) {
|
||||||
|
Argb color = 0xFF000000 | colorIndex;
|
||||||
|
Cam cam = CamFromInt(color);
|
||||||
|
double tone = LstarFromArgb(color);
|
||||||
|
|
||||||
|
// Compute input
|
||||||
|
Argb recovered = SolveToInt(cam.hue, cam.chroma, tone);
|
||||||
|
EXPECT_THAT(recovered, Eq(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
100
src/material-colors/cpp/cam/hct_test.cpp
Normal file
100
src/material-colors/cpp/cam/hct_test.cpp
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/cam/hct.h"
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "testing/base/public/gmock.h"
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
#include "cpp/cam/cam.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using ::testing::Combine;
|
||||||
|
using ::testing::DoubleNear;
|
||||||
|
using ::testing::Eq;
|
||||||
|
using ::testing::Lt;
|
||||||
|
using ::testing::TestWithParam;
|
||||||
|
using ::testing::Values;
|
||||||
|
|
||||||
|
TEST(HctTest, LimitedToSRGB) {
|
||||||
|
// Ensures that the HCT class can only represent sRGB colors.
|
||||||
|
// An impossibly high chroma is used.
|
||||||
|
Hct hct(/*hue=*/120.0, /*chroma=*/200.0, /*tone=*/50.0);
|
||||||
|
Argb argb = hct.ToInt();
|
||||||
|
|
||||||
|
// The hue, chroma, and tone members of hct should actually
|
||||||
|
// represent the sRGB color.
|
||||||
|
EXPECT_THAT(CamFromInt(argb).hue, Eq(hct.get_hue()));
|
||||||
|
EXPECT_THAT(CamFromInt(argb).chroma, Eq(hct.get_chroma()));
|
||||||
|
EXPECT_THAT(LstarFromArgb(argb), Eq(hct.get_tone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HctTest, TruncatesColors) {
|
||||||
|
// Ensures that HCT truncates colors.
|
||||||
|
Hct hct(/*hue=*/120.0, /*chroma=*/60.0, /*tone=*/50.0);
|
||||||
|
double chroma = hct.get_chroma();
|
||||||
|
EXPECT_THAT(chroma, Lt(60.0));
|
||||||
|
|
||||||
|
// The new chroma should be lower than the original.
|
||||||
|
hct.set_tone(180.0);
|
||||||
|
EXPECT_THAT(hct.get_chroma(), Lt(chroma));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsOnBoundary(int rgb_component) {
|
||||||
|
return rgb_component == 0 || rgb_component == 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorIsOnBoundary(int argb) {
|
||||||
|
return IsOnBoundary(RedFromInt(argb)) || IsOnBoundary(GreenFromInt(argb)) ||
|
||||||
|
IsOnBoundary(BlueFromInt(argb));
|
||||||
|
}
|
||||||
|
|
||||||
|
using HctTest = TestWithParam<std::tuple<int, int, int>>;
|
||||||
|
|
||||||
|
TEST_P(HctTest, Correctness) {
|
||||||
|
std::tuple<int, int, int> hctTuple = GetParam();
|
||||||
|
int hue = std::get<0>(hctTuple);
|
||||||
|
int chroma = std::get<1>(hctTuple);
|
||||||
|
int tone = std::get<2>(hctTuple);
|
||||||
|
|
||||||
|
Hct color(hue, chroma, tone);
|
||||||
|
|
||||||
|
if (chroma > 0) {
|
||||||
|
EXPECT_THAT(color.get_hue(), DoubleNear(hue, 4.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(color.get_chroma(), Lt(chroma + 2.5));
|
||||||
|
|
||||||
|
if (color.get_chroma() < chroma - 2.5) {
|
||||||
|
EXPECT_TRUE(ColorIsOnBoundary(color.ToInt()));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_THAT(color.get_tone(), DoubleNear(tone, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(
|
||||||
|
HctTests, HctTest,
|
||||||
|
Combine(/*hues*/ Values(15, 45, 75, 105, 135, 165, 195, 225, 255, 285, 315,
|
||||||
|
345),
|
||||||
|
/*chromas*/ Values(0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100),
|
||||||
|
/*tones*/ Values(20, 30, 40, 50, 60, 70, 80)));
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
118
src/material-colors/cpp/cam/viewing_conditions.cpp
Normal file
118
src/material-colors/cpp/cam/viewing_conditions.cpp
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/cam/viewing_conditions.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
static double lerp(double start, double stop, double amount) {
|
||||||
|
return (1.0 - amount) * start + amount * stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewingConditions CreateViewingConditions(const double white_point[3],
|
||||||
|
const double adapting_luminance,
|
||||||
|
const double background_lstar,
|
||||||
|
const double surround,
|
||||||
|
const bool discounting_illuminant) {
|
||||||
|
double background_lstar_corrected =
|
||||||
|
(background_lstar < 30.0) ? 30.0 : background_lstar;
|
||||||
|
double rgb_w[3] = {
|
||||||
|
0.401288 * white_point[0] + 0.650173 * white_point[1] -
|
||||||
|
0.051461 * white_point[2],
|
||||||
|
-0.250268 * white_point[0] + 1.204414 * white_point[1] +
|
||||||
|
0.045854 * white_point[2],
|
||||||
|
-0.002079 * white_point[0] + 0.048952 * white_point[1] +
|
||||||
|
0.953127 * white_point[2],
|
||||||
|
};
|
||||||
|
double f = 0.8 + (surround / 10.0);
|
||||||
|
double c = f >= 0.9 ? lerp(0.59, 0.69, (f - 0.9) * 10.0)
|
||||||
|
: lerp(0.525, 0.59, (f - 0.8) * 10.0);
|
||||||
|
double d = discounting_illuminant
|
||||||
|
? 1.0
|
||||||
|
: f * (1.0 - ((1.0 / 3.6) *
|
||||||
|
exp((-adapting_luminance - 42.0) / 92.0)));
|
||||||
|
d = d > 1.0 ? 1.0 : d < 0.0 ? 0.0 : d;
|
||||||
|
double nc = f;
|
||||||
|
double rgb_d[3] = {(d * (100.0 / rgb_w[0]) + 1.0 - d),
|
||||||
|
(d * (100.0 / rgb_w[1]) + 1.0 - d),
|
||||||
|
(d * (100.0 / rgb_w[2]) + 1.0 - d)};
|
||||||
|
|
||||||
|
double k = 1.0 / (5.0 * adapting_luminance + 1.0);
|
||||||
|
double k4 = k * k * k * k;
|
||||||
|
double k4f = 1.0 - k4;
|
||||||
|
double fl = (k4 * adapting_luminance) +
|
||||||
|
(0.1 * k4f * k4f * pow(5.0 * adapting_luminance, 1.0 / 3.0));
|
||||||
|
double fl_root = pow(fl, 0.25);
|
||||||
|
double n = YFromLstar(background_lstar_corrected) / white_point[1];
|
||||||
|
double z = 1.48 + sqrt(n);
|
||||||
|
double nbb = 0.725 / pow(n, 0.2);
|
||||||
|
double ncb = nbb;
|
||||||
|
double rgb_a_factors[3] = {pow(fl * rgb_d[0] * rgb_w[0] / 100.0, 0.42),
|
||||||
|
pow(fl * rgb_d[1] * rgb_w[1] / 100.0, 0.42),
|
||||||
|
pow(fl * rgb_d[2] * rgb_w[2] / 100.0, 0.42)};
|
||||||
|
double rgb_a[3] = {
|
||||||
|
400.0 * rgb_a_factors[0] / (rgb_a_factors[0] + 27.13),
|
||||||
|
400.0 * rgb_a_factors[1] / (rgb_a_factors[1] + 27.13),
|
||||||
|
400.0 * rgb_a_factors[2] / (rgb_a_factors[2] + 27.13),
|
||||||
|
};
|
||||||
|
double aw = (40.0 * rgb_a[0] + 20.0 * rgb_a[1] + rgb_a[2]) / 20.0 * nbb;
|
||||||
|
ViewingConditions viewingConditions = {
|
||||||
|
adapting_luminance,
|
||||||
|
background_lstar_corrected,
|
||||||
|
surround,
|
||||||
|
discounting_illuminant,
|
||||||
|
n,
|
||||||
|
aw,
|
||||||
|
nbb,
|
||||||
|
ncb,
|
||||||
|
c,
|
||||||
|
nc,
|
||||||
|
fl,
|
||||||
|
fl_root,
|
||||||
|
z,
|
||||||
|
{white_point[0], white_point[1], white_point[2]},
|
||||||
|
{rgb_d[0], rgb_d[1], rgb_d[2]},
|
||||||
|
};
|
||||||
|
return viewingConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewingConditions DefaultWithBackgroundLstar(const double background_lstar) {
|
||||||
|
return CreateViewingConditions(kWhitePointD65,
|
||||||
|
(200.0 / kPi * YFromLstar(50.0) / 100.0),
|
||||||
|
background_lstar, 2.0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintDefaultFrame() {
|
||||||
|
ViewingConditions frame = CreateViewingConditions(
|
||||||
|
kWhitePointD65, (200.0 / kPi * YFromLstar(50.0) / 100.0), 50.0, 2.0, 0);
|
||||||
|
printf(
|
||||||
|
"(Frame){%0.9lf,\n %0.9lf,\n %0.9lf,\n %s\n, %0.9lf,\n "
|
||||||
|
"%0.9lf,\n%0.9lf,\n%0.9lf,\n%0.9lf,\n%0.9lf,\n"
|
||||||
|
"%0.9lf,\n%0.9lf,\n%0.9lf,\n%0.9lf,\n"
|
||||||
|
"%0.9lf,\n%0.9lf\n};",
|
||||||
|
frame.adapting_luminance, frame.background_lstar, frame.surround,
|
||||||
|
frame.discounting_illuminant ? "true" : "false",
|
||||||
|
frame.background_y_to_white_point_y, frame.aw, frame.nbb, frame.ncb,
|
||||||
|
frame.c, frame.n_c, frame.fl, frame.fl_root, frame.z, frame.rgb_d[0],
|
||||||
|
frame.rgb_d[1], frame.rgb_d[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
68
src/material-colors/cpp/cam/viewing_conditions.h
Normal file
68
src/material-colors/cpp/cam/viewing_conditions.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_CAM_VIEWING_CONDITIONS_H_
|
||||||
|
#define CPP_CAM_VIEWING_CONDITIONS_H_
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct ViewingConditions {
|
||||||
|
double adapting_luminance = 0.0;
|
||||||
|
double background_lstar = 0.0;
|
||||||
|
double surround = 0.0;
|
||||||
|
bool discounting_illuminant = false;
|
||||||
|
double background_y_to_white_point_y = 0.0;
|
||||||
|
double aw = 0.0;
|
||||||
|
double nbb = 0.0;
|
||||||
|
double ncb = 0.0;
|
||||||
|
double c = 0.0;
|
||||||
|
double n_c = 0.0;
|
||||||
|
double fl = 0.0;
|
||||||
|
double fl_root = 0.0;
|
||||||
|
double z = 0.0;
|
||||||
|
|
||||||
|
double white_point[3] = {0.0, 0.0, 0.0};
|
||||||
|
double rgb_d[3] = {0.0, 0.0, 0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
ViewingConditions CreateViewingConditions(const double white_point[3],
|
||||||
|
const double adapting_luminance,
|
||||||
|
const double background_lstar,
|
||||||
|
const double surround,
|
||||||
|
const bool discounting_illuminant);
|
||||||
|
|
||||||
|
ViewingConditions DefaultWithBackgroundLstar(const double background_lstar);
|
||||||
|
|
||||||
|
static const ViewingConditions kDefaultViewingConditions = (ViewingConditions){
|
||||||
|
11.725676537,
|
||||||
|
50.000000000,
|
||||||
|
2.000000000,
|
||||||
|
false,
|
||||||
|
0.184186503,
|
||||||
|
29.981000900,
|
||||||
|
1.016919255,
|
||||||
|
1.016919255,
|
||||||
|
0.689999998,
|
||||||
|
1.000000000,
|
||||||
|
0.388481468,
|
||||||
|
0.789482653,
|
||||||
|
1.909169555,
|
||||||
|
{95.047, 100.0, 108.883},
|
||||||
|
{1.021177769, 0.986307740, 0.933960497},
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
#endif // CPP_CAM_VIEWING_CONDITIONS_H_
|
138
src/material-colors/cpp/contrast/contrast.cpp
Normal file
138
src/material-colors/cpp/contrast/contrast.cpp
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/contrast/contrast.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
// Given a color and a contrast ratio to reach, the luminance of a color that
|
||||||
|
// reaches that ratio with the color can be calculated. However, that luminance
|
||||||
|
// may not contrast as desired, i.e. the contrast ratio of the input color
|
||||||
|
// and the returned luminance may not reach the contrast ratio asked for.
|
||||||
|
//
|
||||||
|
// When the desired contrast ratio and the result contrast ratio differ by
|
||||||
|
// more than this amount, an error value should be returned, or the method
|
||||||
|
// should be documented as 'unsafe', meaning, it will return a valid luminance
|
||||||
|
// but that luminance may not meet the requested contrast ratio.
|
||||||
|
//
|
||||||
|
// 0.04 selected because it ensures the resulting ratio rounds to the
|
||||||
|
// same tenth.
|
||||||
|
constexpr double CONTRAST_RATIO_EPSILON = 0.04;
|
||||||
|
|
||||||
|
// Color spaces that measure luminance, such as Y in XYZ, L* in L*a*b*,
|
||||||
|
// or T in HCT, are known as perceptual accurate color spaces.
|
||||||
|
//
|
||||||
|
// To be displayed, they must gamut map to a "display space", one that has
|
||||||
|
// a defined limit on the number of colors. Display spaces include sRGB,
|
||||||
|
// more commonly understood as RGB/HSL/HSV/HSB.
|
||||||
|
//
|
||||||
|
// Gamut mapping is undefined and not defined by the color space. Any
|
||||||
|
// gamut mapping algorithm must choose how to sacrifice accuracy in hue,
|
||||||
|
// saturation, and/or lightness.
|
||||||
|
//
|
||||||
|
// A principled solution is to maintain lightness, thus maintaining
|
||||||
|
// contrast/a11y, maintain hue, thus maintaining aesthetic intent, and reduce
|
||||||
|
// chroma until the color is in gamut.
|
||||||
|
//
|
||||||
|
// HCT chooses this solution, but, that doesn't mean it will _exactly_ matched
|
||||||
|
// desired lightness, if only because RGB is quantized: RGB is expressed as
|
||||||
|
// a set of integers: there may be an RGB color with, for example,
|
||||||
|
// 47.892 lightness, but not 47.891.
|
||||||
|
//
|
||||||
|
// To allow for this inherent incompatibility between perceptually accurate
|
||||||
|
// color spaces and display color spaces, methods that take a contrast ratio
|
||||||
|
// and luminance, and return a luminance that reaches that contrast ratio for
|
||||||
|
// the input luminance, purposefully darken/lighten their result such that
|
||||||
|
// the desired contrast ratio will be reached even if inaccuracy is introduced.
|
||||||
|
//
|
||||||
|
// 0.4 is generous, ex. HCT requires much less delta. It was chosen because
|
||||||
|
// it provides a rough guarantee that as long as a percetual color space
|
||||||
|
// gamut maps lightness such that the resulting lightness rounds to the same
|
||||||
|
// as the requested, the desired contrast ratio will be reached.
|
||||||
|
constexpr double LUMINANCE_GAMUT_MAP_TOLERANCE = 0.4;
|
||||||
|
|
||||||
|
double RatioOfYs(double y1, double y2) {
|
||||||
|
double lighter = y1 > y2 ? y1 : y2;
|
||||||
|
double darker = (lighter == y2) ? y1 : y2;
|
||||||
|
return (lighter + 5.0) / (darker + 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double RatioOfTones(double tone_a, double tone_b) {
|
||||||
|
tone_a = std::clamp(tone_a, 0.0, 100.0);
|
||||||
|
tone_b = std::clamp(tone_b, 0.0, 100.0);
|
||||||
|
return RatioOfYs(YFromLstar(tone_a), YFromLstar(tone_b));
|
||||||
|
}
|
||||||
|
|
||||||
|
double Lighter(double tone, double ratio) {
|
||||||
|
if (tone < 0.0 || tone > 100.0) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double dark_y = YFromLstar(tone);
|
||||||
|
double light_y = ratio * (dark_y + 5.0) - 5.0;
|
||||||
|
double real_contrast = RatioOfYs(light_y, dark_y);
|
||||||
|
double delta = abs(real_contrast - ratio);
|
||||||
|
if (real_contrast < ratio && delta > CONTRAST_RATIO_EPSILON) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure gamut mapping, which requires a 'range' on tone, will still result
|
||||||
|
// the correct ratio by darkening slightly.
|
||||||
|
double value = LstarFromY(light_y) + LUMINANCE_GAMUT_MAP_TOLERANCE;
|
||||||
|
if (value < 0 || value > 100) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Darker(double tone, double ratio) {
|
||||||
|
if (tone < 0.0 || tone > 100.0) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double light_y = YFromLstar(tone);
|
||||||
|
double dark_y = ((light_y + 5.0) / ratio) - 5.0;
|
||||||
|
double real_contrast = RatioOfYs(light_y, dark_y);
|
||||||
|
|
||||||
|
double delta = abs(real_contrast - ratio);
|
||||||
|
if (real_contrast < ratio && delta > CONTRAST_RATIO_EPSILON) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure gamut mapping, which requires a 'range' on tone, will still result
|
||||||
|
// the correct ratio by darkening slightly.
|
||||||
|
double value = LstarFromY(dark_y) - LUMINANCE_GAMUT_MAP_TOLERANCE;
|
||||||
|
if (value < 0 || value > 100) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
double LighterUnsafe(double tone, double ratio) {
|
||||||
|
double lighter_safe = Lighter(tone, ratio);
|
||||||
|
return (lighter_safe < 0.0) ? 100.0 : lighter_safe;
|
||||||
|
}
|
||||||
|
|
||||||
|
double DarkerUnsafe(double tone, double ratio) {
|
||||||
|
double darker_safe = Darker(tone, ratio);
|
||||||
|
return (darker_safe < 0.0) ? 0.0 : darker_safe;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
97
src/material-colors/cpp/contrast/contrast.h
Normal file
97
src/material-colors/cpp/contrast/contrast.h
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_CONTRAST_CONTRAST_H_
|
||||||
|
#define CPP_CONTRAST_CONTRAST_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for calculating contrast given two colors, or calculating a
|
||||||
|
* color given one color and a contrast ratio.
|
||||||
|
*
|
||||||
|
* Contrast ratio is calculated using XYZ's Y. When linearized to match human
|
||||||
|
* perception, Y becomes HCT's tone and L*a*b*'s' L*. Informally, this is the
|
||||||
|
* lightness of a color.
|
||||||
|
*
|
||||||
|
* Methods refer to tone, T in the the HCT color space.
|
||||||
|
* Tone is equivalent to L* in the L*a*b* color space, or L in the LCH color
|
||||||
|
* space.
|
||||||
|
*/
|
||||||
|
namespace material_color_utilities {
|
||||||
|
/**
|
||||||
|
* @return a contrast ratio, which ranges from 1 to 21.
|
||||||
|
* @param tone_a Tone between 0 and 100. Values outside will be clamped.
|
||||||
|
* @param tone_b Tone between 0 and 100. Values outside will be clamped.
|
||||||
|
*/
|
||||||
|
double RatioOfTones(double tone_a, double tone_b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a tone >= [tone] that ensures [ratio].
|
||||||
|
* Return value is between 0 and 100.
|
||||||
|
* Returns -1 if [ratio] cannot be achieved with [tone].
|
||||||
|
*
|
||||||
|
* @param tone Tone return value must contrast with.
|
||||||
|
* Range is 0 to 100. Invalid values will result in -1 being returned.
|
||||||
|
* @param ratio Contrast ratio of return value and [tone].
|
||||||
|
* Range is 1 to 21, invalid values have undefined behavior.
|
||||||
|
*/
|
||||||
|
double Lighter(double tone, double ratio);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a tone <= [tone] that ensures [ratio].
|
||||||
|
* Return value is between 0 and 100.
|
||||||
|
* Returns -1 if [ratio] cannot be achieved with [tone].
|
||||||
|
*
|
||||||
|
* @param tone Tone return value must contrast with.
|
||||||
|
* Range is 0 to 100. Invalid values will result in -1 being returned.
|
||||||
|
* @param ratio Contrast ratio of return value and [tone].
|
||||||
|
* Range is 1 to 21, invalid values have undefined behavior.
|
||||||
|
*/
|
||||||
|
double Darker(double tone, double ratio);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a tone >= [tone] that ensures [ratio].
|
||||||
|
* Return value is between 0 and 100.
|
||||||
|
* Returns 100 if [ratio] cannot be achieved with [tone].
|
||||||
|
*
|
||||||
|
* This method is unsafe because the returned value is guaranteed to be in
|
||||||
|
* bounds for tone, i.e. between 0 and 100. However, that value may not reach
|
||||||
|
* the [ratio] with [tone]. For example, there is no color lighter than T100.
|
||||||
|
*
|
||||||
|
* @param tone Tone return value must contrast with.
|
||||||
|
* Range is 0 to 100. Invalid values will result in 100 being returned.
|
||||||
|
* @param ratio Desired contrast ratio of return value and tone parameter.
|
||||||
|
* Range is 1 to 21, invalid values have undefined behavior.
|
||||||
|
*/
|
||||||
|
double LighterUnsafe(double tone, double ratio);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a tone <= [tone] that ensures [ratio].
|
||||||
|
* Return value is between 0 and 100.
|
||||||
|
* Returns 0 if [ratio] cannot be achieved with [tone].
|
||||||
|
*
|
||||||
|
* This method is unsafe because the returned value is guaranteed to be in
|
||||||
|
* bounds for tone, i.e. between 0 and 100. However, that value may not reach
|
||||||
|
* the [ratio] with [tone]. For example, there is no color darker than T0.
|
||||||
|
*
|
||||||
|
* @param tone Tone return value must contrast with.
|
||||||
|
* Range is 0 to 100. Invalid values will result in 0 being returned.
|
||||||
|
* @param ratio Desired contrast ratio of return value and tone parameter.
|
||||||
|
* Range is 1 to 21, invalid values have undefined behavior.
|
||||||
|
*/
|
||||||
|
double DarkerUnsafe(double tone, double ratio);
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_CONTRAST_CONTRAST_H_
|
61
src/material-colors/cpp/contrast/contrast_test.cpp
Normal file
61
src/material-colors/cpp/contrast/contrast_test.cpp
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/contrast/contrast.h"
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
TEST(ContrastTest, RatioOfTonesOutOfBoundsInput) {
|
||||||
|
EXPECT_NEAR(RatioOfTones(-10.0, 110.0), 21.0, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ContrastTest, LighterImpossibleRatioErrors) {
|
||||||
|
EXPECT_NEAR(Lighter(90.0, 10.0), -1.0, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ContrastTest, LighterOutOfBoundsInputAboveErrors) {
|
||||||
|
EXPECT_NEAR(Lighter(110.0, 2.0), -1.0, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ContrastTest, LighterOutOfBoundsInputBelowErrors) {
|
||||||
|
EXPECT_NEAR(Lighter(-10.0, 2.0), -1.0, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ContrastTest, LighterUnsafeReturnsMaxTone) {
|
||||||
|
EXPECT_NEAR(LighterUnsafe(100.0, 2.0), 100, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ContrastTest, DarkerImpossibleRatioErrors) {
|
||||||
|
EXPECT_NEAR(Darker(10.0, 20.0), -1.0, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ContrastTest, DarkerOutOfBoundsInputAboveErrors) {
|
||||||
|
EXPECT_NEAR(Darker(110.0, 2.0), -1.0, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ContrastTest, DarkerOutOfBoundsInputBelowErrors) {
|
||||||
|
EXPECT_NEAR(Darker(-10.0, 2.0), -1.0, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ContrastTest, DarkerUnsafeReturnsMinTone) {
|
||||||
|
EXPECT_NEAR(DarkerUnsafe(0.0, 2.0), 0.0, 0.001);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
42
src/material-colors/cpp/dislike/dislike.cpp
Normal file
42
src/material-colors/cpp/dislike/dislike.cpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/dislike/dislike.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
bool IsDisliked(Hct hct) {
|
||||||
|
double roundedHue = std::round(hct.get_hue());
|
||||||
|
|
||||||
|
bool hue_passes = roundedHue >= 90.0 && roundedHue <= 111.0;
|
||||||
|
bool chroma_passes = std::round(hct.get_chroma()) > 16.0;
|
||||||
|
bool tone_passes = std::round(hct.get_tone()) < 65.0;
|
||||||
|
|
||||||
|
return hue_passes && chroma_passes && tone_passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hct FixIfDisliked(Hct hct) {
|
||||||
|
if (IsDisliked(hct)) {
|
||||||
|
return Hct(hct.get_hue(), hct.get_chroma(), 70.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hct;
|
||||||
|
}
|
||||||
|
} // namespace material_color_utilities
|
55
src/material-colors/cpp/dislike/dislike.h
Normal file
55
src/material-colors/cpp/dislike/dislike.h
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_DISLIKE_DISLIKE_H_
|
||||||
|
#define CPP_DISLIKE_DISLIKE_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks and/or fixes universally disliked colors.
|
||||||
|
*
|
||||||
|
* Color science studies of color preference indicate universal distaste for
|
||||||
|
* dark yellow-greens, and also show this is correlated to distate for
|
||||||
|
* biological waste and rotting food.
|
||||||
|
*
|
||||||
|
* See Palmer and Schloss, 2010 or Schloss and Palmer's Chapter 21 in Handbook
|
||||||
|
* of Color Psychology (2015).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the color is disliked.
|
||||||
|
*
|
||||||
|
* Disliked is defined as a dark yellow-green that is not neutral.
|
||||||
|
* @param hct The color to be tested.
|
||||||
|
*/
|
||||||
|
bool IsDisliked(Hct hct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a color is disliked, lightens it to make it likable.
|
||||||
|
*
|
||||||
|
* The original color is not modified.
|
||||||
|
*
|
||||||
|
* @param hct The color to be tested (and fixed, if needed).
|
||||||
|
* @return The original color if it is not disliked; otherwise, the fixed
|
||||||
|
* color.
|
||||||
|
*/
|
||||||
|
Hct FixIfDisliked(Hct hct);
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_DISLIKE_DISLIKE_H_
|
77
src/material-colors/cpp/dislike/dislike_test.cpp
Normal file
77
src/material-colors/cpp/dislike/dislike_test.cpp
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/dislike/dislike.h"
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using testing::TestWithParam;
|
||||||
|
using testing::Values;
|
||||||
|
using SkinToneTest = TestWithParam<int>;
|
||||||
|
|
||||||
|
TEST_P(SkinToneTest, MonkSkinToneScaleColorsLiked) {
|
||||||
|
int argb = GetParam();
|
||||||
|
|
||||||
|
EXPECT_FALSE(IsDisliked(Hct(argb)));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(DislikeTest, SkinToneTest,
|
||||||
|
Values(0xfff6ede4, 0xfff3e7db, 0xfff7ead0, 0xffeadaba,
|
||||||
|
0xffd7bd96, 0xffa07e56, 0xff825c43, 0xff604134,
|
||||||
|
0xff3a312a, 0xff292420));
|
||||||
|
|
||||||
|
using BileTest = TestWithParam<int>;
|
||||||
|
|
||||||
|
TEST_P(BileTest, BileColorsDisliked) {
|
||||||
|
int argb = GetParam();
|
||||||
|
|
||||||
|
EXPECT_TRUE(IsDisliked(Hct(argb)));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(DislikeTest, BileTest,
|
||||||
|
Values(0xff95884B, 0xff716B40, 0xffB08E00, 0xff4C4308,
|
||||||
|
0xff464521));
|
||||||
|
|
||||||
|
using BileFixingTest = TestWithParam<int>;
|
||||||
|
|
||||||
|
TEST_P(BileFixingTest, BileColorsFixed) {
|
||||||
|
int argb = GetParam();
|
||||||
|
|
||||||
|
Hct bile_color = Hct(argb);
|
||||||
|
EXPECT_TRUE(IsDisliked(bile_color));
|
||||||
|
Hct fixed_bile_color = FixIfDisliked(bile_color);
|
||||||
|
EXPECT_FALSE(IsDisliked(fixed_bile_color));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(DislikeTest, BileFixingTest,
|
||||||
|
Values(0xff95884B, 0xff716B40, 0xffB08E00, 0xff4C4308,
|
||||||
|
0xff464521));
|
||||||
|
|
||||||
|
|
||||||
|
TEST(DislikeTest, Tone67Liked) {
|
||||||
|
Hct color = Hct(100.0, 50.0, 67.0);
|
||||||
|
EXPECT_FALSE(IsDisliked(color));
|
||||||
|
EXPECT_EQ(FixIfDisliked(color).ToInt(), color.ToInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
72
src/material-colors/cpp/dynamiccolor/contrast_curve.h
Normal file
72
src/material-colors/cpp/dynamiccolor/contrast_curve.h
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_DYNAMICCOLOR_CONTRAST_CURVE_H_
|
||||||
|
#define CPP_DYNAMICCOLOR_CONTRAST_CURVE_H_
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class containing a value that changes with the contrast level.
|
||||||
|
*
|
||||||
|
* Usually represents the contrast requirements for a dynamic color on its
|
||||||
|
* background. The four values correspond to values for contrast levels -1.0,
|
||||||
|
* 0.0, 0.5, and 1.0, respectively.
|
||||||
|
*/
|
||||||
|
struct ContrastCurve {
|
||||||
|
double low;
|
||||||
|
double normal;
|
||||||
|
double medium;
|
||||||
|
double high;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a `ContrastCurve` object.
|
||||||
|
*
|
||||||
|
* @param low Value for contrast level -1.0
|
||||||
|
* @param normal Value for contrast level 0.0
|
||||||
|
* @param medium Value for contrast level 0.5
|
||||||
|
* @param high Value for contrast level 1.0
|
||||||
|
*/
|
||||||
|
ContrastCurve(double low, double normal, double medium, double high)
|
||||||
|
: low(low), normal(normal), medium(medium), high(high) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at a given contrast level.
|
||||||
|
*
|
||||||
|
* @param contrastLevel The contrast level. 0.0 is the default (normal); -1.0
|
||||||
|
* is the lowest; 1.0 is the highest.
|
||||||
|
* @return The value. For contrast ratios, a number between 1.0 and 21.0.
|
||||||
|
*/
|
||||||
|
double get(double contrastLevel) {
|
||||||
|
if (contrastLevel <= -1.0) {
|
||||||
|
return low;
|
||||||
|
} else if (contrastLevel < 0.0) {
|
||||||
|
return Lerp(low, normal, (contrastLevel - (-1)) / 1);
|
||||||
|
} else if (contrastLevel < 0.5) {
|
||||||
|
return Lerp(normal, medium, (contrastLevel - 0) / 0.5);
|
||||||
|
} else if (contrastLevel < 1.0) {
|
||||||
|
return Lerp(medium, high, (contrastLevel - 0.5) / 0.5);
|
||||||
|
} else {
|
||||||
|
return high;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_DYNAMICCOLOR_CONTRAST_CURVE_H_
|
306
src/material-colors/cpp/dynamiccolor/dynamic_color.cpp
Normal file
306
src/material-colors/cpp/dynamiccolor/dynamic_color.cpp
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/dynamiccolor/dynamic_color.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/contrast/contrast.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/tone_delta_pair.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
using std::function;
|
||||||
|
using std::nullopt;
|
||||||
|
using std::optional;
|
||||||
|
|
||||||
|
using DoubleFunction = function<double(const DynamicScheme&)>;
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
optional<U> SafeCall(optional<function<optional<U>(const T&)>> f, const T& x) {
|
||||||
|
if (f == nullopt) {
|
||||||
|
return nullopt;
|
||||||
|
} else {
|
||||||
|
return f.value()(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
optional<U> SafeCallCleanResult(optional<function<U(T)>> f, T x) {
|
||||||
|
if (f == nullopt) {
|
||||||
|
return nullopt;
|
||||||
|
} else {
|
||||||
|
return f.value()(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double ForegroundTone(double bg_tone, double ratio) {
|
||||||
|
double lighter_tone = LighterUnsafe(/*tone*/ bg_tone, /*ratio*/ ratio);
|
||||||
|
double darker_tone = DarkerUnsafe(/*tone*/ bg_tone, /*ratio*/ ratio);
|
||||||
|
double lighter_ratio = RatioOfTones(lighter_tone, bg_tone);
|
||||||
|
double darker_ratio = RatioOfTones(darker_tone, bg_tone);
|
||||||
|
double prefer_lighter = TonePrefersLightForeground(bg_tone);
|
||||||
|
|
||||||
|
if (prefer_lighter) {
|
||||||
|
double negligible_difference =
|
||||||
|
(abs(lighter_ratio - darker_ratio) < 0.1 && lighter_ratio < ratio &&
|
||||||
|
darker_ratio < ratio);
|
||||||
|
return lighter_ratio >= ratio || lighter_ratio >= darker_ratio ||
|
||||||
|
negligible_difference
|
||||||
|
? lighter_tone
|
||||||
|
: darker_tone;
|
||||||
|
} else {
|
||||||
|
return darker_ratio >= ratio || darker_ratio >= lighter_ratio
|
||||||
|
? darker_tone
|
||||||
|
: lighter_tone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double EnableLightForeground(double tone) {
|
||||||
|
if (TonePrefersLightForeground(tone) && !ToneAllowsLightForeground(tone)) {
|
||||||
|
return 49.0;
|
||||||
|
}
|
||||||
|
return tone;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TonePrefersLightForeground(double tone) { return round(tone) < 60; }
|
||||||
|
|
||||||
|
bool ToneAllowsLightForeground(double tone) { return round(tone) <= 49; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor.
|
||||||
|
*/
|
||||||
|
DynamicColor::DynamicColor(
|
||||||
|
std::string name, std::function<TonalPalette(const DynamicScheme&)> palette,
|
||||||
|
std::function<double(const DynamicScheme&)> tone, bool is_background,
|
||||||
|
|
||||||
|
std::optional<std::function<DynamicColor(const DynamicScheme&)>> background,
|
||||||
|
std::optional<std::function<DynamicColor(const DynamicScheme&)>>
|
||||||
|
second_background,
|
||||||
|
std::optional<ContrastCurve> contrast_curve,
|
||||||
|
std::optional<std::function<ToneDeltaPair(const DynamicScheme&)>>
|
||||||
|
tone_delta_pair)
|
||||||
|
: name_(name),
|
||||||
|
palette_(palette),
|
||||||
|
tone_(tone),
|
||||||
|
is_background_(is_background),
|
||||||
|
background_(background),
|
||||||
|
second_background_(second_background),
|
||||||
|
contrast_curve_(contrast_curve),
|
||||||
|
tone_delta_pair_(tone_delta_pair) {}
|
||||||
|
|
||||||
|
DynamicColor DynamicColor::FromPalette(
|
||||||
|
std::string name, std::function<TonalPalette(const DynamicScheme&)> palette,
|
||||||
|
std::function<double(const DynamicScheme&)> tone) {
|
||||||
|
return DynamicColor(name, palette, tone,
|
||||||
|
/*is_background=*/false,
|
||||||
|
/*background=*/nullopt,
|
||||||
|
/*second_background=*/nullopt,
|
||||||
|
/*contrast_curve=*/nullopt,
|
||||||
|
/*tone_delta_pair=*/nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicColor::GetArgb(const DynamicScheme& scheme) {
|
||||||
|
return palette_(scheme).get(GetTone(scheme));
|
||||||
|
}
|
||||||
|
|
||||||
|
Hct DynamicColor::GetHct(const DynamicScheme& scheme) {
|
||||||
|
return Hct(GetArgb(scheme));
|
||||||
|
}
|
||||||
|
|
||||||
|
double DynamicColor::GetTone(const DynamicScheme& scheme) {
|
||||||
|
bool decreasingContrast = scheme.contrast_level < 0;
|
||||||
|
|
||||||
|
// Case 1: dual foreground, pair of colors with delta constraint.
|
||||||
|
if (tone_delta_pair_ != std::nullopt) {
|
||||||
|
ToneDeltaPair tone_delta_pair = tone_delta_pair_.value()(scheme);
|
||||||
|
DynamicColor role_a = tone_delta_pair.role_a_;
|
||||||
|
DynamicColor role_b = tone_delta_pair.role_b_;
|
||||||
|
double delta = tone_delta_pair.delta_;
|
||||||
|
TonePolarity polarity = tone_delta_pair.polarity_;
|
||||||
|
bool stay_together = tone_delta_pair.stay_together_;
|
||||||
|
|
||||||
|
DynamicColor bg = background_.value()(scheme);
|
||||||
|
double bg_tone = bg.GetTone(scheme);
|
||||||
|
|
||||||
|
bool a_is_nearer =
|
||||||
|
(polarity == TonePolarity::kNearer ||
|
||||||
|
(polarity == TonePolarity::kLighter && !scheme.is_dark) ||
|
||||||
|
(polarity == TonePolarity::kDarker && scheme.is_dark));
|
||||||
|
DynamicColor nearer = a_is_nearer ? role_a : role_b;
|
||||||
|
DynamicColor farther = a_is_nearer ? role_b : role_a;
|
||||||
|
bool am_nearer = this->name_ == nearer.name_;
|
||||||
|
double expansion_dir = scheme.is_dark ? 1 : -1;
|
||||||
|
|
||||||
|
// 1st round: solve to min, each
|
||||||
|
double n_contrast =
|
||||||
|
nearer.contrast_curve_.value().get(scheme.contrast_level);
|
||||||
|
double f_contrast =
|
||||||
|
farther.contrast_curve_.value().get(scheme.contrast_level);
|
||||||
|
|
||||||
|
// If a color is good enough, it is not adjusted.
|
||||||
|
// Initial and adjusted tones for `nearer`
|
||||||
|
double n_initial_tone = nearer.tone_(scheme);
|
||||||
|
double n_tone = RatioOfTones(bg_tone, n_initial_tone) >= n_contrast
|
||||||
|
? n_initial_tone
|
||||||
|
: ForegroundTone(bg_tone, n_contrast);
|
||||||
|
// Initial and adjusted tones for `farther`
|
||||||
|
double f_initial_tone = farther.tone_(scheme);
|
||||||
|
double f_tone = RatioOfTones(bg_tone, f_initial_tone) >= f_contrast
|
||||||
|
? f_initial_tone
|
||||||
|
: ForegroundTone(bg_tone, f_contrast);
|
||||||
|
|
||||||
|
if (decreasingContrast) {
|
||||||
|
// If decreasing contrast, adjust color to the "bare minimum"
|
||||||
|
// that satisfies contrast.
|
||||||
|
n_tone = ForegroundTone(bg_tone, n_contrast);
|
||||||
|
f_tone = ForegroundTone(bg_tone, f_contrast);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((f_tone - n_tone) * expansion_dir >= delta) {
|
||||||
|
// Good! Tones satisfy the constraint; no change needed.
|
||||||
|
} else {
|
||||||
|
// 2nd round: expand farther to match delta.
|
||||||
|
f_tone = std::clamp(n_tone + delta * expansion_dir, 0.0, 100.0);
|
||||||
|
if ((f_tone - n_tone) * expansion_dir >= delta) {
|
||||||
|
// Good! Tones now satisfy the constraint; no change needed.
|
||||||
|
} else {
|
||||||
|
// 3rd round: contract nearer to match delta.
|
||||||
|
n_tone = std::clamp(f_tone - delta * expansion_dir, 0.0, 100.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoids the 50-59 awkward zone.
|
||||||
|
if (50 <= n_tone && n_tone < 60) {
|
||||||
|
// If `nearer` is in the awkward zone, move it away, together with
|
||||||
|
// `farther`.
|
||||||
|
if (expansion_dir > 0) {
|
||||||
|
n_tone = 60;
|
||||||
|
f_tone = std::max(f_tone, n_tone + delta * expansion_dir);
|
||||||
|
} else {
|
||||||
|
n_tone = 49;
|
||||||
|
f_tone = std::min(f_tone, n_tone + delta * expansion_dir);
|
||||||
|
}
|
||||||
|
} else if (50 <= f_tone && f_tone < 60) {
|
||||||
|
if (stay_together) {
|
||||||
|
// Fixes both, to avoid two colors on opposite sides of the "awkward
|
||||||
|
// zone".
|
||||||
|
if (expansion_dir > 0) {
|
||||||
|
n_tone = 60;
|
||||||
|
f_tone = std::max(f_tone, n_tone + delta * expansion_dir);
|
||||||
|
} else {
|
||||||
|
n_tone = 49;
|
||||||
|
f_tone = std::min(f_tone, n_tone + delta * expansion_dir);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not required to stay together; fixes just one.
|
||||||
|
if (expansion_dir > 0) {
|
||||||
|
f_tone = 60;
|
||||||
|
} else {
|
||||||
|
f_tone = 49;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns `n_tone` if this color is `nearer`, otherwise `f_tone`.
|
||||||
|
return am_nearer ? n_tone : f_tone;
|
||||||
|
} else {
|
||||||
|
// Case 2: No contrast pair; just solve for itself.
|
||||||
|
double answer = tone_(scheme);
|
||||||
|
|
||||||
|
if (background_ == std::nullopt) {
|
||||||
|
return answer; // No adjustment for colors with no background.
|
||||||
|
}
|
||||||
|
|
||||||
|
double bg_tone = background_.value()(scheme).GetTone(scheme);
|
||||||
|
|
||||||
|
double desired_ratio = contrast_curve_.value().get(scheme.contrast_level);
|
||||||
|
|
||||||
|
if (RatioOfTones(bg_tone, answer) >= desired_ratio) {
|
||||||
|
// Don't "improve" what's good enough.
|
||||||
|
} else {
|
||||||
|
// Rough improvement.
|
||||||
|
answer = ForegroundTone(bg_tone, desired_ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decreasingContrast) {
|
||||||
|
answer = ForegroundTone(bg_tone, desired_ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_background_ && 50 <= answer && answer < 60) {
|
||||||
|
// Must adjust
|
||||||
|
if (RatioOfTones(49, bg_tone) >= desired_ratio) {
|
||||||
|
answer = 49;
|
||||||
|
} else {
|
||||||
|
answer = 60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (second_background_ != std::nullopt) {
|
||||||
|
// Case 3: Adjust for dual backgrounds.
|
||||||
|
|
||||||
|
double bg_tone_1 = background_.value()(scheme).GetTone(scheme);
|
||||||
|
double bg_tone_2 = second_background_.value()(scheme).GetTone(scheme);
|
||||||
|
|
||||||
|
double upper = std::max(bg_tone_1, bg_tone_2);
|
||||||
|
double lower = std::min(bg_tone_1, bg_tone_2);
|
||||||
|
|
||||||
|
if (RatioOfTones(upper, answer) >= desired_ratio &&
|
||||||
|
RatioOfTones(lower, answer) >= desired_ratio) {
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The darkest light tone that satisfies the desired ratio,
|
||||||
|
// or -1 if such ratio cannot be reached.
|
||||||
|
double lightOption = Lighter(upper, desired_ratio);
|
||||||
|
|
||||||
|
// The lightest dark tone that satisfies the desired ratio,
|
||||||
|
// or -1 if such ratio cannot be reached.
|
||||||
|
double darkOption = Darker(lower, desired_ratio);
|
||||||
|
|
||||||
|
// Tones suitable for the foreground.
|
||||||
|
std::vector<double> availables;
|
||||||
|
if (lightOption != -1) {
|
||||||
|
availables.push_back(lightOption);
|
||||||
|
}
|
||||||
|
if (darkOption != -1) {
|
||||||
|
availables.push_back(darkOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool prefersLight = TonePrefersLightForeground(bg_tone_1) ||
|
||||||
|
TonePrefersLightForeground(bg_tone_2);
|
||||||
|
if (prefersLight) {
|
||||||
|
return (lightOption < 0) ? 100 : lightOption;
|
||||||
|
}
|
||||||
|
if (availables.size() == 1) {
|
||||||
|
return availables[0];
|
||||||
|
}
|
||||||
|
return (darkOption < 0) ? 0 : darkOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
131
src/material-colors/cpp/dynamiccolor/dynamic_color.h
Normal file
131
src/material-colors/cpp/dynamiccolor/dynamic_color.h
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_DYNAMICCOLOR_DYNAMIC_COLOR_H_
|
||||||
|
#define CPP_DYNAMICCOLOR_DYNAMIC_COLOR_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/contrast_curve.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct ToneDeltaPair;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a background tone, find a foreground tone, while ensuring they reach
|
||||||
|
* a contrast ratio that is as close to [ratio] as possible.
|
||||||
|
*
|
||||||
|
* [bgTone] Tone in HCT. Range is 0 to 100, undefined behavior when it falls
|
||||||
|
* outside that range.
|
||||||
|
* [ratio] The contrast ratio desired between [bgTone] and the return value.
|
||||||
|
*/
|
||||||
|
double ForegroundTone(double bg_tone, double ratio);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust a tone such that white has 4.5 contrast, if the tone is
|
||||||
|
* reasonably close to supporting it.
|
||||||
|
*/
|
||||||
|
double EnableLightForeground(double tone);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether [tone] prefers a light foreground.
|
||||||
|
*
|
||||||
|
* People prefer white foregrounds on ~T60-70. Observed over time, and also
|
||||||
|
* by Andrew Somers during research for APCA.
|
||||||
|
*
|
||||||
|
* T60 used as to create the smallest discontinuity possible when skipping
|
||||||
|
* down to T49 in order to ensure light foregrounds.
|
||||||
|
*
|
||||||
|
* Since `tertiaryContainer` in dark monochrome scheme requires a tone of
|
||||||
|
* 60, it should not be adjusted. Therefore, 60 is excluded here.
|
||||||
|
*/
|
||||||
|
bool TonePrefersLightForeground(double tone);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether [tone] can reach a contrast ratio of 4.5 with a lighter
|
||||||
|
* color.
|
||||||
|
*/
|
||||||
|
bool ToneAllowsLightForeground(double tone);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param name_ The name of the dynamic color.
|
||||||
|
* @param palette_ Function that provides a TonalPalette given
|
||||||
|
* DynamicScheme. A TonalPalette is defined by a hue and chroma, so this
|
||||||
|
* replaces the need to specify hue/chroma. By providing a tonal palette, when
|
||||||
|
* contrast adjustments are made, intended chroma can be preserved.
|
||||||
|
* @param tone_ Function that provides a tone given DynamicScheme.
|
||||||
|
* @param is_background_ Whether this dynamic color is a background, with
|
||||||
|
* some other color as the foreground.
|
||||||
|
* @param background_ The background of the dynamic color (as a function of a
|
||||||
|
* `DynamicScheme`), if it exists.
|
||||||
|
* @param second_background_ A second background of the dynamic color (as a
|
||||||
|
* function of a `DynamicScheme`), if it
|
||||||
|
* exists.
|
||||||
|
* @param contrast_curve_ A `ContrastCurve` object specifying how its contrast
|
||||||
|
* against its background should behave in various contrast levels options.
|
||||||
|
* @param tone_delta_pair_ A `ToneDeltaPair` object specifying a tone delta
|
||||||
|
* constraint between two colors. One of them must be the color being
|
||||||
|
* constructed.
|
||||||
|
*/
|
||||||
|
struct DynamicColor {
|
||||||
|
std::string name_;
|
||||||
|
std::function<TonalPalette(const DynamicScheme&)> palette_;
|
||||||
|
std::function<double(const DynamicScheme&)> tone_;
|
||||||
|
bool is_background_;
|
||||||
|
|
||||||
|
std::optional<std::function<DynamicColor(const DynamicScheme&)>> background_;
|
||||||
|
std::optional<std::function<DynamicColor(const DynamicScheme&)>>
|
||||||
|
second_background_;
|
||||||
|
std::optional<ContrastCurve> contrast_curve_;
|
||||||
|
std::optional<std::function<ToneDeltaPair(const DynamicScheme&)>>
|
||||||
|
tone_delta_pair_;
|
||||||
|
|
||||||
|
/** A convenience constructor, only requiring name, palette, and tone. */
|
||||||
|
static DynamicColor FromPalette(
|
||||||
|
std::string name,
|
||||||
|
std::function<TonalPalette(const DynamicScheme&)> palette,
|
||||||
|
std::function<double(const DynamicScheme&)> tone);
|
||||||
|
|
||||||
|
Argb GetArgb(const DynamicScheme& scheme);
|
||||||
|
|
||||||
|
Hct GetHct(const DynamicScheme& scheme);
|
||||||
|
|
||||||
|
double GetTone(const DynamicScheme& scheme);
|
||||||
|
|
||||||
|
/** The default constructor. */
|
||||||
|
DynamicColor(std::string name,
|
||||||
|
std::function<TonalPalette(const DynamicScheme&)> palette,
|
||||||
|
std::function<double(const DynamicScheme&)> tone,
|
||||||
|
bool is_background,
|
||||||
|
|
||||||
|
std::optional<std::function<DynamicColor(const DynamicScheme&)>>
|
||||||
|
background,
|
||||||
|
std::optional<std::function<DynamicColor(const DynamicScheme&)>>
|
||||||
|
second_background,
|
||||||
|
std::optional<ContrastCurve> contrast_curve,
|
||||||
|
std::optional<std::function<ToneDeltaPair(const DynamicScheme&)>>
|
||||||
|
tone_delta_pair);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_DYNAMICCOLOR_DYNAMIC_COLOR_H_
|
286
src/material-colors/cpp/dynamiccolor/dynamic_scheme.cpp
Normal file
286
src/material-colors/cpp/dynamiccolor/dynamic_scheme.cpp
Normal file
|
@ -0,0 +1,286 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/material_dynamic_colors.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
DynamicScheme::DynamicScheme(Hct source_color_hct, Variant variant,
|
||||||
|
double contrast_level, bool is_dark,
|
||||||
|
TonalPalette primary_palette,
|
||||||
|
TonalPalette secondary_palette,
|
||||||
|
TonalPalette tertiary_palette,
|
||||||
|
TonalPalette neutral_palette,
|
||||||
|
TonalPalette neutral_variant_palette,
|
||||||
|
std::optional<TonalPalette> error_palette)
|
||||||
|
: source_color_hct(source_color_hct),
|
||||||
|
variant(variant),
|
||||||
|
is_dark(is_dark),
|
||||||
|
contrast_level(contrast_level),
|
||||||
|
primary_palette(primary_palette),
|
||||||
|
secondary_palette(secondary_palette),
|
||||||
|
tertiary_palette(tertiary_palette),
|
||||||
|
neutral_palette(neutral_palette),
|
||||||
|
neutral_variant_palette(neutral_variant_palette),
|
||||||
|
error_palette(error_palette.value_or(TonalPalette(25.0, 84.0))) {}
|
||||||
|
|
||||||
|
double DynamicScheme::GetRotatedHue(Hct source_color, std::vector<double> hues,
|
||||||
|
std::vector<double> rotations) {
|
||||||
|
double source_hue = source_color.get_hue();
|
||||||
|
|
||||||
|
if (rotations.size() == 1) {
|
||||||
|
return SanitizeDegreesDouble(source_color.get_hue() + rotations[0]);
|
||||||
|
}
|
||||||
|
int size = hues.size();
|
||||||
|
for (int i = 0; i <= (size - 2); ++i) {
|
||||||
|
double this_hue = hues[i];
|
||||||
|
double next_hue = hues[i + 1];
|
||||||
|
if (this_hue < source_hue && source_hue < next_hue) {
|
||||||
|
return SanitizeDegreesDouble(source_hue + rotations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source_hue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::SourceColorArgb() const { return source_color_hct.ToInt(); }
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetPrimaryPaletteKeyColor() const {
|
||||||
|
return MaterialDynamicColors::PrimaryPaletteKeyColor().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSecondaryPaletteKeyColor() const {
|
||||||
|
return MaterialDynamicColors::SecondaryPaletteKeyColor().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetTertiaryPaletteKeyColor() const {
|
||||||
|
return MaterialDynamicColors::TertiaryPaletteKeyColor().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetNeutralPaletteKeyColor() const {
|
||||||
|
return MaterialDynamicColors::NeutralPaletteKeyColor().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetNeutralVariantPaletteKeyColor() const {
|
||||||
|
return MaterialDynamicColors::NeutralVariantPaletteKeyColor().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetBackground() const {
|
||||||
|
return MaterialDynamicColors::Background().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnBackground() const {
|
||||||
|
return MaterialDynamicColors::OnBackground().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurface() const {
|
||||||
|
return MaterialDynamicColors::Surface().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurfaceDim() const {
|
||||||
|
return MaterialDynamicColors::SurfaceDim().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurfaceBright() const {
|
||||||
|
return MaterialDynamicColors::SurfaceBright().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurfaceContainerLowest() const {
|
||||||
|
return MaterialDynamicColors::SurfaceContainerLowest().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurfaceContainerLow() const {
|
||||||
|
return MaterialDynamicColors::SurfaceContainerLow().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurfaceContainer() const {
|
||||||
|
return MaterialDynamicColors::SurfaceContainer().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurfaceContainerHigh() const {
|
||||||
|
return MaterialDynamicColors::SurfaceContainerHigh().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurfaceContainerHighest() const {
|
||||||
|
return MaterialDynamicColors::SurfaceContainerHighest().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnSurface() const {
|
||||||
|
return MaterialDynamicColors::OnSurface().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurfaceVariant() const {
|
||||||
|
return MaterialDynamicColors::SurfaceVariant().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnSurfaceVariant() const {
|
||||||
|
return MaterialDynamicColors::OnSurfaceVariant().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetInverseSurface() const {
|
||||||
|
return MaterialDynamicColors::InverseSurface().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetInverseOnSurface() const {
|
||||||
|
return MaterialDynamicColors::InverseOnSurface().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOutline() const {
|
||||||
|
return MaterialDynamicColors::Outline().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOutlineVariant() const {
|
||||||
|
return MaterialDynamicColors::OutlineVariant().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetShadow() const {
|
||||||
|
return MaterialDynamicColors::Shadow().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetScrim() const {
|
||||||
|
return MaterialDynamicColors::Scrim().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSurfaceTint() const {
|
||||||
|
return MaterialDynamicColors::SurfaceTint().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetPrimary() const {
|
||||||
|
return MaterialDynamicColors::Primary().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnPrimary() const {
|
||||||
|
return MaterialDynamicColors::OnPrimary().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetPrimaryContainer() const {
|
||||||
|
return MaterialDynamicColors::PrimaryContainer().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnPrimaryContainer() const {
|
||||||
|
return MaterialDynamicColors::OnPrimaryContainer().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetInversePrimary() const {
|
||||||
|
return MaterialDynamicColors::InversePrimary().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSecondary() const {
|
||||||
|
return MaterialDynamicColors::Secondary().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnSecondary() const {
|
||||||
|
return MaterialDynamicColors::OnSecondary().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSecondaryContainer() const {
|
||||||
|
return MaterialDynamicColors::SecondaryContainer().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnSecondaryContainer() const {
|
||||||
|
return MaterialDynamicColors::OnSecondaryContainer().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetTertiary() const {
|
||||||
|
return MaterialDynamicColors::Tertiary().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnTertiary() const {
|
||||||
|
return MaterialDynamicColors::OnTertiary().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetTertiaryContainer() const {
|
||||||
|
return MaterialDynamicColors::TertiaryContainer().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnTertiaryContainer() const {
|
||||||
|
return MaterialDynamicColors::OnTertiaryContainer().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetError() const {
|
||||||
|
return MaterialDynamicColors::Error().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnError() const {
|
||||||
|
return MaterialDynamicColors::OnError().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetErrorContainer() const {
|
||||||
|
return MaterialDynamicColors::ErrorContainer().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnErrorContainer() const {
|
||||||
|
return MaterialDynamicColors::OnErrorContainer().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetPrimaryFixed() const {
|
||||||
|
return MaterialDynamicColors::PrimaryFixed().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetPrimaryFixedDim() const {
|
||||||
|
return MaterialDynamicColors::PrimaryFixedDim().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnPrimaryFixed() const {
|
||||||
|
return MaterialDynamicColors::OnPrimaryFixed().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnPrimaryFixedVariant() const {
|
||||||
|
return MaterialDynamicColors::OnPrimaryFixedVariant().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSecondaryFixed() const {
|
||||||
|
return MaterialDynamicColors::SecondaryFixed().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetSecondaryFixedDim() const {
|
||||||
|
return MaterialDynamicColors::SecondaryFixedDim().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnSecondaryFixed() const {
|
||||||
|
return MaterialDynamicColors::OnSecondaryFixed().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnSecondaryFixedVariant() const {
|
||||||
|
return MaterialDynamicColors::OnSecondaryFixedVariant().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetTertiaryFixed() const {
|
||||||
|
return MaterialDynamicColors::TertiaryFixed().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetTertiaryFixedDim() const {
|
||||||
|
return MaterialDynamicColors::TertiaryFixedDim().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnTertiaryFixed() const {
|
||||||
|
return MaterialDynamicColors::OnTertiaryFixed().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb DynamicScheme::GetOnTertiaryFixedVariant() const {
|
||||||
|
return MaterialDynamicColors::OnTertiaryFixedVariant().GetArgb(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
113
src/material-colors/cpp/dynamiccolor/dynamic_scheme.h
Normal file
113
src/material-colors/cpp/dynamiccolor/dynamic_scheme.h
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_DYNAMICCOLOR_DYNAMIC_SCHEME_H_
|
||||||
|
#define CPP_DYNAMICCOLOR_DYNAMIC_SCHEME_H_
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct DynamicScheme {
|
||||||
|
Hct source_color_hct;
|
||||||
|
Variant variant;
|
||||||
|
bool is_dark;
|
||||||
|
double contrast_level;
|
||||||
|
|
||||||
|
TonalPalette primary_palette;
|
||||||
|
TonalPalette secondary_palette;
|
||||||
|
TonalPalette tertiary_palette;
|
||||||
|
TonalPalette neutral_palette;
|
||||||
|
TonalPalette neutral_variant_palette;
|
||||||
|
TonalPalette error_palette;
|
||||||
|
|
||||||
|
DynamicScheme(Hct source_color_hct, Variant variant, double contrast_level,
|
||||||
|
bool is_dark, TonalPalette primary_palette,
|
||||||
|
TonalPalette secondary_palette, TonalPalette tertiary_palette,
|
||||||
|
TonalPalette neutral_palette,
|
||||||
|
TonalPalette neutral_variant_palette,
|
||||||
|
std::optional<TonalPalette> error_palette = std::nullopt);
|
||||||
|
|
||||||
|
static double GetRotatedHue(Hct source_color, std::vector<double> hues,
|
||||||
|
std::vector<double> rotations);
|
||||||
|
|
||||||
|
Argb SourceColorArgb() const;
|
||||||
|
|
||||||
|
Argb GetPrimaryPaletteKeyColor() const;
|
||||||
|
Argb GetSecondaryPaletteKeyColor() const;
|
||||||
|
Argb GetTertiaryPaletteKeyColor() const;
|
||||||
|
Argb GetNeutralPaletteKeyColor() const;
|
||||||
|
Argb GetNeutralVariantPaletteKeyColor() const;
|
||||||
|
Argb GetBackground() const;
|
||||||
|
Argb GetOnBackground() const;
|
||||||
|
Argb GetSurface() const;
|
||||||
|
Argb GetSurfaceDim() const;
|
||||||
|
Argb GetSurfaceBright() const;
|
||||||
|
Argb GetSurfaceContainerLowest() const;
|
||||||
|
Argb GetSurfaceContainerLow() const;
|
||||||
|
Argb GetSurfaceContainer() const;
|
||||||
|
Argb GetSurfaceContainerHigh() const;
|
||||||
|
Argb GetSurfaceContainerHighest() const;
|
||||||
|
Argb GetOnSurface() const;
|
||||||
|
Argb GetSurfaceVariant() const;
|
||||||
|
Argb GetOnSurfaceVariant() const;
|
||||||
|
Argb GetInverseSurface() const;
|
||||||
|
Argb GetInverseOnSurface() const;
|
||||||
|
Argb GetOutline() const;
|
||||||
|
Argb GetOutlineVariant() const;
|
||||||
|
Argb GetShadow() const;
|
||||||
|
Argb GetScrim() const;
|
||||||
|
Argb GetSurfaceTint() const;
|
||||||
|
Argb GetPrimary() const;
|
||||||
|
Argb GetOnPrimary() const;
|
||||||
|
Argb GetPrimaryContainer() const;
|
||||||
|
Argb GetOnPrimaryContainer() const;
|
||||||
|
Argb GetInversePrimary() const;
|
||||||
|
Argb GetSecondary() const;
|
||||||
|
Argb GetOnSecondary() const;
|
||||||
|
Argb GetSecondaryContainer() const;
|
||||||
|
Argb GetOnSecondaryContainer() const;
|
||||||
|
Argb GetTertiary() const;
|
||||||
|
Argb GetOnTertiary() const;
|
||||||
|
Argb GetTertiaryContainer() const;
|
||||||
|
Argb GetOnTertiaryContainer() const;
|
||||||
|
Argb GetError() const;
|
||||||
|
Argb GetOnError() const;
|
||||||
|
Argb GetErrorContainer() const;
|
||||||
|
Argb GetOnErrorContainer() const;
|
||||||
|
Argb GetPrimaryFixed() const;
|
||||||
|
Argb GetPrimaryFixedDim() const;
|
||||||
|
Argb GetOnPrimaryFixed() const;
|
||||||
|
Argb GetOnPrimaryFixedVariant() const;
|
||||||
|
Argb GetSecondaryFixed() const;
|
||||||
|
Argb GetSecondaryFixedDim() const;
|
||||||
|
Argb GetOnSecondaryFixed() const;
|
||||||
|
Argb GetOnSecondaryFixedVariant() const;
|
||||||
|
Argb GetTertiaryFixed() const;
|
||||||
|
Argb GetTertiaryFixedDim() const;
|
||||||
|
Argb GetOnTertiaryFixed() const;
|
||||||
|
Argb GetOnTertiaryFixedVariant() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_DYNAMICCOLOR_DYNAMIC_SCHEME_H_
|
1153
src/material-colors/cpp/dynamiccolor/material_dynamic_colors.cpp
Normal file
1153
src/material-colors/cpp/dynamiccolor/material_dynamic_colors.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_DYNAMICCOLOR_MATERIAL_DYNAMIC_COLORS_H_
|
||||||
|
#define CPP_DYNAMICCOLOR_MATERIAL_DYNAMIC_COLORS_H_
|
||||||
|
|
||||||
|
#include "cpp/dynamiccolor/dynamic_color.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
class MaterialDynamicColors {
|
||||||
|
public:
|
||||||
|
static DynamicColor PrimaryPaletteKeyColor();
|
||||||
|
static DynamicColor SecondaryPaletteKeyColor();
|
||||||
|
static DynamicColor TertiaryPaletteKeyColor();
|
||||||
|
static DynamicColor NeutralPaletteKeyColor();
|
||||||
|
static DynamicColor NeutralVariantPaletteKeyColor();
|
||||||
|
static DynamicColor Background();
|
||||||
|
static DynamicColor OnBackground();
|
||||||
|
static DynamicColor Surface();
|
||||||
|
static DynamicColor SurfaceDim();
|
||||||
|
static DynamicColor SurfaceBright();
|
||||||
|
static DynamicColor SurfaceContainerLowest();
|
||||||
|
static DynamicColor SurfaceContainerLow();
|
||||||
|
static DynamicColor SurfaceContainer();
|
||||||
|
static DynamicColor SurfaceContainerHigh();
|
||||||
|
static DynamicColor SurfaceContainerHighest();
|
||||||
|
static DynamicColor OnSurface();
|
||||||
|
static DynamicColor SurfaceVariant();
|
||||||
|
static DynamicColor OnSurfaceVariant();
|
||||||
|
static DynamicColor InverseSurface();
|
||||||
|
static DynamicColor InverseOnSurface();
|
||||||
|
static DynamicColor Outline();
|
||||||
|
static DynamicColor OutlineVariant();
|
||||||
|
static DynamicColor Shadow();
|
||||||
|
static DynamicColor Scrim();
|
||||||
|
static DynamicColor SurfaceTint();
|
||||||
|
static DynamicColor Primary();
|
||||||
|
static DynamicColor OnPrimary();
|
||||||
|
static DynamicColor PrimaryContainer();
|
||||||
|
static DynamicColor OnPrimaryContainer();
|
||||||
|
static DynamicColor InversePrimary();
|
||||||
|
static DynamicColor Secondary();
|
||||||
|
static DynamicColor OnSecondary();
|
||||||
|
static DynamicColor SecondaryContainer();
|
||||||
|
static DynamicColor OnSecondaryContainer();
|
||||||
|
static DynamicColor Tertiary();
|
||||||
|
static DynamicColor OnTertiary();
|
||||||
|
static DynamicColor TertiaryContainer();
|
||||||
|
static DynamicColor OnTertiaryContainer();
|
||||||
|
static DynamicColor Error();
|
||||||
|
static DynamicColor OnError();
|
||||||
|
static DynamicColor ErrorContainer();
|
||||||
|
static DynamicColor OnErrorContainer();
|
||||||
|
static DynamicColor PrimaryFixed();
|
||||||
|
static DynamicColor PrimaryFixedDim();
|
||||||
|
static DynamicColor OnPrimaryFixed();
|
||||||
|
static DynamicColor OnPrimaryFixedVariant();
|
||||||
|
static DynamicColor SecondaryFixed();
|
||||||
|
static DynamicColor SecondaryFixedDim();
|
||||||
|
static DynamicColor OnSecondaryFixed();
|
||||||
|
static DynamicColor OnSecondaryFixedVariant();
|
||||||
|
static DynamicColor TertiaryFixed();
|
||||||
|
static DynamicColor TertiaryFixedDim();
|
||||||
|
static DynamicColor OnTertiaryFixed();
|
||||||
|
static DynamicColor OnTertiaryFixedVariant();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_DYNAMICCOLOR_MATERIAL_DYNAMIC_COLORS_H_
|
78
src/material-colors/cpp/dynamiccolor/tone_delta_pair.h
Normal file
78
src/material-colors/cpp/dynamiccolor/tone_delta_pair.h
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_DYNAMICCOLOR_TONE_DELTA_PAIR_H_
|
||||||
|
#define CPP_DYNAMICCOLOR_TONE_DELTA_PAIR_H_
|
||||||
|
|
||||||
|
#include "cpp/dynamiccolor/dynamic_color.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the different in tone between colors.
|
||||||
|
*/
|
||||||
|
enum class TonePolarity { kDarker, kLighter, kNearer, kFarther };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Documents a constraint between two DynamicColors, in which their tones must
|
||||||
|
* have a certain distance from each other.
|
||||||
|
*
|
||||||
|
* Prefer a DynamicColor with a background, this is for special cases when
|
||||||
|
* designers want tonal distance, literally contrast, between two colors that
|
||||||
|
* don't have a background / foreground relationship or a contrast guarantee.
|
||||||
|
*/
|
||||||
|
struct ToneDeltaPair {
|
||||||
|
DynamicColor role_a_;
|
||||||
|
DynamicColor role_b_;
|
||||||
|
double delta_;
|
||||||
|
TonePolarity polarity_;
|
||||||
|
bool stay_together_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Documents a constraint in tone distance between two DynamicColors.
|
||||||
|
*
|
||||||
|
* The polarity is an adjective that describes "A", compared to "B".
|
||||||
|
*
|
||||||
|
* For instance, ToneDeltaPair(A, B, 15, 'darker', stayTogether) states that
|
||||||
|
* A's tone should be at least 15 darker than B's.
|
||||||
|
*
|
||||||
|
* 'nearer' and 'farther' describes closeness to the surface roles. For
|
||||||
|
* instance, ToneDeltaPair(A, B, 10, 'nearer', stayTogether) states that A
|
||||||
|
* should be 10 lighter than B in light mode, and 10 darker than B in dark
|
||||||
|
* mode.
|
||||||
|
*
|
||||||
|
* @param roleA The first role in a pair.
|
||||||
|
* @param roleB The second role in a pair.
|
||||||
|
* @param delta Required difference between tones. Absolute value, negative
|
||||||
|
* values have undefined behavior.
|
||||||
|
* @param polarity The relative relation between tones of roleA and roleB,
|
||||||
|
* as described above.
|
||||||
|
* @param stayTogether Whether these two roles should stay on the same side of
|
||||||
|
* the "awkward zone" (T50-59). This is necessary for certain cases where
|
||||||
|
* one role has two backgrounds.
|
||||||
|
*/
|
||||||
|
ToneDeltaPair(DynamicColor role_a, DynamicColor role_b, double delta,
|
||||||
|
TonePolarity polarity, bool stay_together)
|
||||||
|
: role_a_(role_a),
|
||||||
|
role_b_(role_b),
|
||||||
|
delta_(delta),
|
||||||
|
polarity_(polarity),
|
||||||
|
stay_together_(stay_together) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_DYNAMICCOLOR_TONE_DELTA_PAIR_H_
|
36
src/material-colors/cpp/dynamiccolor/variant.h
Normal file
36
src/material-colors/cpp/dynamiccolor/variant.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_DYNAMICCOLOR_VARIANT_H_
|
||||||
|
#define CPP_DYNAMICCOLOR_VARIANT_H_
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
enum class Variant {
|
||||||
|
kMonochrome,
|
||||||
|
kNeutral,
|
||||||
|
kTonalSpot,
|
||||||
|
kVibrant,
|
||||||
|
kExpressive,
|
||||||
|
kFidelity,
|
||||||
|
kContent,
|
||||||
|
kRainbow,
|
||||||
|
kFruitSalad,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_DYNAMICCOLOR_VARIANT_H_
|
39
src/material-colors/cpp/palettes/core.h
Normal file
39
src/material-colors/cpp/palettes/core.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_PALETTES_CORE_H_
|
||||||
|
#define CPP_PALETTES_CORE_H_
|
||||||
|
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comprises foundational palettes to build a color scheme. Generated from a
|
||||||
|
* source color, these palettes will then be part of a [DynamicScheme] together
|
||||||
|
* with appearance preferences.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
TonalPalette primary;
|
||||||
|
TonalPalette secondary;
|
||||||
|
TonalPalette tertiary;
|
||||||
|
TonalPalette neutral;
|
||||||
|
TonalPalette neutral_variant;
|
||||||
|
} CorePalettes;
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_PALETTES_CORE_H_
|
116
src/material-colors/cpp/palettes/tones.cpp
Normal file
116
src/material-colors/cpp/palettes/tones.cpp
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "cpp/cam/cam.h"
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
TonalPalette::TonalPalette(Argb argb) : key_color_(0.0, 0.0, 0.0) {
|
||||||
|
Cam cam = CamFromInt(argb);
|
||||||
|
hue_ = cam.hue;
|
||||||
|
chroma_ = cam.chroma;
|
||||||
|
key_color_ = KeyColor(cam.hue, cam.chroma).create();
|
||||||
|
}
|
||||||
|
|
||||||
|
TonalPalette::TonalPalette(Hct hct)
|
||||||
|
: key_color_(hct.get_hue(), hct.get_chroma(), hct.get_tone()) {
|
||||||
|
hue_ = hct.get_hue();
|
||||||
|
chroma_ = hct.get_chroma();
|
||||||
|
}
|
||||||
|
|
||||||
|
TonalPalette::TonalPalette(double hue, double chroma)
|
||||||
|
: key_color_(hue, chroma, 0.0) {
|
||||||
|
hue_ = hue;
|
||||||
|
chroma_ = chroma;
|
||||||
|
key_color_ = KeyColor(hue, chroma).create();
|
||||||
|
}
|
||||||
|
|
||||||
|
TonalPalette::TonalPalette(double hue, double chroma, Hct key_color)
|
||||||
|
: key_color_(key_color.get_hue(), key_color.get_chroma(),
|
||||||
|
key_color.get_tone()) {
|
||||||
|
hue_ = hue;
|
||||||
|
chroma_ = chroma;
|
||||||
|
}
|
||||||
|
|
||||||
|
Argb TonalPalette::get(double tone) const {
|
||||||
|
return IntFromHcl(hue_, chroma_, tone);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyColor::KeyColor(double hue, double requested_chroma)
|
||||||
|
: hue_(hue), requested_chroma_(requested_chroma) {}
|
||||||
|
|
||||||
|
Hct KeyColor::create() {
|
||||||
|
// Pivot around T50 because T50 has the most chroma available, on
|
||||||
|
// average. Thus it is most likely to have a direct answer.
|
||||||
|
const int pivot_tone = 50;
|
||||||
|
const int tone_step_size = 1;
|
||||||
|
// Epsilon to accept values slightly higher than the requested chroma.
|
||||||
|
const double epsilon = 0.01;
|
||||||
|
|
||||||
|
// Binary search to find the tone that can provide a chroma that is closest
|
||||||
|
// to the requested chroma.
|
||||||
|
int lower_tone = 0;
|
||||||
|
int upper_tone = 100;
|
||||||
|
while (lower_tone < upper_tone) {
|
||||||
|
const int mid_tone = (lower_tone + upper_tone) / 2;
|
||||||
|
bool is_ascending =
|
||||||
|
max_chroma(mid_tone) < max_chroma(mid_tone + tone_step_size);
|
||||||
|
bool sufficient_chroma =
|
||||||
|
max_chroma(mid_tone) >= requested_chroma_ - epsilon;
|
||||||
|
|
||||||
|
if (sufficient_chroma) {
|
||||||
|
// Either range [lower_tone, mid_tone] or [mid_tone, upper_tone] has
|
||||||
|
// the answer, so search in the range that is closer the pivot tone.
|
||||||
|
if (abs(lower_tone - pivot_tone) < abs(upper_tone - pivot_tone)) {
|
||||||
|
upper_tone = mid_tone;
|
||||||
|
} else {
|
||||||
|
if (lower_tone == mid_tone) {
|
||||||
|
return Hct(hue_, requested_chroma_, lower_tone);
|
||||||
|
}
|
||||||
|
lower_tone = mid_tone;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// As there's no sufficient chroma in the mid_tone, follow the direction
|
||||||
|
// to the chroma peak.
|
||||||
|
if (is_ascending) {
|
||||||
|
lower_tone = mid_tone + tone_step_size;
|
||||||
|
} else {
|
||||||
|
// Keep mid_tone for potential chroma peak.
|
||||||
|
upper_tone = mid_tone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Hct(hue_, requested_chroma_, lower_tone);
|
||||||
|
}
|
||||||
|
|
||||||
|
double KeyColor::max_chroma(double tone) {
|
||||||
|
auto it = chroma_cache_.find(tone);
|
||||||
|
if (it != chroma_cache_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
double chroma = Hct(hue_, max_chroma_value_, tone).get_chroma();
|
||||||
|
chroma_cache_[tone] = chroma;
|
||||||
|
return chroma;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
78
src/material-colors/cpp/palettes/tones.h
Normal file
78
src/material-colors/cpp/palettes/tones.h
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_PALETTES_TONES_H_
|
||||||
|
#define CPP_PALETTES_TONES_H_
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
class TonalPalette {
|
||||||
|
public:
|
||||||
|
explicit TonalPalette(Argb argb);
|
||||||
|
TonalPalette(Hct hct);
|
||||||
|
TonalPalette(double hue, double chroma);
|
||||||
|
TonalPalette(double hue, double chroma, Hct key_color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color for a given tone in this palette.
|
||||||
|
*
|
||||||
|
* @param tone 0.0 <= tone <= 100.0
|
||||||
|
* @return a color as an integer, in ARGB format.
|
||||||
|
*/
|
||||||
|
Argb get(double tone) const;
|
||||||
|
|
||||||
|
double get_hue() const { return hue_; }
|
||||||
|
double get_chroma() const { return chroma_; }
|
||||||
|
Hct get_key_color() const { return key_color_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
double hue_;
|
||||||
|
double chroma_;
|
||||||
|
Hct key_color_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key color is a color that represents the hue and chroma of a tonal palette
|
||||||
|
*/
|
||||||
|
class KeyColor {
|
||||||
|
public:
|
||||||
|
KeyColor(double hue, double requested_chroma);
|
||||||
|
/**
|
||||||
|
* Creates a key color from a [hue] and a [chroma].
|
||||||
|
* The key color is the first tone, starting from T50, matching the given hue
|
||||||
|
* and chroma.
|
||||||
|
*
|
||||||
|
* @return Key color in Hct.
|
||||||
|
*/
|
||||||
|
Hct create();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const double max_chroma_value_ = 200.0;
|
||||||
|
double hue_;
|
||||||
|
double requested_chroma_;
|
||||||
|
// Cache that maps tone to max chroma to avoid duplicated HCT calculation.
|
||||||
|
std::unordered_map<double, double> chroma_cache_;
|
||||||
|
|
||||||
|
double max_chroma(double tone);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
#endif // CPP_PALETTES_TONES_H_
|
85
src/material-colors/cpp/palettes/tones_test.cpp
Normal file
85
src/material-colors/cpp/palettes/tones_test.cpp
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(TonesTest, Blue) {
|
||||||
|
Argb color = 0xff0000ff;
|
||||||
|
TonalPalette tonal_palette = TonalPalette(color);
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(100)), "ffffffff");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(95)), "fff1efff");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(90)), "ffe0e0ff");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(80)), "ffbec2ff");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(70)), "ff9da3ff");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(60)), "ff7c84ff");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(50)), "ff5a64ff");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(40)), "ff343dff");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(30)), "ff0000ef");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(20)), "ff0001ac");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(10)), "ff00006e");
|
||||||
|
EXPECT_EQ(HexFromArgb(tonal_palette.get(0)), "ff000000");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(KeyColorTests, ExactChromaAvailable) {
|
||||||
|
// Requested chroma is exactly achievable at a certain tone.
|
||||||
|
TonalPalette palette = TonalPalette(50.0, 60.0);
|
||||||
|
Hct result = palette.get_key_color();
|
||||||
|
|
||||||
|
EXPECT_NEAR(result.get_hue(), 50.0, 10.0);
|
||||||
|
EXPECT_NEAR(result.get_chroma(), 60.0, 0.5);
|
||||||
|
// Tone might vary, but should be within the range from 0 to 100.
|
||||||
|
EXPECT_GT(result.get_tone(), 0);
|
||||||
|
EXPECT_LT(result.get_tone(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(KeyColorTests, UnusuallyHighChroma) {
|
||||||
|
// Requested chroma is above what is achievable. For Hue 149, chroma peak
|
||||||
|
// is 89.6 at Tone 87.9. The result key color's chroma should be close to the
|
||||||
|
// chroma peak.
|
||||||
|
TonalPalette palette = TonalPalette(149.0, 200.0);
|
||||||
|
Hct result = palette.get_key_color();
|
||||||
|
|
||||||
|
EXPECT_NEAR(result.get_hue(), 149.0, 10.0);
|
||||||
|
EXPECT_GT(result.get_chroma(), 89.0);
|
||||||
|
// Tone might vary, but should be within the range from 0 to 100.
|
||||||
|
EXPECT_GT(result.get_tone(), 0);
|
||||||
|
EXPECT_LT(result.get_tone(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(KeyColorTests, UnusuallyLowChroma) {
|
||||||
|
// By definition, the key color should be the first tone, starting from Tone
|
||||||
|
// 50, matching the given hue and chroma. When requesting a very low chroma,
|
||||||
|
// the result should be close to Tone 50, since most tones can produce a low
|
||||||
|
// chroma.
|
||||||
|
TonalPalette palette = TonalPalette(50.0, 3.0);
|
||||||
|
Hct result = palette.get_key_color();
|
||||||
|
|
||||||
|
// Higher error tolerance for hue when the requested chroma is unusually low.
|
||||||
|
EXPECT_NEAR(result.get_hue(), 50.0, 10.0);
|
||||||
|
EXPECT_NEAR(result.get_chroma(), 3.0, 0.5);
|
||||||
|
EXPECT_NEAR(result.get_tone(), 50.0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
60
src/material-colors/cpp/quantize/celebi.cpp
Normal file
60
src/material-colors/cpp/quantize/celebi.cpp
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/quantize/celebi.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/quantize/wsmeans.h"
|
||||||
|
#include "cpp/quantize/wu.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
QuantizerResult QuantizeCelebi(const std::vector<Argb>& pixels,
|
||||||
|
uint16_t max_colors) {
|
||||||
|
if (max_colors == 0 || pixels.empty()) {
|
||||||
|
return QuantizerResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_colors > 256) {
|
||||||
|
max_colors = 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pixel_count = pixels.size();
|
||||||
|
|
||||||
|
std::vector<Argb> opaque_pixels;
|
||||||
|
opaque_pixels.reserve(pixel_count);
|
||||||
|
for (int i = 0; i < pixel_count; i++) {
|
||||||
|
int pixel = pixels[i];
|
||||||
|
if (!IsOpaque(pixel)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
opaque_pixels.push_back(pixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Argb> wu_result = QuantizeWu(opaque_pixels, max_colors);
|
||||||
|
|
||||||
|
QuantizerResult result =
|
||||||
|
QuantizeWsmeans(opaque_pixels, wu_result, max_colors);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
35
src/material-colors/cpp/quantize/celebi.h
Normal file
35
src/material-colors/cpp/quantize/celebi.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_QUANTIZE_CELEBI_H_
|
||||||
|
#define CPP_QUANTIZE_CELEBI_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/quantize/wsmeans.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
QuantizerResult QuantizeCelebi(const std::vector<Argb>& pixels,
|
||||||
|
uint16_t max_colors);
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_QUANTIZE_CELEBI_H_
|
131
src/material-colors/cpp/quantize/celebi_test.cpp
Normal file
131
src/material-colors/cpp/quantize/celebi_test.cpp
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/quantize/celebi.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(CelebiTest, FullImage) {
|
||||||
|
std::vector<Argb> pixels(12544);
|
||||||
|
for (size_t i = 0; i < pixels.size(); i++) {
|
||||||
|
// Creates 128 distinct colors
|
||||||
|
pixels[i] = i % 8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
int iterations = 1;
|
||||||
|
uint16_t max_colors = 128;
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < iterations; i++) {
|
||||||
|
clock_t begin = clock();
|
||||||
|
QuantizeCelebi(pixels, max_colors);
|
||||||
|
clock_t end = clock();
|
||||||
|
double time_spent = static_cast<double>(end - begin) / CLOCKS_PER_SEC;
|
||||||
|
sum += time_spent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CelebiTest, OneRed) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
QuantizerResult result = QuantizeCelebi(pixels, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xffff0000], 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CelebiTest, OneGreen) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
QuantizerResult result = QuantizeCelebi(pixels, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff00ff00], 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CelebiTest, OneBlue) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xff0000ff);
|
||||||
|
QuantizerResult result = QuantizeCelebi(pixels, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff0000ff], 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CelebiTest, FiveBlue) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
pixels.push_back(0xff0000ff);
|
||||||
|
}
|
||||||
|
QuantizerResult result = QuantizeCelebi(pixels, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff0000ff], 5u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CelebiTest, OneRedOneGreenOneBlue) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
pixels.push_back(0xff0000ff);
|
||||||
|
QuantizerResult result = QuantizeCelebi(pixels, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 3u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xffff0000], 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff00ff00], 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff0000ff], 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CelebiTest, TwoRedThreeGreen) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
QuantizerResult result = QuantizeCelebi(pixels, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 2u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xffff0000], 2u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff00ff00], 3u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CelebiTest, NoColors) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xFFFFFFFF);
|
||||||
|
QuantizerResult result = QuantizeCelebi(pixels, 0);
|
||||||
|
EXPECT_TRUE(result.color_to_count.empty());
|
||||||
|
EXPECT_TRUE(result.input_pixel_to_cluster_pixel.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CelebiTest, SingleTransparent) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0x20F93013);
|
||||||
|
QuantizerResult result = QuantizeCelebi(pixels, 1);
|
||||||
|
EXPECT_TRUE(result.color_to_count.empty());
|
||||||
|
EXPECT_TRUE(result.input_pixel_to_cluster_pixel.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CelebiTest, TooManyColors) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
QuantizerResult result = QuantizeCelebi(pixels, 32767);
|
||||||
|
EXPECT_TRUE(result.color_to_count.empty());
|
||||||
|
EXPECT_TRUE(result.input_pixel_to_cluster_pixel.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
96
src/material-colors/cpp/quantize/lab.cpp
Normal file
96
src/material-colors/cpp/quantize/lab.cpp
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/quantize/lab.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
Argb IntFromLab(const Lab lab) {
|
||||||
|
double e = 216.0 / 24389.0;
|
||||||
|
double kappa = 24389.0 / 27.0;
|
||||||
|
double ke = 8.0;
|
||||||
|
|
||||||
|
double fy = (lab.l + 16.0) / 116.0;
|
||||||
|
double fx = (lab.a / 500.0) + fy;
|
||||||
|
double fz = fy - (lab.b / 200.0);
|
||||||
|
double fx3 = fx * fx * fx;
|
||||||
|
double x_normalized = (fx3 > e) ? fx3 : (116.0 * fx - 16.0) / kappa;
|
||||||
|
double y_normalized = (lab.l > ke) ? fy * fy * fy : (lab.l / kappa);
|
||||||
|
double fz3 = fz * fz * fz;
|
||||||
|
double z_normalized = (fz3 > e) ? fz3 : (116.0 * fz - 16.0) / kappa;
|
||||||
|
double x = x_normalized * kWhitePointD65[0];
|
||||||
|
double y = y_normalized * kWhitePointD65[1];
|
||||||
|
double z = z_normalized * kWhitePointD65[2];
|
||||||
|
|
||||||
|
// intFromXyz
|
||||||
|
double rL = 3.2406 * x - 1.5372 * y - 0.4986 * z;
|
||||||
|
double gL = -0.9689 * x + 1.8758 * y + 0.0415 * z;
|
||||||
|
double bL = 0.0557 * x - 0.2040 * y + 1.0570 * z;
|
||||||
|
|
||||||
|
int red = Delinearized(rL);
|
||||||
|
int green = Delinearized(gL);
|
||||||
|
int blue = Delinearized(bL);
|
||||||
|
|
||||||
|
return ArgbFromRgb(red, green, blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lab LabFromInt(const Argb argb) {
|
||||||
|
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;
|
||||||
|
double y_normalized = y / kWhitePointD65[1];
|
||||||
|
double e = 216.0 / 24389.0;
|
||||||
|
double kappa = 24389.0 / 27.0;
|
||||||
|
double fy;
|
||||||
|
if (y_normalized > e) {
|
||||||
|
fy = pow(y_normalized, 1.0 / 3.0);
|
||||||
|
} else {
|
||||||
|
fy = (kappa * y_normalized + 16) / 116;
|
||||||
|
}
|
||||||
|
|
||||||
|
double x_normalized = x / kWhitePointD65[0];
|
||||||
|
double fx;
|
||||||
|
if (x_normalized > e) {
|
||||||
|
fx = pow(x_normalized, 1.0 / 3.0);
|
||||||
|
} else {
|
||||||
|
fx = (kappa * x_normalized + 16) / 116;
|
||||||
|
}
|
||||||
|
|
||||||
|
double z_normalized = z / kWhitePointD65[2];
|
||||||
|
double fz;
|
||||||
|
if (z_normalized > e) {
|
||||||
|
fz = pow(z_normalized, 1.0 / 3.0);
|
||||||
|
} else {
|
||||||
|
fz = (kappa * z_normalized + 16) / 116;
|
||||||
|
}
|
||||||
|
|
||||||
|
double l = 116.0 * fy - 16;
|
||||||
|
double a = 500.0 * (fx - fy);
|
||||||
|
double b = 200.0 * (fy - fz);
|
||||||
|
return {l, a, b};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
57
src/material-colors/cpp/quantize/lab.h
Normal file
57
src/material-colors/cpp/quantize/lab.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_QUANTIZE_LAB_H_
|
||||||
|
#define CPP_QUANTIZE_LAB_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct Lab {
|
||||||
|
double l = 0.0;
|
||||||
|
double a = 0.0;
|
||||||
|
double b = 0.0;
|
||||||
|
|
||||||
|
double DeltaE(const Lab& lab) {
|
||||||
|
double d_l = l - lab.l;
|
||||||
|
double d_a = a - lab.a;
|
||||||
|
double d_b = b - lab.b;
|
||||||
|
return (d_l * d_l) + (d_a * d_a) + (d_b * d_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToString() {
|
||||||
|
return "Lab: L* " + std::to_string(l) + " a* " + std::to_string(a) +
|
||||||
|
" b* " + std::to_string(b);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Argb IntFromLab(const Lab lab);
|
||||||
|
Lab LabFromInt(const Argb argb);
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
#endif // CPP_QUANTIZE_LAB_H_
|
267
src/material-colors/cpp/quantize/wsmeans.cpp
Normal file
267
src/material-colors/cpp/quantize/wsmeans.cpp
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/quantize/wsmeans.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/quantize/lab.h"
|
||||||
|
|
||||||
|
constexpr int kMaxIterations = 100;
|
||||||
|
constexpr double kMinDeltaE = 3.0;
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct Swatch {
|
||||||
|
Argb argb = 0;
|
||||||
|
int population = 0;
|
||||||
|
|
||||||
|
bool operator<(const Swatch& b) const { return population > b.population; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DistanceToIndex {
|
||||||
|
double distance = 0.0;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
bool operator<(const DistanceToIndex& a) const {
|
||||||
|
return distance < a.distance;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QuantizerResult QuantizeWsmeans(const std::vector<Argb>& input_pixels,
|
||||||
|
const std::vector<Argb>& starting_clusters,
|
||||||
|
uint16_t max_colors) {
|
||||||
|
if (max_colors == 0 || input_pixels.empty()) {
|
||||||
|
return QuantizerResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_colors > 256) {
|
||||||
|
// If colors is outside the range, just set it the max.
|
||||||
|
max_colors = 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t pixel_count = input_pixels.size();
|
||||||
|
//absl::flat_hash_map<Argb, int> pixel_to_count;
|
||||||
|
std::unordered_map<Argb, int> pixel_to_count;
|
||||||
|
|
||||||
|
std::vector<uint32_t> pixels;
|
||||||
|
pixels.reserve(pixel_count);
|
||||||
|
std::vector<Lab> points;
|
||||||
|
points.reserve(pixel_count);
|
||||||
|
for (Argb pixel : input_pixels) {
|
||||||
|
// tested over 1000 runs with 128 colors, 12544 (112 x 112)
|
||||||
|
// std::map 10.9 ms
|
||||||
|
// std::unordered_map 10.2 ms
|
||||||
|
// absl::btree_map 9.0 ms
|
||||||
|
// absl::flat_hash_map 8.0 ms
|
||||||
|
auto it = pixel_to_count.find(pixel);
|
||||||
|
if (it != pixel_to_count.end()) {
|
||||||
|
it->second++;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pixels.push_back(pixel);
|
||||||
|
points.push_back(LabFromInt(pixel));
|
||||||
|
pixel_to_count[pixel] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int cluster_count = std::min((int)max_colors, (int)points.size());
|
||||||
|
|
||||||
|
if (!starting_clusters.empty()) {
|
||||||
|
cluster_count = std::min(cluster_count, (int)starting_clusters.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int pixel_count_sums[256] = {};
|
||||||
|
std::vector<Lab> clusters;
|
||||||
|
clusters.reserve(starting_clusters.size());
|
||||||
|
for (int argb : starting_clusters) {
|
||||||
|
clusters.push_back(LabFromInt(argb));
|
||||||
|
}
|
||||||
|
|
||||||
|
srand(42688);
|
||||||
|
int additional_clusters_needed = cluster_count - clusters.size();
|
||||||
|
if (starting_clusters.empty() && additional_clusters_needed > 0) {
|
||||||
|
for (int i = 0; i < additional_clusters_needed; i++) {
|
||||||
|
// Adds a random Lab color to clusters.
|
||||||
|
double l = rand() / (static_cast<double>(RAND_MAX)) * (100.0) + 0.0;
|
||||||
|
double a =
|
||||||
|
rand() / (static_cast<double>(RAND_MAX)) * (100.0 - -100.0) - 100.0;
|
||||||
|
double b =
|
||||||
|
rand() / (static_cast<double>(RAND_MAX)) * (100.0 - -100.0) - 100.0;
|
||||||
|
clusters.push_back({l, a, b});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> cluster_indices;
|
||||||
|
cluster_indices.reserve(points.size());
|
||||||
|
|
||||||
|
srand(42688);
|
||||||
|
for (size_t i = 0; i < points.size(); i++) {
|
||||||
|
cluster_indices.push_back(rand() % cluster_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<int>> index_matrix(
|
||||||
|
cluster_count, std::vector<int>(cluster_count, 0));
|
||||||
|
|
||||||
|
std::vector<std::vector<DistanceToIndex>> distance_to_index_matrix(
|
||||||
|
cluster_count, std::vector<DistanceToIndex>(cluster_count));
|
||||||
|
|
||||||
|
for (int iteration = 0; iteration < kMaxIterations; iteration++) {
|
||||||
|
// Calculate cluster distances
|
||||||
|
for (int i = 0; i < cluster_count; i++) {
|
||||||
|
distance_to_index_matrix[i][i].distance = 0;
|
||||||
|
distance_to_index_matrix[i][i].index = i;
|
||||||
|
for (int j = i + 1; j < cluster_count; j++) {
|
||||||
|
double distance = clusters[i].DeltaE(clusters[j]);
|
||||||
|
|
||||||
|
distance_to_index_matrix[j][i].distance = distance;
|
||||||
|
distance_to_index_matrix[j][i].index = i;
|
||||||
|
distance_to_index_matrix[i][j].distance = distance;
|
||||||
|
distance_to_index_matrix[i][j].index = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DistanceToIndex> row = distance_to_index_matrix[i];
|
||||||
|
std::sort(row.begin(), row.end());
|
||||||
|
|
||||||
|
for (int j = 0; j < cluster_count; j++) {
|
||||||
|
index_matrix[i][j] = row[j].index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reassign points
|
||||||
|
bool color_moved = false;
|
||||||
|
for (size_t i = 0; i < points.size(); i++) {
|
||||||
|
Lab point = points[i];
|
||||||
|
|
||||||
|
int previous_cluster_index = cluster_indices[i];
|
||||||
|
Lab previous_cluster = clusters[previous_cluster_index];
|
||||||
|
double previous_distance = point.DeltaE(previous_cluster);
|
||||||
|
double minimum_distance = previous_distance;
|
||||||
|
int new_cluster_index = -1;
|
||||||
|
|
||||||
|
for (int j = 0; j < cluster_count; j++) {
|
||||||
|
if (distance_to_index_matrix[previous_cluster_index][j].distance >=
|
||||||
|
4 * previous_distance) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
double distance = point.DeltaE(clusters[j]);
|
||||||
|
if (distance < minimum_distance) {
|
||||||
|
minimum_distance = distance;
|
||||||
|
new_cluster_index = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (new_cluster_index != -1) {
|
||||||
|
double distanceChange =
|
||||||
|
abs(sqrt(minimum_distance) - sqrt(previous_distance));
|
||||||
|
if (distanceChange > kMinDeltaE) {
|
||||||
|
color_moved = true;
|
||||||
|
cluster_indices[i] = new_cluster_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!color_moved && (iteration != 0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalculate cluster centers
|
||||||
|
double component_a_sums[256] = {};
|
||||||
|
double component_b_sums[256] = {};
|
||||||
|
double component_c_sums[256] = {};
|
||||||
|
for (int i = 0; i < cluster_count; i++) {
|
||||||
|
pixel_count_sums[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < points.size(); i++) {
|
||||||
|
int clusterIndex = cluster_indices[i];
|
||||||
|
Lab point = points[i];
|
||||||
|
int count = pixel_to_count[pixels[i]];
|
||||||
|
|
||||||
|
pixel_count_sums[clusterIndex] += count;
|
||||||
|
component_a_sums[clusterIndex] += (point.l * count);
|
||||||
|
component_b_sums[clusterIndex] += (point.a * count);
|
||||||
|
component_c_sums[clusterIndex] += (point.b * count);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < cluster_count; i++) {
|
||||||
|
int count = pixel_count_sums[i];
|
||||||
|
if (count == 0) {
|
||||||
|
clusters[i] = {0, 0, 0};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
double a = component_a_sums[i] / count;
|
||||||
|
double b = component_b_sums[i] / count;
|
||||||
|
double c = component_c_sums[i] / count;
|
||||||
|
clusters[i] = {a, b, c};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Swatch> swatches;
|
||||||
|
std::vector<Argb> cluster_argbs;
|
||||||
|
std::vector<Argb> all_cluster_argbs;
|
||||||
|
for (int i = 0; i < cluster_count; i++) {
|
||||||
|
Argb possible_new_cluster = IntFromLab(clusters[i]);
|
||||||
|
all_cluster_argbs.push_back(possible_new_cluster);
|
||||||
|
|
||||||
|
int count = pixel_count_sums[i];
|
||||||
|
if (count == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int use_new_cluster = 1;
|
||||||
|
for (size_t j = 0; j < swatches.size(); j++) {
|
||||||
|
if (swatches[j].argb == possible_new_cluster) {
|
||||||
|
swatches[j].population += count;
|
||||||
|
use_new_cluster = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use_new_cluster == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cluster_argbs.push_back(possible_new_cluster);
|
||||||
|
swatches.push_back({possible_new_cluster, count});
|
||||||
|
}
|
||||||
|
std::sort(swatches.begin(), swatches.end());
|
||||||
|
|
||||||
|
// Constructs the quantizer result to return.
|
||||||
|
|
||||||
|
std::map<Argb, uint32_t> color_to_count;
|
||||||
|
for (size_t i = 0; i < swatches.size(); i++) {
|
||||||
|
color_to_count[swatches[i].argb] = swatches[i].population;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<Argb, Argb> input_pixel_to_cluster_pixel;
|
||||||
|
for (size_t i = 0; i < points.size(); i++) {
|
||||||
|
int pixel = pixels[i];
|
||||||
|
int cluster_index = cluster_indices[i];
|
||||||
|
int cluster_argb = all_cluster_argbs[cluster_index];
|
||||||
|
input_pixel_to_cluster_pixel[pixel] = cluster_argb;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {color_to_count, input_pixel_to_cluster_pixel};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
38
src/material-colors/cpp/quantize/wsmeans.h
Normal file
38
src/material-colors/cpp/quantize/wsmeans.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_QUANTIZE_WSMEANS_H_
|
||||||
|
#define CPP_QUANTIZE_WSMEANS_H_
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct QuantizerResult {
|
||||||
|
std::map<Argb, uint32_t> color_to_count;
|
||||||
|
std::map<Argb, Argb> input_pixel_to_cluster_pixel;
|
||||||
|
};
|
||||||
|
|
||||||
|
QuantizerResult QuantizeWsmeans(const std::vector<Argb>& input_pixels,
|
||||||
|
const std::vector<Argb>& starting_clusters,
|
||||||
|
uint16_t max_colors);
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_QUANTIZE_WSMEANS_H_
|
96
src/material-colors/cpp/quantize/wsmeans_test.cpp
Normal file
96
src/material-colors/cpp/quantize/wsmeans_test.cpp
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/quantize/wsmeans.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
TEST(WsmeansTest, FullImage) {
|
||||||
|
std::vector<Argb> pixels(12544);
|
||||||
|
for (size_t i = 0; i < pixels.size(); i++) {
|
||||||
|
// Creates 128 distinct colors
|
||||||
|
pixels[i] = i % 8000;
|
||||||
|
}
|
||||||
|
std::vector<Argb> starting_clusters;
|
||||||
|
|
||||||
|
int iterations = 1;
|
||||||
|
int max_colors = 128;
|
||||||
|
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < iterations; i++) {
|
||||||
|
clock_t begin = clock();
|
||||||
|
QuantizeWsmeans(pixels, starting_clusters, max_colors);
|
||||||
|
clock_t end = clock();
|
||||||
|
double time_spent = static_cast<double>(end - begin) / CLOCKS_PER_SEC;
|
||||||
|
sum += time_spent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WsmeansTest, OneRedAndO) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xff141216);
|
||||||
|
std::vector<Argb> starting_clusters;
|
||||||
|
QuantizerResult result = QuantizeWsmeans(pixels, starting_clusters, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff141216], 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WsmeansTest, OneRed) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
std::vector<Argb> starting_clusters;
|
||||||
|
QuantizerResult result = QuantizeWsmeans(pixels, starting_clusters, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xffff0000], 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WsmeansTest, OneGreen) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
std::vector<Argb> starting_clusters;
|
||||||
|
QuantizerResult result = QuantizeWsmeans(pixels, starting_clusters, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff00ff00], 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WsmeansTest, OneBlue) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xff0000ff);
|
||||||
|
std::vector<Argb> starting_clusters;
|
||||||
|
QuantizerResult result = QuantizeWsmeans(pixels, starting_clusters, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff0000ff], 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WsmeansTest, FiveBlue) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
pixels.push_back(0xff0000ff);
|
||||||
|
}
|
||||||
|
std::vector<Argb> starting_clusters;
|
||||||
|
QuantizerResult result = QuantizeWsmeans(pixels, starting_clusters, 256);
|
||||||
|
EXPECT_EQ(result.color_to_count.size(), 1u);
|
||||||
|
EXPECT_EQ(result.color_to_count[0xff0000ff], 5u);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
357
src/material-colors/cpp/quantize/wu.cpp
Normal file
357
src/material-colors/cpp/quantize/wu.cpp
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/quantize/wu.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct Box {
|
||||||
|
int r0 = 0;
|
||||||
|
int r1 = 0;
|
||||||
|
int g0 = 0;
|
||||||
|
int g1 = 0;
|
||||||
|
int b0 = 0;
|
||||||
|
int b1 = 0;
|
||||||
|
int vol = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Direction {
|
||||||
|
kRed,
|
||||||
|
kGreen,
|
||||||
|
kBlue,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int kIndexBits = 5;
|
||||||
|
constexpr int kIndexCount = ((1 << kIndexBits) + 1);
|
||||||
|
constexpr int kTotalSize = (kIndexCount * kIndexCount * kIndexCount);
|
||||||
|
constexpr int kMaxColors = 256;
|
||||||
|
|
||||||
|
using IntArray = std::vector<int64_t>;
|
||||||
|
using DoubleArray = std::vector<double>;
|
||||||
|
|
||||||
|
int GetIndex(int r, int g, int b) {
|
||||||
|
return (r << (kIndexBits * 2)) + (r << (kIndexBits + 1)) + (g << kIndexBits) +
|
||||||
|
r + g + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConstructHistogram(const std::vector<Argb>& pixels, IntArray& weights,
|
||||||
|
IntArray& m_r, IntArray& m_g, IntArray& m_b,
|
||||||
|
DoubleArray& moments) {
|
||||||
|
for (size_t i = 0; i < pixels.size(); i++) {
|
||||||
|
Argb pixel = pixels[i];
|
||||||
|
int red = RedFromInt(pixel);
|
||||||
|
int green = GreenFromInt(pixel);
|
||||||
|
int blue = BlueFromInt(pixel);
|
||||||
|
|
||||||
|
int bits_to_remove = 8 - kIndexBits;
|
||||||
|
int index_r = (red >> bits_to_remove) + 1;
|
||||||
|
int index_g = (green >> bits_to_remove) + 1;
|
||||||
|
int index_b = (blue >> bits_to_remove) + 1;
|
||||||
|
int index = GetIndex(index_r, index_g, index_b);
|
||||||
|
|
||||||
|
weights[index]++;
|
||||||
|
m_r[index] += red;
|
||||||
|
m_g[index] += green;
|
||||||
|
m_b[index] += blue;
|
||||||
|
moments[index] += (red * red) + (green * green) + (blue * blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputeMoments(IntArray& weights, IntArray& m_r, IntArray& m_g,
|
||||||
|
IntArray& m_b, DoubleArray& moments) {
|
||||||
|
for (int r = 1; r < kIndexCount; r++) {
|
||||||
|
int64_t area[kIndexCount] = {};
|
||||||
|
int64_t area_r[kIndexCount] = {};
|
||||||
|
int64_t area_g[kIndexCount] = {};
|
||||||
|
int64_t area_b[kIndexCount] = {};
|
||||||
|
double area_2[kIndexCount] = {};
|
||||||
|
for (int g = 1; g < kIndexCount; g++) {
|
||||||
|
int64_t line = 0;
|
||||||
|
int64_t line_r = 0;
|
||||||
|
int64_t line_g = 0;
|
||||||
|
int64_t line_b = 0;
|
||||||
|
double line_2 = 0.0;
|
||||||
|
for (int b = 1; b < kIndexCount; b++) {
|
||||||
|
int index = GetIndex(r, g, b);
|
||||||
|
line += weights[index];
|
||||||
|
line_r += m_r[index];
|
||||||
|
line_g += m_g[index];
|
||||||
|
line_b += m_b[index];
|
||||||
|
line_2 += moments[index];
|
||||||
|
|
||||||
|
area[b] += line;
|
||||||
|
area_r[b] += line_r;
|
||||||
|
area_g[b] += line_g;
|
||||||
|
area_b[b] += line_b;
|
||||||
|
area_2[b] += line_2;
|
||||||
|
|
||||||
|
int previous_index = GetIndex(r - 1, g, b);
|
||||||
|
weights[index] = weights[previous_index] + area[b];
|
||||||
|
m_r[index] = m_r[previous_index] + area_r[b];
|
||||||
|
m_g[index] = m_g[previous_index] + area_g[b];
|
||||||
|
m_b[index] = m_b[previous_index] + area_b[b];
|
||||||
|
moments[index] = moments[previous_index] + area_2[b];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Top(const Box& cube, const Direction direction, const int position,
|
||||||
|
const IntArray& moment) {
|
||||||
|
if (direction == Direction::kRed) {
|
||||||
|
return (moment[GetIndex(position, cube.g1, cube.b1)] -
|
||||||
|
moment[GetIndex(position, cube.g1, cube.b0)] -
|
||||||
|
moment[GetIndex(position, cube.g0, cube.b1)] +
|
||||||
|
moment[GetIndex(position, cube.g0, cube.b0)]);
|
||||||
|
} else if (direction == Direction::kGreen) {
|
||||||
|
return (moment[GetIndex(cube.r1, position, cube.b1)] -
|
||||||
|
moment[GetIndex(cube.r1, position, cube.b0)] -
|
||||||
|
moment[GetIndex(cube.r0, position, cube.b1)] +
|
||||||
|
moment[GetIndex(cube.r0, position, cube.b0)]);
|
||||||
|
} else {
|
||||||
|
return (moment[GetIndex(cube.r1, cube.g1, position)] -
|
||||||
|
moment[GetIndex(cube.r1, cube.g0, position)] -
|
||||||
|
moment[GetIndex(cube.r0, cube.g1, position)] +
|
||||||
|
moment[GetIndex(cube.r0, cube.g0, position)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Bottom(const Box& cube, const Direction direction,
|
||||||
|
const IntArray& moment) {
|
||||||
|
if (direction == Direction::kRed) {
|
||||||
|
return (-moment[GetIndex(cube.r0, cube.g1, cube.b1)] +
|
||||||
|
moment[GetIndex(cube.r0, cube.g1, cube.b0)] +
|
||||||
|
moment[GetIndex(cube.r0, cube.g0, cube.b1)] -
|
||||||
|
moment[GetIndex(cube.r0, cube.g0, cube.b0)]);
|
||||||
|
} else if (direction == Direction::kGreen) {
|
||||||
|
return (-moment[GetIndex(cube.r1, cube.g0, cube.b1)] +
|
||||||
|
moment[GetIndex(cube.r1, cube.g0, cube.b0)] +
|
||||||
|
moment[GetIndex(cube.r0, cube.g0, cube.b1)] -
|
||||||
|
moment[GetIndex(cube.r0, cube.g0, cube.b0)]);
|
||||||
|
} else {
|
||||||
|
return (-moment[GetIndex(cube.r1, cube.g1, cube.b0)] +
|
||||||
|
moment[GetIndex(cube.r1, cube.g0, cube.b0)] +
|
||||||
|
moment[GetIndex(cube.r0, cube.g1, cube.b0)] -
|
||||||
|
moment[GetIndex(cube.r0, cube.g0, cube.b0)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Vol(const Box& cube, const IntArray& moment) {
|
||||||
|
return (moment[GetIndex(cube.r1, cube.g1, cube.b1)] -
|
||||||
|
moment[GetIndex(cube.r1, cube.g1, cube.b0)] -
|
||||||
|
moment[GetIndex(cube.r1, cube.g0, cube.b1)] +
|
||||||
|
moment[GetIndex(cube.r1, cube.g0, cube.b0)] -
|
||||||
|
moment[GetIndex(cube.r0, cube.g1, cube.b1)] +
|
||||||
|
moment[GetIndex(cube.r0, cube.g1, cube.b0)] +
|
||||||
|
moment[GetIndex(cube.r0, cube.g0, cube.b1)] -
|
||||||
|
moment[GetIndex(cube.r0, cube.g0, cube.b0)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Variance(const Box& cube, const IntArray& weights, const IntArray& m_r,
|
||||||
|
const IntArray& m_g, const IntArray& m_b,
|
||||||
|
const DoubleArray& moments) {
|
||||||
|
double dr = Vol(cube, m_r);
|
||||||
|
double dg = Vol(cube, m_g);
|
||||||
|
double db = Vol(cube, m_b);
|
||||||
|
double xx = moments[GetIndex(cube.r1, cube.g1, cube.b1)] -
|
||||||
|
moments[GetIndex(cube.r1, cube.g1, cube.b0)] -
|
||||||
|
moments[GetIndex(cube.r1, cube.g0, cube.b1)] +
|
||||||
|
moments[GetIndex(cube.r1, cube.g0, cube.b0)] -
|
||||||
|
moments[GetIndex(cube.r0, cube.g1, cube.b1)] +
|
||||||
|
moments[GetIndex(cube.r0, cube.g1, cube.b0)] +
|
||||||
|
moments[GetIndex(cube.r0, cube.g0, cube.b1)] -
|
||||||
|
moments[GetIndex(cube.r0, cube.g0, cube.b0)];
|
||||||
|
double hypotenuse = dr * dr + dg * dg + db * db;
|
||||||
|
double volume = Vol(cube, weights);
|
||||||
|
return xx - hypotenuse / volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Maximize(const Box& cube, const Direction direction, const int first,
|
||||||
|
const int last, int* cut, const int64_t whole_w,
|
||||||
|
const int64_t whole_r, const int64_t whole_g,
|
||||||
|
const int64_t whole_b, const IntArray& weights,
|
||||||
|
const IntArray& m_r, const IntArray& m_g, const IntArray& m_b) {
|
||||||
|
int64_t bottom_r = Bottom(cube, direction, m_r);
|
||||||
|
int64_t bottom_g = Bottom(cube, direction, m_g);
|
||||||
|
int64_t bottom_b = Bottom(cube, direction, m_b);
|
||||||
|
int64_t bottom_w = Bottom(cube, direction, weights);
|
||||||
|
|
||||||
|
double max = 0.0;
|
||||||
|
*cut = -1;
|
||||||
|
|
||||||
|
int64_t half_r, half_g, half_b, half_w;
|
||||||
|
for (int i = first; i < last; i++) {
|
||||||
|
half_r = bottom_r + Top(cube, direction, i, m_r);
|
||||||
|
half_g = bottom_g + Top(cube, direction, i, m_g);
|
||||||
|
half_b = bottom_b + Top(cube, direction, i, m_b);
|
||||||
|
half_w = bottom_w + Top(cube, direction, i, weights);
|
||||||
|
if (half_w == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double temp = (static_cast<double>(half_r) * half_r +
|
||||||
|
static_cast<double>(half_g) * half_g +
|
||||||
|
static_cast<double>(half_b) * half_b) /
|
||||||
|
static_cast<double>(half_w);
|
||||||
|
|
||||||
|
half_r = whole_r - half_r;
|
||||||
|
half_g = whole_g - half_g;
|
||||||
|
half_b = whole_b - half_b;
|
||||||
|
half_w = whole_w - half_w;
|
||||||
|
if (half_w == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
temp += (static_cast<double>(half_r) * half_r +
|
||||||
|
static_cast<double>(half_g) * half_g +
|
||||||
|
static_cast<double>(half_b) * half_b) /
|
||||||
|
static_cast<double>(half_w);
|
||||||
|
|
||||||
|
if (temp > max) {
|
||||||
|
max = temp;
|
||||||
|
*cut = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cut(Box& box1, Box& box2, const IntArray& weights, const IntArray& m_r,
|
||||||
|
const IntArray& m_g, const IntArray& m_b) {
|
||||||
|
int64_t whole_r = Vol(box1, m_r);
|
||||||
|
int64_t whole_g = Vol(box1, m_g);
|
||||||
|
int64_t whole_b = Vol(box1, m_b);
|
||||||
|
int64_t whole_w = Vol(box1, weights);
|
||||||
|
|
||||||
|
int cut_r, cut_g, cut_b;
|
||||||
|
double max_r =
|
||||||
|
Maximize(box1, Direction::kRed, box1.r0 + 1, box1.r1, &cut_r, whole_w,
|
||||||
|
whole_r, whole_g, whole_b, weights, m_r, m_g, m_b);
|
||||||
|
double max_g =
|
||||||
|
Maximize(box1, Direction::kGreen, box1.g0 + 1, box1.g1, &cut_g, whole_w,
|
||||||
|
whole_r, whole_g, whole_b, weights, m_r, m_g, m_b);
|
||||||
|
double max_b =
|
||||||
|
Maximize(box1, Direction::kBlue, box1.b0 + 1, box1.b1, &cut_b, whole_w,
|
||||||
|
whole_r, whole_g, whole_b, weights, m_r, m_g, m_b);
|
||||||
|
|
||||||
|
Direction direction;
|
||||||
|
if (max_r >= max_g && max_r >= max_b) {
|
||||||
|
direction = Direction::kRed;
|
||||||
|
if (cut_r < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (max_g >= max_r && max_g >= max_b) {
|
||||||
|
direction = Direction::kGreen;
|
||||||
|
} else {
|
||||||
|
direction = Direction::kBlue;
|
||||||
|
}
|
||||||
|
|
||||||
|
box2.r1 = box1.r1;
|
||||||
|
box2.g1 = box1.g1;
|
||||||
|
box2.b1 = box1.b1;
|
||||||
|
|
||||||
|
if (direction == Direction::kRed) {
|
||||||
|
box2.r0 = box1.r1 = cut_r;
|
||||||
|
box2.g0 = box1.g0;
|
||||||
|
box2.b0 = box1.b0;
|
||||||
|
} else if (direction == Direction::kGreen) {
|
||||||
|
box2.r0 = box1.r0;
|
||||||
|
box2.g0 = box1.g1 = cut_g;
|
||||||
|
box2.b0 = box1.b0;
|
||||||
|
} else {
|
||||||
|
box2.r0 = box1.r0;
|
||||||
|
box2.g0 = box1.g0;
|
||||||
|
box2.b0 = box1.b1 = cut_b;
|
||||||
|
}
|
||||||
|
|
||||||
|
box1.vol = (box1.r1 - box1.r0) * (box1.g1 - box1.g0) * (box1.b1 - box1.b0);
|
||||||
|
box2.vol = (box2.r1 - box2.r0) * (box2.g1 - box2.g0) * (box2.b1 - box2.b0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Argb> QuantizeWu(const std::vector<Argb>& pixels,
|
||||||
|
uint16_t max_colors) {
|
||||||
|
if (max_colors <= 0 || max_colors > 256 || pixels.empty()) {
|
||||||
|
return std::vector<Argb>();
|
||||||
|
}
|
||||||
|
|
||||||
|
IntArray weights(kTotalSize, 0);
|
||||||
|
IntArray moments_red(kTotalSize, 0);
|
||||||
|
IntArray moments_green(kTotalSize, 0);
|
||||||
|
IntArray moments_blue(kTotalSize, 0);
|
||||||
|
DoubleArray moments(kTotalSize, 0.0);
|
||||||
|
ConstructHistogram(pixels, weights, moments_red, moments_green, moments_blue,
|
||||||
|
moments);
|
||||||
|
ComputeMoments(weights, moments_red, moments_green, moments_blue, moments);
|
||||||
|
|
||||||
|
std::vector<Box> cubes(kMaxColors);
|
||||||
|
cubes[0].r0 = cubes[0].g0 = cubes[0].b0 = 0;
|
||||||
|
cubes[0].r1 = cubes[0].g1 = cubes[0].b1 = kIndexCount - 1;
|
||||||
|
|
||||||
|
std::vector<double> volume_variance(kMaxColors);
|
||||||
|
int next = 0;
|
||||||
|
for (int i = 1; i < max_colors; ++i) {
|
||||||
|
if (Cut(cubes[next], cubes[i], weights, moments_red, moments_green,
|
||||||
|
moments_blue)) {
|
||||||
|
volume_variance[next] =
|
||||||
|
cubes[next].vol > 1 ? Variance(cubes[next], weights, moments_red,
|
||||||
|
moments_green, moments_blue, moments)
|
||||||
|
: 0.0;
|
||||||
|
volume_variance[i] = cubes[i].vol > 1
|
||||||
|
? Variance(cubes[i], weights, moments_red,
|
||||||
|
moments_green, moments_blue, moments)
|
||||||
|
: 0.0;
|
||||||
|
} else {
|
||||||
|
volume_variance[next] = 0.0;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
|
||||||
|
next = 0;
|
||||||
|
double temp = volume_variance[0];
|
||||||
|
for (int j = 1; j <= i; j++) {
|
||||||
|
if (volume_variance[j] > temp) {
|
||||||
|
temp = volume_variance[j];
|
||||||
|
next = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (temp <= 0.0) {
|
||||||
|
max_colors = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Argb> out_colors;
|
||||||
|
for (int i = 0; i < max_colors; ++i) {
|
||||||
|
int64_t weight = Vol(cubes[i], weights);
|
||||||
|
if (weight > 0) {
|
||||||
|
int32_t red = Vol(cubes[i], moments_red) / weight;
|
||||||
|
int32_t green = Vol(cubes[i], moments_green) / weight;
|
||||||
|
int32_t blue = Vol(cubes[i], moments_blue) / weight;
|
||||||
|
uint32_t argb = ArgbFromRgb(red, green, blue);
|
||||||
|
out_colors.push_back(argb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out_colors;
|
||||||
|
}
|
||||||
|
} // namespace material_color_utilities
|
31
src/material-colors/cpp/quantize/wu.h
Normal file
31
src/material-colors/cpp/quantize/wu.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_QUANTIZE_WU_H_
|
||||||
|
#define CPP_QUANTIZE_WU_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
std::vector<Argb> QuantizeWu(const std::vector<Argb>& pixels,
|
||||||
|
uint16_t max_colors);
|
||||||
|
}
|
||||||
|
#endif // CPP_QUANTIZE_WU_H_
|
116
src/material-colors/cpp/quantize/wu_test.cpp
Normal file
116
src/material-colors/cpp/quantize/wu_test.cpp
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/quantize/wu.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(WuTest, FullImage) {
|
||||||
|
std::vector<Argb> pixels(12544);
|
||||||
|
for (size_t i = 0; i < pixels.size(); i++) {
|
||||||
|
// Creates 128 distinct colors
|
||||||
|
pixels[i] = i % 8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t max_colors = 128;
|
||||||
|
|
||||||
|
QuantizeWu(pixels, max_colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WuTest, TwoRedThreeGreen) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
std::vector<Argb> result = QuantizeWu(pixels, 256);
|
||||||
|
EXPECT_EQ(result.size(), 2u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WuTest, OneRed) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
std::vector<Argb> result = QuantizeWu(pixels, 256);
|
||||||
|
EXPECT_EQ(result.size(), 1u);
|
||||||
|
EXPECT_EQ(result[0], 0xffff0000);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WuTest, OneGreen) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
std::vector<Argb> result = QuantizeWu(pixels, 256);
|
||||||
|
EXPECT_EQ(result.size(), 1u);
|
||||||
|
EXPECT_EQ(result[0], 0xff00ff00);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WuTest, OneBlue) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xff0000ff);
|
||||||
|
std::vector<Argb> result = QuantizeWu(pixels, 256);
|
||||||
|
EXPECT_EQ(result.size(), 1u);
|
||||||
|
EXPECT_EQ(result[0], 0xff0000ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WuTest, FiveBlue) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
pixels.push_back(0xff0000ff);
|
||||||
|
}
|
||||||
|
std::vector<Argb> result = QuantizeWu(pixels, 256);
|
||||||
|
EXPECT_EQ(result.size(), 1u);
|
||||||
|
EXPECT_EQ(result[0], 0xff0000ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WuTest, OneRedAndO) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xff141216);
|
||||||
|
std::vector<Argb> result = QuantizeWu(pixels, 256);
|
||||||
|
EXPECT_EQ(result.size(), 1u);
|
||||||
|
EXPECT_EQ(result[0], 0xff141216);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WuTest, RedGreenBlue) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xffff0000);
|
||||||
|
pixels.push_back(0xff00ff00);
|
||||||
|
pixels.push_back(0xff0000ff);
|
||||||
|
std::vector<Argb> result = QuantizeWu(pixels, 256);
|
||||||
|
EXPECT_EQ(result.size(), 3u);
|
||||||
|
EXPECT_EQ(result[0], 0xff0000ff);
|
||||||
|
EXPECT_EQ(result[1], 0xffff0000);
|
||||||
|
EXPECT_EQ(result[2], 0xff00ff00);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WuTest, Testonly) {
|
||||||
|
std::vector<Argb> pixels;
|
||||||
|
pixels.push_back(0xff010203);
|
||||||
|
pixels.push_back(0xff665544);
|
||||||
|
pixels.push_back(0xff708090);
|
||||||
|
pixels.push_back(0xffc0ffee);
|
||||||
|
pixels.push_back(0xfffedcba);
|
||||||
|
std::vector<Argb> result = QuantizeWu(pixels, 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
58
src/material-colors/cpp/scheme/scheme_content.cpp
Normal file
58
src/material-colors/cpp/scheme/scheme_content.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/scheme/scheme_content.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dislike/dislike.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
#include "cpp/temperature/temperature_cache.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
SchemeContent::SchemeContent(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level)
|
||||||
|
: DynamicScheme(
|
||||||
|
/*set_source_color_hct:*/ set_source_color_hct,
|
||||||
|
/*variant:*/ Variant::kContent,
|
||||||
|
/*contrast_level:*/ set_contrast_level,
|
||||||
|
/*is_dark:*/ set_is_dark,
|
||||||
|
/*primary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(),
|
||||||
|
set_source_color_hct.get_chroma()),
|
||||||
|
/*secondary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(),
|
||||||
|
fmax(set_source_color_hct.get_chroma() - 32.0,
|
||||||
|
set_source_color_hct.get_chroma() * 0.5)),
|
||||||
|
/*tertiary_palette:*/
|
||||||
|
TonalPalette(FixIfDisliked(TemperatureCache(set_source_color_hct)
|
||||||
|
.GetAnalogousColors(3, 6)
|
||||||
|
.at(2))),
|
||||||
|
/*neutral_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(),
|
||||||
|
set_source_color_hct.get_chroma() / 8.0),
|
||||||
|
/*neutral_variant_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(),
|
||||||
|
set_source_color_hct.get_chroma() / 8.0 + 4.0)) {}
|
||||||
|
|
||||||
|
SchemeContent::SchemeContent(Hct set_source_color_hct, bool set_is_dark)
|
||||||
|
: SchemeContent::SchemeContent(set_source_color_hct, set_is_dark, 0.0) {}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
33
src/material-colors/cpp/scheme/scheme_content.h
Normal file
33
src/material-colors/cpp/scheme/scheme_content.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCHEME_SCHEME_CONTENT_H_
|
||||||
|
#define CPP_SCHEME_SCHEME_CONTENT_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct SchemeContent : public DynamicScheme {
|
||||||
|
SchemeContent(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level);
|
||||||
|
SchemeContent(Hct set_source_color_hct, bool set_is_dark);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCHEME_SCHEME_CONTENT_H_
|
62
src/material-colors/cpp/scheme/scheme_expressive.cpp
Normal file
62
src/material-colors/cpp/scheme/scheme_expressive.cpp
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/scheme/scheme_expressive.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
const std::vector<double> kHues = {0, 21, 51, 121, 151, 191, 271, 321, 360};
|
||||||
|
|
||||||
|
const std::vector<double> kSecondaryRotations = {45, 95, 45, 20, 45,
|
||||||
|
90, 45, 45, 45};
|
||||||
|
|
||||||
|
const std::vector<double> kTertiaryRotations = {120, 120, 20, 45, 20,
|
||||||
|
15, 20, 120, 120};
|
||||||
|
|
||||||
|
SchemeExpressive::SchemeExpressive(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level)
|
||||||
|
: DynamicScheme(
|
||||||
|
/*set_source_color_hct:*/ set_source_color_hct,
|
||||||
|
/*variant:*/ Variant::kExpressive,
|
||||||
|
/*contrast_level:*/ set_contrast_level,
|
||||||
|
/*is_dark:*/ set_is_dark,
|
||||||
|
/*primary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue() + 240.0, 40.0),
|
||||||
|
/*secondary_palette:*/
|
||||||
|
TonalPalette(DynamicScheme::GetRotatedHue(set_source_color_hct, kHues,
|
||||||
|
kSecondaryRotations),
|
||||||
|
24.0),
|
||||||
|
/*tertiary_palette:*/
|
||||||
|
TonalPalette(DynamicScheme::GetRotatedHue(set_source_color_hct, kHues,
|
||||||
|
kTertiaryRotations),
|
||||||
|
32.0),
|
||||||
|
/*neutral_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue() + 15.0, 8.0),
|
||||||
|
/*neutral_variant_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue() + 15, 12.0)) {}
|
||||||
|
|
||||||
|
SchemeExpressive::SchemeExpressive(Hct set_source_color_hct, bool set_is_dark)
|
||||||
|
: SchemeExpressive::SchemeExpressive(set_source_color_hct, set_is_dark,
|
||||||
|
0.0) {}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
32
src/material-colors/cpp/scheme/scheme_expressive.h
Normal file
32
src/material-colors/cpp/scheme/scheme_expressive.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCHEME_SCHEME_EXPRESSIVE_H_
|
||||||
|
#define CPP_SCHEME_SCHEME_EXPRESSIVE_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct SchemeExpressive : public DynamicScheme {
|
||||||
|
SchemeExpressive(Hct source_color_hct, bool is_dark, double contrast_level);
|
||||||
|
SchemeExpressive(Hct source_color_hct, bool is_dark);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCHEME_SCHEME_EXPRESSIVE_H_
|
57
src/material-colors/cpp/scheme/scheme_fidelity.cpp
Normal file
57
src/material-colors/cpp/scheme/scheme_fidelity.cpp
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/scheme/scheme_fidelity.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dislike/dislike.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
#include "cpp/temperature/temperature_cache.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
SchemeFidelity::SchemeFidelity(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level)
|
||||||
|
: DynamicScheme(
|
||||||
|
/*set_source_color_hct:*/ set_source_color_hct,
|
||||||
|
/*variant:*/ Variant::kFidelity,
|
||||||
|
/*contrast_level:*/ set_contrast_level,
|
||||||
|
/*is_dark:*/ set_is_dark,
|
||||||
|
/*primary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(),
|
||||||
|
set_source_color_hct.get_chroma()),
|
||||||
|
/*secondary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(),
|
||||||
|
fmax(set_source_color_hct.get_chroma() - 32.0,
|
||||||
|
set_source_color_hct.get_chroma() * 0.5)),
|
||||||
|
/*tertiary_palette:*/
|
||||||
|
TonalPalette(FixIfDisliked(
|
||||||
|
TemperatureCache(set_source_color_hct).GetComplement())),
|
||||||
|
/*neutral_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(),
|
||||||
|
set_source_color_hct.get_chroma() / 8.0),
|
||||||
|
/*neutral_variant_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(),
|
||||||
|
set_source_color_hct.get_chroma() / 8.0 + 4.0)) {}
|
||||||
|
|
||||||
|
SchemeFidelity::SchemeFidelity(Hct set_source_color_hct, bool set_is_dark)
|
||||||
|
: SchemeFidelity::SchemeFidelity(set_source_color_hct, set_is_dark, 0.0) {}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
33
src/material-colors/cpp/scheme/scheme_fidelity.h
Normal file
33
src/material-colors/cpp/scheme/scheme_fidelity.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCHEME_SCHEME_FIDELITY_H_
|
||||||
|
#define CPP_SCHEME_SCHEME_FIDELITY_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct SchemeFidelity : public DynamicScheme {
|
||||||
|
SchemeFidelity(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level);
|
||||||
|
SchemeFidelity(Hct set_source_color_hct, bool set_is_dark);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCHEME_SCHEME_FIDELITY_H_
|
52
src/material-colors/cpp/scheme/scheme_fruit_salad.cpp
Normal file
52
src/material-colors/cpp/scheme/scheme_fruit_salad.cpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/scheme/scheme_fruit_salad.h"
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
SchemeFruitSalad::SchemeFruitSalad(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level)
|
||||||
|
: DynamicScheme(
|
||||||
|
/*set_source_color_hct:*/ set_source_color_hct,
|
||||||
|
/*variant:*/ Variant::kFruitSalad,
|
||||||
|
/*contrast_level:*/ set_contrast_level,
|
||||||
|
/*is_dark:*/ set_is_dark,
|
||||||
|
/*primary_palette:*/
|
||||||
|
TonalPalette(
|
||||||
|
SanitizeDegreesDouble(set_source_color_hct.get_hue() - 50.0),
|
||||||
|
48.0),
|
||||||
|
/*secondary_palette:*/
|
||||||
|
TonalPalette(
|
||||||
|
SanitizeDegreesDouble(set_source_color_hct.get_hue() - 50.0),
|
||||||
|
36.0),
|
||||||
|
/*tertiary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 36.0),
|
||||||
|
/*neutral_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 10.0),
|
||||||
|
/*neutral_variant_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 16.0)) {}
|
||||||
|
|
||||||
|
SchemeFruitSalad::SchemeFruitSalad(Hct set_source_color_hct, bool set_is_dark)
|
||||||
|
: SchemeFruitSalad::SchemeFruitSalad(set_source_color_hct, set_is_dark,
|
||||||
|
0.0) {}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
32
src/material-colors/cpp/scheme/scheme_fruit_salad.h
Normal file
32
src/material-colors/cpp/scheme/scheme_fruit_salad.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCHEME_SCHEME_FRUIT_SALAD_H_
|
||||||
|
#define CPP_SCHEME_SCHEME_FRUIT_SALAD_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct SchemeFruitSalad : public DynamicScheme {
|
||||||
|
SchemeFruitSalad(Hct source_color_hct, bool is_dark, double contrast_level);
|
||||||
|
SchemeFruitSalad(Hct source_color_hct, bool is_dark);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCHEME_SCHEME_FRUIT_SALAD_H_
|
48
src/material-colors/cpp/scheme/scheme_monochrome.cpp
Normal file
48
src/material-colors/cpp/scheme/scheme_monochrome.cpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/scheme/scheme_monochrome.h"
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
SchemeMonochrome::SchemeMonochrome(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level)
|
||||||
|
: DynamicScheme(
|
||||||
|
/*set_source_color_hct:*/ set_source_color_hct,
|
||||||
|
/*variant:*/ Variant::kMonochrome,
|
||||||
|
/*contrast_level:*/ set_contrast_level,
|
||||||
|
/*is_dark:*/ set_is_dark,
|
||||||
|
/*primary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 0.0),
|
||||||
|
/*secondary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 0.0),
|
||||||
|
/*tertiary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 0.0),
|
||||||
|
/*neutral_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 0.0),
|
||||||
|
/*neutral_variant_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 0.0)) {}
|
||||||
|
|
||||||
|
SchemeMonochrome::SchemeMonochrome(Hct set_source_color_hct, bool set_is_dark)
|
||||||
|
: SchemeMonochrome::SchemeMonochrome(set_source_color_hct, set_is_dark,
|
||||||
|
0.0) {}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
32
src/material-colors/cpp/scheme/scheme_monochrome.h
Normal file
32
src/material-colors/cpp/scheme/scheme_monochrome.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCHEME_SCHEME_MONOCHROME_H_
|
||||||
|
#define CPP_SCHEME_SCHEME_MONOCHROME_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct SchemeMonochrome : public DynamicScheme {
|
||||||
|
SchemeMonochrome(Hct source_color_hct, bool is_dark, double contrast_level);
|
||||||
|
SchemeMonochrome(Hct source_color_hct, bool is_dark);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCHEME_SCHEME_MONOCHROME_H_
|
95
src/material-colors/cpp/scheme/scheme_monochrome_test.cpp
Normal file
95
src/material-colors/cpp/scheme/scheme_monochrome_test.cpp
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/scheme/scheme_monochrome.h"
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/material_dynamic_colors.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
TEST(SchemeMonochromeTest, darkTheme_monochromeSpec) {
|
||||||
|
SchemeMonochrome scheme = SchemeMonochrome(Hct(0xff0000ff), true, 0.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::Primary().GetHct(scheme).get_tone(), 100.0,
|
||||||
|
1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::OnPrimary().GetHct(scheme).get_tone(),
|
||||||
|
10.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::PrimaryContainer().GetHct(scheme).get_tone(), 85.0,
|
||||||
|
1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::OnPrimaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
0.0, 1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::Secondary().GetHct(scheme).get_tone(),
|
||||||
|
80.0, 1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::OnSecondary().GetHct(scheme).get_tone(),
|
||||||
|
10.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::SecondaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
30.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::OnSecondaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
90.0, 1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::Tertiary().GetHct(scheme).get_tone(), 90.0,
|
||||||
|
1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::OnTertiary().GetHct(scheme).get_tone(),
|
||||||
|
10.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::TertiaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
60.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::OnTertiaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SchemeMonochromeTest, lightTheme_monochromeSpec) {
|
||||||
|
SchemeMonochrome scheme = SchemeMonochrome(Hct(0xff0000ff), false, 0.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::Primary().GetHct(scheme).get_tone(), 0.0,
|
||||||
|
1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::OnPrimary().GetHct(scheme).get_tone(),
|
||||||
|
90.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::PrimaryContainer().GetHct(scheme).get_tone(), 25.0,
|
||||||
|
1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::OnPrimaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
100.0, 1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::Secondary().GetHct(scheme).get_tone(),
|
||||||
|
40.0, 1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::OnSecondary().GetHct(scheme).get_tone(),
|
||||||
|
100.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::SecondaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
85.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::OnSecondaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
10.0, 1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::Tertiary().GetHct(scheme).get_tone(), 25.0,
|
||||||
|
1.0);
|
||||||
|
EXPECT_NEAR(MaterialDynamicColors::OnTertiary().GetHct(scheme).get_tone(),
|
||||||
|
90.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::TertiaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
49.0, 1.0);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
MaterialDynamicColors::OnTertiaryContainer().GetHct(scheme).get_tone(),
|
||||||
|
100.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
47
src/material-colors/cpp/scheme/scheme_neutral.cpp
Normal file
47
src/material-colors/cpp/scheme/scheme_neutral.cpp
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/scheme/scheme_neutral.h"
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
SchemeNeutral::SchemeNeutral(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level)
|
||||||
|
: DynamicScheme(
|
||||||
|
/*set_source_color_hct:*/ set_source_color_hct,
|
||||||
|
/*variant:*/ Variant::kNeutral,
|
||||||
|
/*contrast_level:*/ set_contrast_level,
|
||||||
|
/*is_dark:*/ set_is_dark,
|
||||||
|
/*primary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 12.0),
|
||||||
|
/*secondary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 8.0),
|
||||||
|
/*tertiary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 16.0),
|
||||||
|
/*neutral_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 2.0),
|
||||||
|
/*neutral_variant_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 2.0)) {}
|
||||||
|
|
||||||
|
SchemeNeutral::SchemeNeutral(Hct set_source_color_hct, bool set_is_dark)
|
||||||
|
: SchemeNeutral::SchemeNeutral(set_source_color_hct, set_is_dark, 0.0) {}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
32
src/material-colors/cpp/scheme/scheme_neutral.h
Normal file
32
src/material-colors/cpp/scheme/scheme_neutral.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCHEME_SCHEME_NEUTRAL_H_
|
||||||
|
#define CPP_SCHEME_SCHEME_NEUTRAL_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct SchemeNeutral : public DynamicScheme {
|
||||||
|
SchemeNeutral(Hct source_color_hct, bool is_dark, double contrast_level);
|
||||||
|
SchemeNeutral(Hct source_color_hct, bool is_dark);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCHEME_SCHEME_NEUTRAL_H_
|
49
src/material-colors/cpp/scheme/scheme_rainbow.cpp
Normal file
49
src/material-colors/cpp/scheme/scheme_rainbow.cpp
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/scheme/scheme_rainbow.h"
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
SchemeRainbow::SchemeRainbow(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level)
|
||||||
|
: DynamicScheme(
|
||||||
|
/*set_source_color_hct:*/ set_source_color_hct,
|
||||||
|
/*variant:*/ Variant::kRainbow,
|
||||||
|
/*contrast_level:*/ set_contrast_level,
|
||||||
|
/*is_dark:*/ set_is_dark,
|
||||||
|
/*primary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 48.0),
|
||||||
|
/*secondary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 16.0),
|
||||||
|
/*tertiary_palette:*/
|
||||||
|
TonalPalette(
|
||||||
|
SanitizeDegreesDouble(set_source_color_hct.get_hue() + 60.0),
|
||||||
|
24.0),
|
||||||
|
/*neutral_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 0.0),
|
||||||
|
/*neutral_variant_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 0.0)) {}
|
||||||
|
|
||||||
|
SchemeRainbow::SchemeRainbow(Hct set_source_color_hct, bool set_is_dark)
|
||||||
|
: SchemeRainbow::SchemeRainbow(set_source_color_hct, set_is_dark, 0.0) {}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
32
src/material-colors/cpp/scheme/scheme_rainbow.h
Normal file
32
src/material-colors/cpp/scheme/scheme_rainbow.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCHEME_SCHEME_RAINBOW_H_
|
||||||
|
#define CPP_SCHEME_SCHEME_RAINBOW_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct SchemeRainbow : public DynamicScheme {
|
||||||
|
SchemeRainbow(Hct source_color_hct, bool is_dark, double contrast_level);
|
||||||
|
SchemeRainbow(Hct source_color_hct, bool is_dark);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCHEME_SCHEME_RAINBOW_H_
|
49
src/material-colors/cpp/scheme/scheme_tonal_spot.cpp
Normal file
49
src/material-colors/cpp/scheme/scheme_tonal_spot.cpp
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/scheme/scheme_tonal_spot.h"
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
SchemeTonalSpot::SchemeTonalSpot(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level)
|
||||||
|
: DynamicScheme(
|
||||||
|
/*set_source_color_hct:*/ set_source_color_hct,
|
||||||
|
/*variant:*/ Variant::kTonalSpot,
|
||||||
|
/*contrast_level:*/ set_contrast_level,
|
||||||
|
/*is_dark:*/ set_is_dark,
|
||||||
|
/*primary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 36.0),
|
||||||
|
/*secondary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 16.0),
|
||||||
|
/*tertiary_palette:*/
|
||||||
|
TonalPalette(
|
||||||
|
SanitizeDegreesDouble(set_source_color_hct.get_hue() + 60), 24.0),
|
||||||
|
/*neutral_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 6.0),
|
||||||
|
/*neutral_variant_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 8.0)) {}
|
||||||
|
|
||||||
|
SchemeTonalSpot::SchemeTonalSpot(Hct set_source_color_hct, bool set_is_dark)
|
||||||
|
: SchemeTonalSpot::SchemeTonalSpot(set_source_color_hct, set_is_dark, 0.0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
32
src/material-colors/cpp/scheme/scheme_tonal_spot.h
Normal file
32
src/material-colors/cpp/scheme/scheme_tonal_spot.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCHEME_SCHEME_TONAL_SPOT_H_
|
||||||
|
#define CPP_SCHEME_SCHEME_TONAL_SPOT_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct SchemeTonalSpot : public DynamicScheme {
|
||||||
|
SchemeTonalSpot(Hct source_color_hct, bool is_dark, double contrast_level);
|
||||||
|
SchemeTonalSpot(Hct source_color_hct, bool is_dark);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCHEME_SCHEME_TONAL_SPOT_H_
|
61
src/material-colors/cpp/scheme/scheme_vibrant.cpp
Normal file
61
src/material-colors/cpp/scheme/scheme_vibrant.cpp
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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 "cpp/scheme/scheme_vibrant.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
#include "cpp/dynamiccolor/variant.h"
|
||||||
|
#include "cpp/palettes/tones.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
const std::vector<double> kHues = {0, 41, 61, 101, 131, 181, 251, 301, 360};
|
||||||
|
|
||||||
|
const std::vector<double> kSecondaryRotations = {18, 15, 10, 12, 15,
|
||||||
|
18, 15, 12, 12};
|
||||||
|
|
||||||
|
const std::vector<double> kTertiaryRotations = {35, 30, 20, 25, 30,
|
||||||
|
35, 30, 25, 25};
|
||||||
|
|
||||||
|
SchemeVibrant::SchemeVibrant(Hct set_source_color_hct, bool set_is_dark,
|
||||||
|
double set_contrast_level)
|
||||||
|
: DynamicScheme(
|
||||||
|
/*set_source_color_hct:*/ set_source_color_hct,
|
||||||
|
/*variant:*/ Variant::kVibrant,
|
||||||
|
/*contrast_level:*/ set_contrast_level,
|
||||||
|
/*is_dark:*/ set_is_dark,
|
||||||
|
/*primary_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 200.0),
|
||||||
|
/*secondary_palette:*/
|
||||||
|
TonalPalette(DynamicScheme::GetRotatedHue(set_source_color_hct, kHues,
|
||||||
|
kSecondaryRotations),
|
||||||
|
24.0),
|
||||||
|
/*tertiary_palette:*/
|
||||||
|
TonalPalette(DynamicScheme::GetRotatedHue(set_source_color_hct, kHues,
|
||||||
|
kTertiaryRotations),
|
||||||
|
32.0),
|
||||||
|
/*neutral_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 10.0),
|
||||||
|
/*neutral_variant_palette:*/
|
||||||
|
TonalPalette(set_source_color_hct.get_hue(), 12.0)) {}
|
||||||
|
|
||||||
|
SchemeVibrant::SchemeVibrant(Hct set_source_color_hct, bool set_is_dark)
|
||||||
|
: SchemeVibrant::SchemeVibrant(set_source_color_hct, set_is_dark, 0.0) {}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
32
src/material-colors/cpp/scheme/scheme_vibrant.h
Normal file
32
src/material-colors/cpp/scheme/scheme_vibrant.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCHEME_SCHEME_VARIANT_H_
|
||||||
|
#define CPP_SCHEME_SCHEME_VARIANT_H_
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/dynamiccolor/dynamic_scheme.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
struct SchemeVibrant : public DynamicScheme {
|
||||||
|
SchemeVibrant(Hct source_color_hct, bool is_dark, double contrast_level);
|
||||||
|
SchemeVibrant(Hct source_color_hct, bool is_dark);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCHEME_SCHEME_VARIANT_H_
|
124
src/material-colors/cpp/score/score.cpp
Normal file
124
src/material-colors/cpp/score/score.cpp
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/score/score.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
constexpr double kTargetChroma = 48.0; // A1 Chroma
|
||||||
|
constexpr double kWeightProportion = 0.7;
|
||||||
|
constexpr double kWeightChromaAbove = 0.3;
|
||||||
|
constexpr double kWeightChromaBelow = 0.1;
|
||||||
|
constexpr double kCutoffChroma = 5.0;
|
||||||
|
constexpr double kCutoffExcitedProportion = 0.01;
|
||||||
|
|
||||||
|
bool CompareScoredHCT(const std::pair<Hct, double>& a,
|
||||||
|
const std::pair<Hct, double>& b) {
|
||||||
|
return a.second > b.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Argb> RankedSuggestions(
|
||||||
|
const std::map<Argb, uint32_t>& argb_to_population,
|
||||||
|
const ScoreOptions& options) {
|
||||||
|
// Get the HCT color for each Argb value, while finding the per hue count and
|
||||||
|
// total count.
|
||||||
|
std::vector<Hct> colors_hct;
|
||||||
|
std::vector<uint32_t> hue_population(360, 0);
|
||||||
|
double population_sum = 0;
|
||||||
|
for (const auto& [argb, population] : argb_to_population) {
|
||||||
|
Hct hct(argb);
|
||||||
|
colors_hct.push_back(hct);
|
||||||
|
int hue = floor(hct.get_hue());
|
||||||
|
hue_population[hue] += population;
|
||||||
|
population_sum += population;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hues with more usage in neighboring 30 degree slice get a larger number.
|
||||||
|
std::vector<double> hue_excited_proportions(360, 0.0);
|
||||||
|
for (int hue = 0; hue < 360; hue++) {
|
||||||
|
double proportion = hue_population[hue] / population_sum;
|
||||||
|
for (int i = hue - 14; i < hue + 16; i++) {
|
||||||
|
int neighbor_hue = SanitizeDegreesInt(i);
|
||||||
|
hue_excited_proportions[neighbor_hue] += proportion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scores each HCT color based on usage and chroma, while optionally
|
||||||
|
// filtering out values that do not have enough chroma or usage.
|
||||||
|
std::vector<std::pair<Hct, double>> scored_hcts;
|
||||||
|
for (Hct hct : colors_hct) {
|
||||||
|
int hue = SanitizeDegreesInt(round(hct.get_hue()));
|
||||||
|
double proportion = hue_excited_proportions[hue];
|
||||||
|
if (options.filter && (hct.get_chroma() < kCutoffChroma ||
|
||||||
|
proportion <= kCutoffExcitedProportion)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double proportion_score = proportion * 100.0 * kWeightProportion;
|
||||||
|
double chroma_weight = hct.get_chroma() < kTargetChroma
|
||||||
|
? kWeightChromaBelow
|
||||||
|
: kWeightChromaAbove;
|
||||||
|
double chroma_score = (hct.get_chroma() - kTargetChroma) * chroma_weight;
|
||||||
|
double score = proportion_score + chroma_score;
|
||||||
|
scored_hcts.push_back({hct, score});
|
||||||
|
}
|
||||||
|
// Sorted so that colors with higher scores come first.
|
||||||
|
sort(scored_hcts.begin(), scored_hcts.end(), CompareScoredHCT);
|
||||||
|
|
||||||
|
// Iterates through potential hue differences in degrees in order to select
|
||||||
|
// the colors with the largest distribution of hues possible. Starting at
|
||||||
|
// 90 degrees(maximum difference for 4 colors) then decreasing down to a
|
||||||
|
// 15 degree minimum.
|
||||||
|
std::vector<Hct> chosen_colors;
|
||||||
|
for (int difference_degrees = 90; difference_degrees >= 15;
|
||||||
|
difference_degrees--) {
|
||||||
|
chosen_colors.clear();
|
||||||
|
for (auto entry : scored_hcts) {
|
||||||
|
Hct hct = entry.first;
|
||||||
|
auto duplicate_hue = std::find_if(
|
||||||
|
chosen_colors.begin(), chosen_colors.end(),
|
||||||
|
[&hct, difference_degrees](Hct chosen_hct) {
|
||||||
|
return DiffDegrees(hct.get_hue(), chosen_hct.get_hue()) <
|
||||||
|
difference_degrees;
|
||||||
|
});
|
||||||
|
if (duplicate_hue == chosen_colors.end()) {
|
||||||
|
chosen_colors.push_back(hct);
|
||||||
|
if (chosen_colors.size() >= options.desired) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chosen_colors.size() >= options.desired) break;
|
||||||
|
}
|
||||||
|
std::vector<Argb> colors;
|
||||||
|
if (chosen_colors.empty()) {
|
||||||
|
colors.push_back(options.fallback_color_argb);
|
||||||
|
}
|
||||||
|
for (auto chosen_hct : chosen_colors) {
|
||||||
|
colors.push_back(chosen_hct.ToInt());
|
||||||
|
}
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
59
src/material-colors/cpp/score/score.h
Normal file
59
src/material-colors/cpp/score/score.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_SCORE_SCORE_H_
|
||||||
|
#define CPP_SCORE_SCORE_H_
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default options for ranking colors based on usage counts.
|
||||||
|
* `desired`: is the max count of the colors returned.
|
||||||
|
* `fallback_color_argb`: Is the default color that should be used if no
|
||||||
|
* other colors are suitable.
|
||||||
|
* `filter`: controls if the resulting colors should be filtered to not include
|
||||||
|
* hues that are not used often enough, and colors that are effectively
|
||||||
|
* grayscale.
|
||||||
|
*/
|
||||||
|
struct ScoreOptions {
|
||||||
|
size_t desired = 4; // 4 colors matches the Android wallpaper picker.
|
||||||
|
int fallback_color_argb = 0xff4285f4; // Google Blue.
|
||||||
|
bool filter = true; // Avoid unsuitable colors.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a map with keys of colors and values of how often the color appears,
|
||||||
|
* rank the colors based on suitability for being used for a UI theme.
|
||||||
|
*
|
||||||
|
* The list returned is of length <= [desired]. The recommended color is the
|
||||||
|
* first item, the least suitable is the last. There will always be at least
|
||||||
|
* one color returned. If all the input colors were not suitable for a theme,
|
||||||
|
* a default fallback color will be provided, Google Blue, or supplied fallback
|
||||||
|
* color. The default number of colors returned is 4, simply because that's the
|
||||||
|
* # of colors display in Android 12's wallpaper picker.
|
||||||
|
*/
|
||||||
|
std::vector<Argb> RankedSuggestions(
|
||||||
|
const std::map<Argb, uint32_t>& argb_to_population,
|
||||||
|
const ScoreOptions& options = {});
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_SCORE_SCORE_H_
|
257
src/material-colors/cpp/score/score_test.cpp
Normal file
257
src/material-colors/cpp/score/score_test.cpp
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/score/score.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(ScoreTest, PrioritizesChroma) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xff000000, 1}, {0xffffffff, 1}, {0xff0000ff, 1}};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked =
|
||||||
|
RankedSuggestions(argb_to_population, {.desired = 4});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 1u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff0000ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, PrioritizesChromaWhenProportionsEqual) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xffff0000, 1}, {0xff00ff00, 1}, {0xff0000ff, 1}};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked =
|
||||||
|
RankedSuggestions(argb_to_population, {.desired = 4});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 3u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xffff0000);
|
||||||
|
EXPECT_EQ(ranked[1], 0xff00ff00);
|
||||||
|
EXPECT_EQ(ranked[2], 0xff0000ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratesGblueWhenNoColorsAvailable) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {{0xff000000, 1}};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked =
|
||||||
|
RankedSuggestions(argb_to_population, {.desired = 4});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 1u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, DedupesNearbyHues) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xff008772, 1}, // H 180 C 42 T 50
|
||||||
|
{0xff318477, 1} // H 184 C 35 T 50
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked =
|
||||||
|
RankedSuggestions(argb_to_population, {.desired = 4});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 1u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff008772);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, MaximizesHueDistance) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xff008772, 1}, // H 180 C 42 T 50
|
||||||
|
{0xff008587, 1}, // H 198 C 50 T 50
|
||||||
|
{0xff007ebc, 1} // H 245 C 50 T 50
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked =
|
||||||
|
RankedSuggestions(argb_to_population, {.desired = 2});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 2u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff007ebc);
|
||||||
|
EXPECT_EQ(ranked[1], 0xff008772);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioOne) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xff7ea16d, 67},
|
||||||
|
{0xffd8ccae, 67},
|
||||||
|
{0xff835c0d, 49},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 3, .fallback_color_argb = (int)0xff8d3819, .filter = false});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 3u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff7ea16d);
|
||||||
|
EXPECT_EQ(ranked[1], 0xffd8ccae);
|
||||||
|
EXPECT_EQ(ranked[2], 0xff835c0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioTwo) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xffd33881, 14},
|
||||||
|
{0xff3205cc, 77},
|
||||||
|
{0xff0b48cf, 36},
|
||||||
|
{0xffa08f5d, 81},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 4, .fallback_color_argb = (int)0xff7d772b, .filter = true});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 3u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff3205cc);
|
||||||
|
EXPECT_EQ(ranked[1], 0xffa08f5d);
|
||||||
|
EXPECT_EQ(ranked[2], 0xffd33881);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioThree) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xffbe94a6, 23},
|
||||||
|
{0xffc33fd7, 42},
|
||||||
|
{0xff899f36, 90},
|
||||||
|
{0xff94c574, 82},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 3, .fallback_color_argb = (int)0xffaa79a4, .filter = true});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 3u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff94c574);
|
||||||
|
EXPECT_EQ(ranked[1], 0xffc33fd7);
|
||||||
|
EXPECT_EQ(ranked[2], 0xffbe94a6);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioFour) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xffdf241c, 85}, {0xff685859, 44}, {0xffd06d5f, 34},
|
||||||
|
{0xff561c54, 27}, {0xff713090, 88},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 5, .fallback_color_argb = (int)0xff58c19c, .filter = false});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 2u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xffdf241c);
|
||||||
|
EXPECT_EQ(ranked[1], 0xff561c54);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioFive) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xffbe66f8, 41}, {0xff4bbda9, 88}, {0xff80f6f9, 44},
|
||||||
|
{0xffab8017, 43}, {0xffe89307, 65},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 3, .fallback_color_argb = (int)0xff916691, .filter = false});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 3u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xffab8017);
|
||||||
|
EXPECT_EQ(ranked[1], 0xff4bbda9);
|
||||||
|
EXPECT_EQ(ranked[2], 0xffbe66f8);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioSix) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xff18ea8f, 93}, {0xff327593, 18}, {0xff066a18, 53},
|
||||||
|
{0xfffa8a23, 74}, {0xff04ca1f, 62},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 2, .fallback_color_argb = (int)0xff4c377a, .filter = false});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 2u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff18ea8f);
|
||||||
|
EXPECT_EQ(ranked[1], 0xfffa8a23);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioSeven) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xff2e05ed, 23}, {0xff153e55, 90}, {0xff9ab220, 23},
|
||||||
|
{0xff153379, 66}, {0xff68bcc3, 81},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 2, .fallback_color_argb = (int)0xfff588dc, .filter = true});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 2u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff2e05ed);
|
||||||
|
EXPECT_EQ(ranked[1], 0xff9ab220);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioEight) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xff816ec5, 24},
|
||||||
|
{0xff6dcb94, 19},
|
||||||
|
{0xff3cae91, 98},
|
||||||
|
{0xff5b542f, 25},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 1, .fallback_color_argb = (int)0xff84b0fd, .filter = false});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 1u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff3cae91);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioNine) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xff206f86, 52}, {0xff4a620d, 96}, {0xfff51401, 85},
|
||||||
|
{0xff2b8ebf, 3}, {0xff277766, 59},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 3, .fallback_color_argb = (int)0xff02b415, .filter = true});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 3u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xfff51401);
|
||||||
|
EXPECT_EQ(ranked[1], 0xff4a620d);
|
||||||
|
EXPECT_EQ(ranked[2], 0xff2b8ebf);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScoreTest, GeneratedScenarioTen) {
|
||||||
|
std::map<Argb, uint32_t> argb_to_population = {
|
||||||
|
{0xff8b1d99, 54},
|
||||||
|
{0xff27effe, 43},
|
||||||
|
{0xff6f558d, 2},
|
||||||
|
{0xff77fdf2, 78},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Argb> ranked = RankedSuggestions(
|
||||||
|
argb_to_population,
|
||||||
|
{.desired = 4, .fallback_color_argb = (int)0xff5e7a10, .filter = true});
|
||||||
|
|
||||||
|
EXPECT_EQ(ranked.size(), 3u);
|
||||||
|
EXPECT_EQ(ranked[0], 0xff27effe);
|
||||||
|
EXPECT_EQ(ranked[1], 0xff8b1d99);
|
||||||
|
EXPECT_EQ(ranked[2], 0xff6f558d);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
249
src/material-colors/cpp/temperature/temperature_cache.cpp
Normal file
249
src/material-colors/cpp/temperature/temperature_cache.cpp
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/temperature/temperature_cache.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
#include "cpp/quantize/lab.h"
|
||||||
|
#include "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
TemperatureCache::TemperatureCache(Hct input) : input_(input) {}
|
||||||
|
|
||||||
|
Hct TemperatureCache::GetComplement() {
|
||||||
|
if (precomputed_complement_.has_value()) {
|
||||||
|
return precomputed_complement_.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
double coldest_hue = GetColdest().get_hue();
|
||||||
|
double coldest_temp = GetTempsByHct().at(GetColdest());
|
||||||
|
|
||||||
|
double warmest_hue = GetWarmest().get_hue();
|
||||||
|
double warmest_temp = GetTempsByHct().at(GetWarmest());
|
||||||
|
double range = warmest_temp - coldest_temp;
|
||||||
|
bool start_hue_is_coldest_to_warmest =
|
||||||
|
IsBetween(input_.get_hue(), coldest_hue, warmest_hue);
|
||||||
|
double start_hue =
|
||||||
|
start_hue_is_coldest_to_warmest ? warmest_hue : coldest_hue;
|
||||||
|
double end_hue = start_hue_is_coldest_to_warmest ? coldest_hue : warmest_hue;
|
||||||
|
double direction_of_rotation = 1.0;
|
||||||
|
double smallest_error = 1000.0;
|
||||||
|
Hct answer = GetHctsByHue().at((int)round(input_.get_hue()));
|
||||||
|
|
||||||
|
double complement_relative_temp = (1.0 - GetRelativeTemperature(input_));
|
||||||
|
// Find the color in the other section, closest to the inverse percentile
|
||||||
|
// of the input color. This is the complement.
|
||||||
|
for (double hue_addend = 0.0; hue_addend <= 360.0; hue_addend += 1.0) {
|
||||||
|
double hue =
|
||||||
|
SanitizeDegreesDouble(start_hue + direction_of_rotation * hue_addend);
|
||||||
|
if (!IsBetween(hue, start_hue, end_hue)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Hct possible_answer = GetHctsByHue().at((int)round(hue));
|
||||||
|
double relative_temp =
|
||||||
|
(GetTempsByHct().at(possible_answer) - coldest_temp) / range;
|
||||||
|
double error = abs(complement_relative_temp - relative_temp);
|
||||||
|
if (error < smallest_error) {
|
||||||
|
smallest_error = error;
|
||||||
|
answer = possible_answer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
precomputed_complement_ = answer;
|
||||||
|
return precomputed_complement_.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Hct> TemperatureCache::GetAnalogousColors() {
|
||||||
|
return GetAnalogousColors(5, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Hct> TemperatureCache::GetAnalogousColors(int count,
|
||||||
|
int divisions) {
|
||||||
|
// The starting hue is the hue of the input color.
|
||||||
|
int start_hue = (int)round(input_.get_hue());
|
||||||
|
Hct start_hct = GetHctsByHue().at(start_hue);
|
||||||
|
double last_temp = GetRelativeTemperature(start_hct);
|
||||||
|
|
||||||
|
std::vector<Hct> all_colors;
|
||||||
|
all_colors.push_back(start_hct);
|
||||||
|
|
||||||
|
double absolute_total_temp_delta = 0.0;
|
||||||
|
for (int i = 0; i < 360; i++) {
|
||||||
|
int hue = SanitizeDegreesInt(start_hue + i);
|
||||||
|
Hct hct = GetHctsByHue().at(hue);
|
||||||
|
double temp = GetRelativeTemperature(hct);
|
||||||
|
double temp_delta = abs(temp - last_temp);
|
||||||
|
last_temp = temp;
|
||||||
|
absolute_total_temp_delta += temp_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hue_addend = 1;
|
||||||
|
double temp_step = absolute_total_temp_delta / (double)divisions;
|
||||||
|
double total_temp_delta = 0.0;
|
||||||
|
last_temp = GetRelativeTemperature(start_hct);
|
||||||
|
while (all_colors.size() < static_cast<size_t>(divisions)) {
|
||||||
|
int hue = SanitizeDegreesInt(start_hue + hue_addend);
|
||||||
|
Hct hct = GetHctsByHue().at(hue);
|
||||||
|
double temp = GetRelativeTemperature(hct);
|
||||||
|
double temp_delta = abs(temp - last_temp);
|
||||||
|
total_temp_delta += temp_delta;
|
||||||
|
|
||||||
|
double desired_total_temp_delta_for_index = (all_colors.size() * temp_step);
|
||||||
|
bool index_satisfied =
|
||||||
|
total_temp_delta >= desired_total_temp_delta_for_index;
|
||||||
|
int index_addend = 1;
|
||||||
|
// Keep adding this hue to the answers until its temperature is
|
||||||
|
// insufficient. This ensures consistent behavior when there aren't
|
||||||
|
// `divisions` discrete steps between 0 and 360 in hue with `temp_step`
|
||||||
|
// delta in temperature between them.
|
||||||
|
//
|
||||||
|
// For example, white and black have no analogues: there are no other
|
||||||
|
// colors at T100/T0. Therefore, they should just be added to the array
|
||||||
|
// as answers.
|
||||||
|
while (index_satisfied &&
|
||||||
|
all_colors.size() < static_cast<size_t>(divisions)) {
|
||||||
|
all_colors.push_back(hct);
|
||||||
|
desired_total_temp_delta_for_index =
|
||||||
|
((all_colors.size() + index_addend) * temp_step);
|
||||||
|
index_satisfied = total_temp_delta >= desired_total_temp_delta_for_index;
|
||||||
|
index_addend++;
|
||||||
|
}
|
||||||
|
last_temp = temp;
|
||||||
|
hue_addend++;
|
||||||
|
|
||||||
|
if (hue_addend > 360) {
|
||||||
|
while (all_colors.size() < static_cast<size_t>(divisions)) {
|
||||||
|
all_colors.push_back(hct);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Hct> answers;
|
||||||
|
answers.push_back(input_);
|
||||||
|
|
||||||
|
int ccw_count = (int)floor(((double)count - 1.0) / 2.0);
|
||||||
|
for (int i = 1; i < (ccw_count + 1); i++) {
|
||||||
|
int index = 0 - i;
|
||||||
|
while (index < 0) {
|
||||||
|
index = all_colors.size() + index;
|
||||||
|
}
|
||||||
|
if (static_cast<size_t>(index) >= all_colors.size()) {
|
||||||
|
index = index % all_colors.size();
|
||||||
|
}
|
||||||
|
answers.insert(answers.begin(), all_colors.at(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
int cw_count = count - ccw_count - 1;
|
||||||
|
for (int i = 1; i < (cw_count + 1); i++) {
|
||||||
|
size_t index = i;
|
||||||
|
while (index < 0) {
|
||||||
|
index = all_colors.size() + index;
|
||||||
|
}
|
||||||
|
if (index >= all_colors.size()) {
|
||||||
|
index = index % all_colors.size();
|
||||||
|
}
|
||||||
|
answers.push_back(all_colors.at(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
|
||||||
|
double TemperatureCache::GetRelativeTemperature(Hct hct) {
|
||||||
|
double range =
|
||||||
|
GetTempsByHct().at(GetWarmest()) - GetTempsByHct().at(GetColdest());
|
||||||
|
double difference_from_coldest =
|
||||||
|
GetTempsByHct().at(hct) - GetTempsByHct().at(GetColdest());
|
||||||
|
// Handle when there's no difference in temperature between warmest and
|
||||||
|
// coldest: for example, at T100, only one color is available, white.
|
||||||
|
if (range == 0.) {
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
return difference_from_coldest / range;
|
||||||
|
}
|
||||||
|
|
||||||
|
double TemperatureCache::RawTemperature(Hct color) {
|
||||||
|
Lab lab = LabFromInt(color.ToInt());
|
||||||
|
double hue = SanitizeDegreesDouble(atan2(lab.b, lab.a) * 180.0 / kPi);
|
||||||
|
double chroma = hypot(lab.a, lab.b);
|
||||||
|
return -0.5 + 0.02 * pow(chroma, 1.07) *
|
||||||
|
cos(SanitizeDegreesDouble(hue - 50.) * kPi / 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hct TemperatureCache::GetColdest() { return GetHctsByTemp().at(0); }
|
||||||
|
|
||||||
|
std::vector<Hct> TemperatureCache::GetHctsByHue() {
|
||||||
|
if (precomputed_hcts_by_hue_.has_value()) {
|
||||||
|
return precomputed_hcts_by_hue_.value();
|
||||||
|
}
|
||||||
|
std::vector<Hct> hcts;
|
||||||
|
for (double hue = 0.; hue <= 360.; hue += 1.) {
|
||||||
|
Hct color_at_hue(hue, input_.get_chroma(), input_.get_tone());
|
||||||
|
hcts.push_back(color_at_hue);
|
||||||
|
}
|
||||||
|
precomputed_hcts_by_hue_ = hcts;
|
||||||
|
return precomputed_hcts_by_hue_.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Hct> TemperatureCache::GetHctsByTemp() {
|
||||||
|
if (precomputed_hcts_by_temp_.has_value()) {
|
||||||
|
return precomputed_hcts_by_temp_.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Hct> hcts(GetHctsByHue());
|
||||||
|
hcts.push_back(input_);
|
||||||
|
std::map<Hct, double> temps_by_hct(GetTempsByHct());
|
||||||
|
sort(hcts.begin(), hcts.end(),
|
||||||
|
[temps_by_hct](const Hct a, const Hct b) -> bool {
|
||||||
|
return temps_by_hct.at(a) < temps_by_hct.at(b);
|
||||||
|
});
|
||||||
|
precomputed_hcts_by_temp_ = hcts;
|
||||||
|
return precomputed_hcts_by_temp_.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<Hct, double> TemperatureCache::GetTempsByHct() {
|
||||||
|
if (precomputed_temps_by_hct_.has_value()) {
|
||||||
|
return precomputed_temps_by_hct_.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Hct> all_hcts(GetHctsByHue());
|
||||||
|
all_hcts.push_back(input_);
|
||||||
|
|
||||||
|
std::map<Hct, double> temperatures_by_hct;
|
||||||
|
for (Hct hct : all_hcts) {
|
||||||
|
temperatures_by_hct[hct] = RawTemperature(hct);
|
||||||
|
}
|
||||||
|
|
||||||
|
precomputed_temps_by_hct_ = temperatures_by_hct;
|
||||||
|
return precomputed_temps_by_hct_.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
Hct TemperatureCache::GetWarmest() {
|
||||||
|
return GetHctsByTemp().at(GetHctsByTemp().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TemperatureCache::IsBetween(double angle, double a, double b) {
|
||||||
|
if (a < b) {
|
||||||
|
return a <= angle && angle <= b;
|
||||||
|
}
|
||||||
|
return a <= angle || angle <= b;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
139
src/material-colors/cpp/temperature/temperature_cache.h
Normal file
139
src/material-colors/cpp/temperature/temperature_cache.h
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_TEMPERATURE_TEMPERATURE_CACHE_H_
|
||||||
|
#define CPP_TEMPERATURE_TEMPERATURE_CACHE_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Design utilities using color temperature theory.
|
||||||
|
*
|
||||||
|
* <p>Analogous colors, complementary color, and cache to efficiently, lazily,
|
||||||
|
* generate data for calculations when needed.
|
||||||
|
*/
|
||||||
|
class TemperatureCache {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Create a cache that allows calculation of ex. complementary and analogous
|
||||||
|
* colors.
|
||||||
|
*
|
||||||
|
* @param input Color to find complement/analogous colors of. Any colors will
|
||||||
|
* have the same tone, and chroma as the input color, modulo any restrictions
|
||||||
|
* due to the other hues having lower limits on chroma.
|
||||||
|
*/
|
||||||
|
explicit TemperatureCache(Hct input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A color that complements the input color aesthetically.
|
||||||
|
*
|
||||||
|
* <p>In art, this is usually described as being across the color wheel.
|
||||||
|
* History of this shows intent as a color that is just as cool-warm as the
|
||||||
|
* input color is warm-cool.
|
||||||
|
*/
|
||||||
|
Hct GetComplement();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 5 colors that pair well with the input color.
|
||||||
|
*
|
||||||
|
* <p>The colors are equidistant in temperature and adjacent in hue.
|
||||||
|
*/
|
||||||
|
std::vector<Hct> GetAnalogousColors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of colors with differing hues, equidistant in temperature.
|
||||||
|
*
|
||||||
|
* <p>In art, this is usually described as a set of 5 colors on a color wheel
|
||||||
|
* divided into 12 sections. This method allows provision of either of those
|
||||||
|
* values.
|
||||||
|
*
|
||||||
|
* <p>Behavior is undefined when count or divisions is 0. When divisions <
|
||||||
|
* count, colors repeat.
|
||||||
|
*
|
||||||
|
* @param count The number of colors to return, includes the input color.
|
||||||
|
* @param divisions The number of divisions on the color wheel.
|
||||||
|
*/
|
||||||
|
std::vector<Hct> GetAnalogousColors(int count, int divisions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temperature relative to all colors with the same chroma and tone.
|
||||||
|
*
|
||||||
|
* @param hct HCT to find the relative temperature of.
|
||||||
|
* @return Value on a scale from 0 to 1.
|
||||||
|
*/
|
||||||
|
double GetRelativeTemperature(Hct hct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value representing cool-warm factor of a color. Values below 0 are
|
||||||
|
* considered cool, above, warm.
|
||||||
|
*
|
||||||
|
* <p>Color science has researched emotion and harmony, which art uses to
|
||||||
|
* select colors. Warm-cool is the foundation of analogous and complementary
|
||||||
|
* colors. See: - Li-Chen Ou's Chapter 19 in Handbook of Color Psychology
|
||||||
|
* (2015). - Josef Albers' Interaction of Color chapters 19 and 21.
|
||||||
|
*
|
||||||
|
* <p>Implementation of Ou, Woodcock and Wright's algorithm, which uses
|
||||||
|
* Lab/LCH color space. Return value has these properties:<br>
|
||||||
|
* - Values below 0 are cool, above 0 are warm.<br>
|
||||||
|
* - Lower bound: -9.66. Chroma is infinite. Assuming max of Lab chroma
|
||||||
|
* 130.<br>
|
||||||
|
* - Upper bound: 8.61. Chroma is infinite. Assuming max of Lab chroma 130.
|
||||||
|
*/
|
||||||
|
static double RawTemperature(Hct color);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Hct input_;
|
||||||
|
|
||||||
|
std::optional<Hct> precomputed_complement_;
|
||||||
|
std::optional<std::vector<Hct>> precomputed_hcts_by_temp_;
|
||||||
|
std::optional<std::vector<Hct>> precomputed_hcts_by_hue_;
|
||||||
|
std::optional<std::map<Hct, double>> precomputed_temps_by_hct_;
|
||||||
|
|
||||||
|
/** Coldest color with same chroma and tone as input. */
|
||||||
|
Hct GetColdest();
|
||||||
|
|
||||||
|
/** Warmest color with same chroma and tone as input. */
|
||||||
|
Hct GetWarmest();
|
||||||
|
|
||||||
|
/** Determines if an angle is between two other angles, rotating clockwise. */
|
||||||
|
static bool IsBetween(double angle, double a, double b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HCTs for all colors with the same chroma/tone as the input.
|
||||||
|
*
|
||||||
|
* <p>Sorted by hue, ex. index 0 is hue 0.
|
||||||
|
*/
|
||||||
|
std::vector<Hct> GetHctsByHue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HCTs for all colors with the same chroma/tone as the input.
|
||||||
|
*
|
||||||
|
* <p>Sorted from coldest first to warmest last.
|
||||||
|
*/
|
||||||
|
std::vector<Hct> GetHctsByTemp();
|
||||||
|
|
||||||
|
/** Keys of HCTs in GetHctsByTemp, values of raw temperature. */
|
||||||
|
std::map<Hct, double> GetTempsByHct();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
|
||||||
|
#endif // CPP_TEMPERATURE_TEMPERATURE_CACHE_H_
|
115
src/material-colors/cpp/temperature/temperature_cache_test.cpp
Normal file
115
src/material-colors/cpp/temperature/temperature_cache_test.cpp
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/temperature/temperature_cache.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
#include "cpp/cam/hct.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(TemperatureCacheTest, RawTemperature) {
|
||||||
|
Hct blue_hct(0xff0000ff);
|
||||||
|
double blue_temp = TemperatureCache::RawTemperature(blue_hct);
|
||||||
|
EXPECT_NEAR(-1.393, blue_temp, 0.001);
|
||||||
|
|
||||||
|
Hct red_hct(0xffff0000);
|
||||||
|
double red_temp = TemperatureCache::RawTemperature(red_hct);
|
||||||
|
EXPECT_NEAR(2.351, red_temp, 0.001);
|
||||||
|
|
||||||
|
Hct green_hct(0xff00ff00);
|
||||||
|
double green_temp = TemperatureCache::RawTemperature(green_hct);
|
||||||
|
EXPECT_NEAR(-0.267, green_temp, 0.001);
|
||||||
|
|
||||||
|
Hct white_hct(0xffffffff);
|
||||||
|
double white_temp = TemperatureCache::RawTemperature(white_hct);
|
||||||
|
EXPECT_NEAR(-0.5, white_temp, 0.001);
|
||||||
|
|
||||||
|
Hct black_hct(0xff000000);
|
||||||
|
double black_temp = TemperatureCache::RawTemperature(black_hct);
|
||||||
|
EXPECT_NEAR(-0.5, black_temp, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TemperatureCacheTest, Complement) {
|
||||||
|
unsigned int blue_complement =
|
||||||
|
TemperatureCache(Hct(0xff0000ff)).GetComplement().ToInt();
|
||||||
|
EXPECT_EQ(0xff9d0002, blue_complement);
|
||||||
|
|
||||||
|
unsigned int red_complement =
|
||||||
|
TemperatureCache(Hct(0xffff0000)).GetComplement().ToInt();
|
||||||
|
EXPECT_EQ(0xff007bfc, red_complement);
|
||||||
|
|
||||||
|
unsigned int green_complement =
|
||||||
|
TemperatureCache(Hct(0xff00ff00)).GetComplement().ToInt();
|
||||||
|
EXPECT_EQ(0xffffd2c9, green_complement);
|
||||||
|
|
||||||
|
unsigned int white_complement =
|
||||||
|
TemperatureCache(Hct(0xffffffff)).GetComplement().ToInt();
|
||||||
|
EXPECT_EQ(0xffffffff, white_complement);
|
||||||
|
|
||||||
|
unsigned int black_complement =
|
||||||
|
TemperatureCache(Hct(0xff000000)).GetComplement().ToInt();
|
||||||
|
EXPECT_EQ(0xff000000, black_complement);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TemperatureCacheTest, Analogous) {
|
||||||
|
std::vector<Hct> blue_analogous =
|
||||||
|
TemperatureCache(Hct(0xff0000ff)).GetAnalogousColors();
|
||||||
|
EXPECT_EQ(0xff00590c, blue_analogous.at(0).ToInt());
|
||||||
|
EXPECT_EQ(0xff00564e, blue_analogous.at(1).ToInt());
|
||||||
|
EXPECT_EQ(0xff0000ff, blue_analogous.at(2).ToInt());
|
||||||
|
EXPECT_EQ(0xff6700cc, blue_analogous.at(3).ToInt());
|
||||||
|
EXPECT_EQ(0xff81009f, blue_analogous.at(4).ToInt());
|
||||||
|
|
||||||
|
std::vector<Hct> red_analogous =
|
||||||
|
TemperatureCache(Hct(0xffff0000)).GetAnalogousColors();
|
||||||
|
EXPECT_EQ(0xfff60082, red_analogous.at(0).ToInt());
|
||||||
|
EXPECT_EQ(0xfffc004c, red_analogous.at(1).ToInt());
|
||||||
|
EXPECT_EQ(0xffff0000, red_analogous.at(2).ToInt());
|
||||||
|
EXPECT_EQ(0xffd95500, red_analogous.at(3).ToInt());
|
||||||
|
EXPECT_EQ(0xffaf7200, red_analogous.at(4).ToInt());
|
||||||
|
|
||||||
|
std::vector<Hct> green_analogous =
|
||||||
|
TemperatureCache(Hct(0xff00ff00)).GetAnalogousColors();
|
||||||
|
EXPECT_EQ(0xffcee900, green_analogous.at(0).ToInt());
|
||||||
|
EXPECT_EQ(0xff92f500, green_analogous.at(1).ToInt());
|
||||||
|
EXPECT_EQ(0xff00ff00, green_analogous.at(2).ToInt());
|
||||||
|
EXPECT_EQ(0xff00fd6f, green_analogous.at(3).ToInt());
|
||||||
|
EXPECT_EQ(0xff00fab3, green_analogous.at(4).ToInt());
|
||||||
|
|
||||||
|
std::vector<Hct> black_analogous =
|
||||||
|
TemperatureCache(Hct(0xff000000)).GetAnalogousColors();
|
||||||
|
EXPECT_EQ(0xff000000, black_analogous.at(0).ToInt());
|
||||||
|
EXPECT_EQ(0xff000000, black_analogous.at(1).ToInt());
|
||||||
|
EXPECT_EQ(0xff000000, black_analogous.at(2).ToInt());
|
||||||
|
EXPECT_EQ(0xff000000, black_analogous.at(3).ToInt());
|
||||||
|
EXPECT_EQ(0xff000000, black_analogous.at(4).ToInt());
|
||||||
|
|
||||||
|
std::vector<Hct> white_analogous =
|
||||||
|
TemperatureCache(Hct(0xffffffff)).GetAnalogousColors();
|
||||||
|
EXPECT_EQ(0xffffffff, white_analogous.at(0).ToInt());
|
||||||
|
EXPECT_EQ(0xffffffff, white_analogous.at(1).ToInt());
|
||||||
|
EXPECT_EQ(0xffffffff, white_analogous.at(2).ToInt());
|
||||||
|
EXPECT_EQ(0xffffffff, white_analogous.at(3).ToInt());
|
||||||
|
EXPECT_EQ(0xffffffff, white_analogous.at(4).ToInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
178
src/material-colors/cpp/utils/utils.cpp
Normal file
178
src/material-colors/cpp/utils/utils.cpp
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
//#include "absl/strings/str_cat.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
int RedFromInt(const Argb argb) { return (argb & 0x00ff0000) >> 16; }
|
||||||
|
|
||||||
|
int GreenFromInt(const Argb argb) { return (argb & 0x0000ff00) >> 8; }
|
||||||
|
|
||||||
|
int BlueFromInt(const Argb argb) { return (argb & 0x000000ff); }
|
||||||
|
|
||||||
|
Argb ArgbFromRgb(const int red, const int green, const int blue) {
|
||||||
|
return 0xFF000000 | ((red & 0xff) << 16) | ((green & 0xff) << 8) |
|
||||||
|
(blue & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a color from linear RGB components to ARGB format.
|
||||||
|
Argb ArgbFromLinrgb(Vec3 linrgb) {
|
||||||
|
int r = Delinearized(linrgb.a);
|
||||||
|
int g = Delinearized(linrgb.b);
|
||||||
|
int b = Delinearized(linrgb.c);
|
||||||
|
|
||||||
|
return 0xFF000000 | ((r & 0x0ff) << 16) | ((g & 0x0ff) << 8) | (b & 0x0ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Delinearized(const double rgb_component) {
|
||||||
|
double normalized = rgb_component / 100;
|
||||||
|
double delinearized;
|
||||||
|
if (normalized <= 0.0031308) {
|
||||||
|
delinearized = normalized * 12.92;
|
||||||
|
} else {
|
||||||
|
delinearized = 1.055 * std::pow(normalized, 1.0 / 2.4) - 0.055;
|
||||||
|
}
|
||||||
|
return std::clamp((int)round(delinearized * 255.0), 0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Linearized(const int rgb_component) {
|
||||||
|
double normalized = rgb_component / 255.0;
|
||||||
|
if (normalized <= 0.040449936) {
|
||||||
|
return normalized / 12.92 * 100.0;
|
||||||
|
} else {
|
||||||
|
return std::pow((normalized + 0.055) / 1.055, 2.4) * 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int AlphaFromInt(Argb argb) { return (argb & 0xff000000) >> 24; }
|
||||||
|
|
||||||
|
bool IsOpaque(Argb argb) { return AlphaFromInt(argb) == 255; }
|
||||||
|
|
||||||
|
double LstarFromArgb(Argb argb) {
|
||||||
|
// xyz from argb
|
||||||
|
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 y = 0.2126 * red_l + 0.7152 * green_l + 0.0722 * blue_l;
|
||||||
|
return LstarFromY(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
double YFromLstar(double lstar) {
|
||||||
|
static const double ke = 8.0;
|
||||||
|
if (lstar > ke) {
|
||||||
|
double cube_root = (lstar + 16.0) / 116.0;
|
||||||
|
double cube = cube_root * cube_root * cube_root;
|
||||||
|
return cube * 100.0;
|
||||||
|
} else {
|
||||||
|
return lstar / (24389.0 / 27.0) * 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double LstarFromY(double y) {
|
||||||
|
static const double e = 216.0 / 24389.0;
|
||||||
|
double yNormalized = y / 100.0;
|
||||||
|
if (yNormalized <= e) {
|
||||||
|
return (24389.0 / 27.0) * yNormalized;
|
||||||
|
} else {
|
||||||
|
return 116.0 * std::pow(yNormalized, 1.0 / 3.0) - 16.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SanitizeDegreesInt(const int degrees) {
|
||||||
|
if (degrees < 0) {
|
||||||
|
return (degrees % 360) + 360;
|
||||||
|
} else if (degrees >= 360.0) {
|
||||||
|
return degrees % 360;
|
||||||
|
} else {
|
||||||
|
return degrees;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitizes a degree measure as a floating-point number.
|
||||||
|
//
|
||||||
|
// Returns a degree measure between 0.0 (inclusive) and 360.0 (exclusive).
|
||||||
|
double SanitizeDegreesDouble(const double degrees) {
|
||||||
|
if (degrees < 0.0) {
|
||||||
|
return fmod(degrees, 360.0) + 360;
|
||||||
|
} else if (degrees >= 360.0) {
|
||||||
|
return fmod(degrees, 360.0);
|
||||||
|
} else {
|
||||||
|
return degrees;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double DiffDegrees(const double a, const double b) {
|
||||||
|
return 180.0 - abs(abs(a - b) - 180.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double RotationDirection(const double from, const double to) {
|
||||||
|
double increasing_difference = SanitizeDegreesDouble(to - from);
|
||||||
|
return increasing_difference <= 180.0 ? 1.0 : -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a color in ARGB format to a hexadecimal string in lowercase.
|
||||||
|
//
|
||||||
|
// For instance: hex_from_argb(0xff012345) == "ff012345"
|
||||||
|
std::string HexFromArgb(Argb argb) { return std::to_string(argb); }
|
||||||
|
|
||||||
|
Argb IntFromLstar(const double lstar) {
|
||||||
|
double y = YFromLstar(lstar);
|
||||||
|
int component = Delinearized(y);
|
||||||
|
return ArgbFromRgb(component, component, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The signum function.
|
||||||
|
//
|
||||||
|
// Returns 1 if num > 0, -1 if num < 0, and 0 if num = 0
|
||||||
|
int Signum(double num) {
|
||||||
|
if (num < 0) {
|
||||||
|
return -1;
|
||||||
|
} else if (num == 0) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double Lerp(double start, double stop, double amount) {
|
||||||
|
return (1.0 - amount) * start + amount * stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 MatrixMultiply(Vec3 input, const double matrix[3][3]) {
|
||||||
|
double a =
|
||||||
|
input.a * matrix[0][0] + input.b * matrix[0][1] + input.c * matrix[0][2];
|
||||||
|
double b =
|
||||||
|
input.a * matrix[1][0] + input.b * matrix[1][1] + input.c * matrix[1][2];
|
||||||
|
double c =
|
||||||
|
input.a * matrix[2][0] + input.b * matrix[2][1] + input.c * matrix[2][2];
|
||||||
|
return (Vec3){a, b, c};
|
||||||
|
}
|
||||||
|
} // namespace material_color_utilities
|
211
src/material-colors/cpp/utils/utils.h
Normal file
211
src/material-colors/cpp/utils/utils.h
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPP_UTILS_UTILS_H_
|
||||||
|
#define CPP_UTILS_UTILS_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
using Argb = uint32_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A vector with three floating-point numbers as components.
|
||||||
|
*/
|
||||||
|
struct Vec3 {
|
||||||
|
double a = 0.0;
|
||||||
|
double b = 0.0;
|
||||||
|
double c = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of pi.
|
||||||
|
*/
|
||||||
|
inline constexpr double kPi = 3.141592653589793;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the standard white point; white on a sunny day.
|
||||||
|
*/
|
||||||
|
inline constexpr double kWhitePointD65[] = {95.047, 100.0, 108.883};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the red component of a color in ARGB format.
|
||||||
|
*/
|
||||||
|
int RedFromInt(const Argb argb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the green component of a color in ARGB format.
|
||||||
|
*/
|
||||||
|
int GreenFromInt(const Argb argb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the blue component of a color in ARGB format.
|
||||||
|
*/
|
||||||
|
int BlueFromInt(const Argb argb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the alpha component of a color in ARGB format.
|
||||||
|
*/
|
||||||
|
int AlphaFromInt(const Argb argb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a color from RGB components to ARGB format.
|
||||||
|
*/
|
||||||
|
Argb ArgbFromRgb(const int red, const int green, const int blue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a color from linear RGB components to ARGB format.
|
||||||
|
*/
|
||||||
|
Argb ArgbFromLinrgb(Vec3 linrgb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a color in ARGB format is opaque.
|
||||||
|
*/
|
||||||
|
bool IsOpaque(const Argb argb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes a degree measure as an integer.
|
||||||
|
*
|
||||||
|
* @return a degree measure between 0 (inclusive) and 360 (exclusive).
|
||||||
|
*/
|
||||||
|
int SanitizeDegreesInt(const int degrees);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes a degree measure as an floating-point number.
|
||||||
|
*
|
||||||
|
* @return a degree measure between 0.0 (inclusive) and 360.0 (exclusive).
|
||||||
|
*/
|
||||||
|
double SanitizeDegreesDouble(const double degrees);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance of two points on a circle, represented using degrees.
|
||||||
|
*/
|
||||||
|
double DiffDegrees(const double a, const double b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign of direction change needed to travel from one angle to
|
||||||
|
* another.
|
||||||
|
*
|
||||||
|
* For angles that are 180 degrees apart from each other, both
|
||||||
|
* directions have the same travel distance, so either direction is
|
||||||
|
* shortest. The value 1.0 is returned in this case.
|
||||||
|
*
|
||||||
|
* @param from The angle travel starts from, in degrees.
|
||||||
|
*
|
||||||
|
* @param to The angle travel ends at, in degrees.
|
||||||
|
*
|
||||||
|
* @return -1 if decreasing from leads to the shortest travel
|
||||||
|
* distance, 1 if increasing from leads to the shortest travel
|
||||||
|
* distance.
|
||||||
|
*/
|
||||||
|
double RotationDirection(const double from, const double to);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the L* value of a color in ARGB representation.
|
||||||
|
*
|
||||||
|
* @param argb ARGB representation of a color
|
||||||
|
*
|
||||||
|
* @return L*, from L*a*b*, coordinate of the color
|
||||||
|
*/
|
||||||
|
double LstarFromArgb(const Argb argb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the hexadecimal representation of a color.
|
||||||
|
*/
|
||||||
|
std::string HexFromArgb(Argb argb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linearizes an RGB component.
|
||||||
|
*
|
||||||
|
* @param rgb_component 0 <= rgb_component <= 255, represents R/G/B
|
||||||
|
* channel
|
||||||
|
*
|
||||||
|
* @return 0.0 <= output <= 100.0, color channel converted to
|
||||||
|
* linear RGB space
|
||||||
|
*/
|
||||||
|
double Linearized(const int rgb_component);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delinearizes an RGB component.
|
||||||
|
*
|
||||||
|
* @param rgb_component 0.0 <= rgb_component <= 100.0, represents linear
|
||||||
|
* R/G/B channel
|
||||||
|
*
|
||||||
|
* @return 0 <= output <= 255, color channel converted to regular
|
||||||
|
* RGB space
|
||||||
|
*/
|
||||||
|
int Delinearized(const double rgb_component);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an L* value to a Y value.
|
||||||
|
*
|
||||||
|
* L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
|
||||||
|
*
|
||||||
|
* L* measures perceptual luminance, a linear scale. Y in XYZ
|
||||||
|
* measures relative luminance, a logarithmic scale.
|
||||||
|
*
|
||||||
|
* @param lstar L* in L*a*b*. 0.0 <= L* <= 100.0
|
||||||
|
*
|
||||||
|
* @return Y in XYZ. 0.0 <= Y <= 100.0
|
||||||
|
*/
|
||||||
|
double YFromLstar(const double lstar);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Y value to an L* value.
|
||||||
|
*
|
||||||
|
* L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
|
||||||
|
*
|
||||||
|
* L* measures perceptual luminance, a linear scale. Y in XYZ
|
||||||
|
* measures relative luminance, a logarithmic scale.
|
||||||
|
*
|
||||||
|
* @param y Y in XYZ. 0.0 <= Y <= 100.0
|
||||||
|
*
|
||||||
|
* @return L* in L*a*b*. 0.0 <= L* <= 100.0
|
||||||
|
*/
|
||||||
|
double LstarFromY(const double y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an L* value to an ARGB representation.
|
||||||
|
*
|
||||||
|
* @param lstar L* in L*a*b*. 0.0 <= L* <= 100.0
|
||||||
|
*
|
||||||
|
* @return ARGB representation of grayscale color with lightness matching L*
|
||||||
|
*/
|
||||||
|
Argb IntFromLstar(const double lstar);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The signum function.
|
||||||
|
*
|
||||||
|
* @return 1 if num > 0, -1 if num < 0, and 0 if num = 0
|
||||||
|
*/
|
||||||
|
int Signum(double num);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The linear interpolation function.
|
||||||
|
*
|
||||||
|
* @return start if amount = 0 and stop if amount = 1
|
||||||
|
*/
|
||||||
|
double Lerp(double start, double stop, double amount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies a 1x3 row vector with a 3x3 matrix, returning the product.
|
||||||
|
*/
|
||||||
|
Vec3 MatrixMultiply(Vec3 input, const double matrix[3][3]);
|
||||||
|
|
||||||
|
} // namespace material_color_utilities
|
||||||
|
#endif // CPP_UTILS_UTILS_H_
|
363
src/material-colors/cpp/utils/utils_test.cpp
Normal file
363
src/material-colors/cpp/utils/utils_test.cpp
Normal file
|
@ -0,0 +1,363 @@
|
||||||
|
/*
|
||||||
|
* 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 "cpp/utils/utils.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "testing/base/public/gmock.h"
|
||||||
|
#include "testing/base/public/gunit.h"
|
||||||
|
|
||||||
|
namespace material_color_utilities {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using testing::DoubleNear;
|
||||||
|
|
||||||
|
constexpr double kMatrix[3][3] = {
|
||||||
|
{1, 2, 3},
|
||||||
|
{-4, 5, -6},
|
||||||
|
{-7, -8, -9},
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(ArgbFromRgbTest, ReturnsCorrectValueForBlack) {
|
||||||
|
EXPECT_EQ(ArgbFromRgb(0, 0, 0), 0xff000000);
|
||||||
|
EXPECT_EQ(ArgbFromRgb(0, 0, 0), 4278190080);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ArgbFromRgbTest, ReturnsCorrectValueForWhite) {
|
||||||
|
EXPECT_EQ(ArgbFromRgb(255, 255, 255), 0xffffffff);
|
||||||
|
EXPECT_EQ(ArgbFromRgb(255, 255, 255), 4294967295);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ArgbFromRgbTest, ReturnsCorrectValueForRandomColor) {
|
||||||
|
EXPECT_EQ(ArgbFromRgb(50, 150, 250), 0xff3296fa);
|
||||||
|
EXPECT_EQ(ArgbFromRgb(50, 150, 250), 4281505530);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, Signum) {
|
||||||
|
EXPECT_EQ(Signum(0.001), 1);
|
||||||
|
EXPECT_EQ(Signum(3.0), 1);
|
||||||
|
EXPECT_EQ(Signum(100.0), 1);
|
||||||
|
EXPECT_EQ(Signum(-0.002), -1);
|
||||||
|
EXPECT_EQ(Signum(-4.0), -1);
|
||||||
|
EXPECT_EQ(Signum(-101.0), -1);
|
||||||
|
EXPECT_EQ(Signum(0.0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, RotationIsPositiveForCounterclockwise) {
|
||||||
|
EXPECT_EQ(RotationDirection(0.0, 30.0), 1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(0.0, 60.0), 1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(0.0, 150.0), 1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(90.0, 240.0), 1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(300.0, 30.0), 1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(270.0, 60.0), 1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(360.0 * 2, 15.0), 1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(360.0 * 3 + 15.0, -360.0 * 4 + 30.0), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, RotationIsNegativeForClockwise) {
|
||||||
|
EXPECT_EQ(RotationDirection(30.0, 0.0), -1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(60.0, 0.0), -1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(150.0, 0.0), -1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(240.0, 90.0), -1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(30.0, 300.0), -1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(60.0, 270.0), -1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(15.0, -360.0 * 2), -1.0);
|
||||||
|
EXPECT_EQ(RotationDirection(-360.0 * 4 + 270.0, 360.0 * 5 + 180.0), -1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, AngleDifference) {
|
||||||
|
EXPECT_EQ(DiffDegrees(0.0, 30.0), 30.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(0.0, 60.0), 60.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(0.0, 150.0), 150.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(90.0, 240.0), 150.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(300.0, 30.0), 90.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(270.0, 60.0), 150.0);
|
||||||
|
|
||||||
|
EXPECT_EQ(DiffDegrees(30.0, 0.0), 30.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(60.0, 0.0), 60.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(150.0, 0.0), 150.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(240.0, 90.0), 150.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(30.0, 300.0), 90.0);
|
||||||
|
EXPECT_EQ(DiffDegrees(60.0, 270.0), 150.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, AngleSanitation) {
|
||||||
|
EXPECT_EQ(SanitizeDegreesInt(30), 30);
|
||||||
|
EXPECT_EQ(SanitizeDegreesInt(240), 240);
|
||||||
|
EXPECT_EQ(SanitizeDegreesInt(360), 0);
|
||||||
|
EXPECT_EQ(SanitizeDegreesInt(-30), 330);
|
||||||
|
EXPECT_EQ(SanitizeDegreesInt(-750), 330);
|
||||||
|
EXPECT_EQ(SanitizeDegreesInt(-54321), 39);
|
||||||
|
|
||||||
|
EXPECT_THAT(SanitizeDegreesDouble(30.0), DoubleNear(30.0, 1e-4));
|
||||||
|
EXPECT_THAT(SanitizeDegreesDouble(240.0), DoubleNear(240.0, 1e-4));
|
||||||
|
EXPECT_THAT(SanitizeDegreesDouble(360.0), DoubleNear(0.0, 1e-4));
|
||||||
|
EXPECT_THAT(SanitizeDegreesDouble(-30.0), DoubleNear(330.0, 1e-4));
|
||||||
|
EXPECT_THAT(SanitizeDegreesDouble(-750.0), DoubleNear(330.0, 1e-4));
|
||||||
|
EXPECT_THAT(SanitizeDegreesDouble(-54321.0), DoubleNear(39.0, 1e-4));
|
||||||
|
EXPECT_THAT(SanitizeDegreesDouble(360.125), DoubleNear(0.125, 1e-4));
|
||||||
|
EXPECT_THAT(SanitizeDegreesDouble(-11111.11), DoubleNear(48.89, 1e-4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, MatrixMultiply) {
|
||||||
|
Vec3 vector_one = MatrixMultiply({1, 3, 5}, kMatrix);
|
||||||
|
EXPECT_THAT(vector_one.a, DoubleNear(22, 1e-4));
|
||||||
|
EXPECT_THAT(vector_one.b, DoubleNear(-19, 1e-4));
|
||||||
|
EXPECT_THAT(vector_one.c, DoubleNear(-76, 1e-4));
|
||||||
|
|
||||||
|
Vec3 vector_two = MatrixMultiply({-11.1, 22.2, -33.3}, kMatrix);
|
||||||
|
EXPECT_THAT(vector_two.a, DoubleNear(-66.6, 1e-4));
|
||||||
|
EXPECT_THAT(vector_two.b, DoubleNear(355.2, 1e-4));
|
||||||
|
EXPECT_THAT(vector_two.c, DoubleNear(199.8, 1e-4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, AlphaFromInt) {
|
||||||
|
EXPECT_EQ(AlphaFromInt(0xff123456), 0xff);
|
||||||
|
EXPECT_EQ(AlphaFromInt(0xffabcdef), 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, RedFromInt) {
|
||||||
|
EXPECT_EQ(RedFromInt(0xff123456), 0x12);
|
||||||
|
EXPECT_EQ(RedFromInt(0xffabcdef), 0xab);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, GreenFromInt) {
|
||||||
|
EXPECT_EQ(GreenFromInt(0xff123456), 0x34);
|
||||||
|
EXPECT_EQ(GreenFromInt(0xffabcdef), 0xcd);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, BlueFromInt) {
|
||||||
|
EXPECT_EQ(BlueFromInt(0xff123456), 0x56);
|
||||||
|
EXPECT_EQ(BlueFromInt(0xffabcdef), 0xef);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, Opaqueness) {
|
||||||
|
EXPECT_TRUE(IsOpaque(0xff123456));
|
||||||
|
EXPECT_FALSE(IsOpaque(0xf0123456));
|
||||||
|
EXPECT_FALSE(IsOpaque(0x00123456));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, LinearizedComponents) {
|
||||||
|
EXPECT_THAT(Linearized(0), DoubleNear(0.0, 1e-4));
|
||||||
|
EXPECT_THAT(Linearized(1), DoubleNear(0.0303527, 1e-4));
|
||||||
|
EXPECT_THAT(Linearized(2), DoubleNear(0.0607054, 1e-4));
|
||||||
|
EXPECT_THAT(Linearized(8), DoubleNear(0.242822, 1e-4));
|
||||||
|
EXPECT_THAT(Linearized(9), DoubleNear(0.273174, 1e-4));
|
||||||
|
EXPECT_THAT(Linearized(16), DoubleNear(0.518152, 1e-4));
|
||||||
|
EXPECT_THAT(Linearized(32), DoubleNear(1.44438, 1e-4));
|
||||||
|
EXPECT_THAT(Linearized(64), DoubleNear(5.12695, 1e-4));
|
||||||
|
EXPECT_THAT(Linearized(128), DoubleNear(21.5861, 1e-4));
|
||||||
|
EXPECT_THAT(Linearized(255), DoubleNear(100.0, 1e-4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, DelinearizedComponents) {
|
||||||
|
EXPECT_EQ(Delinearized(0.0), 0);
|
||||||
|
EXPECT_EQ(Delinearized(0.0303527), 1);
|
||||||
|
EXPECT_EQ(Delinearized(0.0607054), 2);
|
||||||
|
EXPECT_EQ(Delinearized(0.242822), 8);
|
||||||
|
EXPECT_EQ(Delinearized(0.273174), 9);
|
||||||
|
EXPECT_EQ(Delinearized(0.518152), 16);
|
||||||
|
EXPECT_EQ(Delinearized(1.44438), 32);
|
||||||
|
EXPECT_EQ(Delinearized(5.12695), 64);
|
||||||
|
EXPECT_EQ(Delinearized(21.5861), 128);
|
||||||
|
EXPECT_EQ(Delinearized(100.0), 255);
|
||||||
|
|
||||||
|
EXPECT_EQ(Delinearized(25.0), 137);
|
||||||
|
EXPECT_EQ(Delinearized(50.0), 188);
|
||||||
|
EXPECT_EQ(Delinearized(75.0), 225);
|
||||||
|
|
||||||
|
// Delinearized clamps out-of-range inputs.
|
||||||
|
EXPECT_EQ(Delinearized(-1.0), 0);
|
||||||
|
EXPECT_EQ(Delinearized(-10000.0), 0);
|
||||||
|
EXPECT_EQ(Delinearized(101.0), 255);
|
||||||
|
EXPECT_EQ(Delinearized(10000.0), 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, DelinearizedIsLeftInverseOfLinearized) {
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(0)), 0);
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(1)), 1);
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(2)), 2);
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(8)), 8);
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(9)), 9);
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(16)), 16);
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(32)), 32);
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(64)), 64);
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(128)), 128);
|
||||||
|
EXPECT_EQ(Delinearized(Linearized(255)), 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, ArgbFromLinrgb) {
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(ArgbFromLinrgb({25.0, 50.0, 75.0})),
|
||||||
|
0xff89bce1);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(ArgbFromLinrgb({0.03, 0.06, 0.12})),
|
||||||
|
0xff010204);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, LstarFromArgb) {
|
||||||
|
EXPECT_THAT(LstarFromArgb(0xff89bce1), DoubleNear(74.011, 1e-4));
|
||||||
|
EXPECT_THAT(LstarFromArgb(0xff010204), DoubleNear(0.529651, 1e-4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, HexFromArgb) {
|
||||||
|
EXPECT_EQ(HexFromArgb(0xff89bce1), "ff89bce1");
|
||||||
|
EXPECT_EQ(HexFromArgb(0xff010204), "ff010204");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, IntFromLstar) {
|
||||||
|
// Given an L* brightness value in [0, 100], IntFromLstar returns a greyscale
|
||||||
|
// color in ARGB format with that brightness.
|
||||||
|
// For L* outside the domain [0, 100], returns black or white.
|
||||||
|
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(0.0)), 0xff000000);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(0.25)), 0xff010101);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(0.5)), 0xff020202);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(1.0)), 0xff040404);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(2.0)), 0xff070707);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(4.0)), 0xff0e0e0e);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(8.0)), 0xff181818);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(25.0)), 0xff3b3b3b);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(50.0)), 0xff777777);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(75.0)), 0xffb9b9b9);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(99.0)), 0xfffcfcfc);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(100.0)), 0xffffffff);
|
||||||
|
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(-1.0)), 0xff000000);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(-2.0)), 0xff000000);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(-3.0)), 0xff000000);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(-9999999.0)), 0xff000000);
|
||||||
|
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(101.0)), 0xffffffff);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(111.0)), 0xffffffff);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(9999999.0)), 0xffffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, LstarArgbRoundtripProperty) {
|
||||||
|
// Confirms that L* -> ARGB -> L* preserves original value
|
||||||
|
// (taking ARGB rounding into consideration).
|
||||||
|
EXPECT_THAT(LstarFromArgb(IntFromLstar(0.0)), DoubleNear(0.0, 1.0));
|
||||||
|
EXPECT_THAT(LstarFromArgb(IntFromLstar(1.0)), DoubleNear(1.0, 1.0));
|
||||||
|
EXPECT_THAT(LstarFromArgb(IntFromLstar(2.0)), DoubleNear(2.0, 1.0));
|
||||||
|
EXPECT_THAT(LstarFromArgb(IntFromLstar(8.0)), DoubleNear(8.0, 1.0));
|
||||||
|
EXPECT_THAT(LstarFromArgb(IntFromLstar(25.0)), DoubleNear(25.0, 1.0));
|
||||||
|
EXPECT_THAT(LstarFromArgb(IntFromLstar(50.0)), DoubleNear(50.0, 1.0));
|
||||||
|
EXPECT_THAT(LstarFromArgb(IntFromLstar(75.0)), DoubleNear(75.0, 1.0));
|
||||||
|
EXPECT_THAT(LstarFromArgb(IntFromLstar(99.0)), DoubleNear(99.0, 1.0));
|
||||||
|
EXPECT_THAT(LstarFromArgb(IntFromLstar(100.0)), DoubleNear(100.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, ArgbLstarRoundtripProperty) {
|
||||||
|
// Confirms that ARGB -> L* -> ARGB preserves original value
|
||||||
|
// for greyscale colors.
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(LstarFromArgb(0xff000000))),
|
||||||
|
0xff000000);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(LstarFromArgb(0xff010101))),
|
||||||
|
0xff010101);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(LstarFromArgb(0xff020202))),
|
||||||
|
0xff020202);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(LstarFromArgb(0xff111111))),
|
||||||
|
0xff111111);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(LstarFromArgb(0xff333333))),
|
||||||
|
0xff333333);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(LstarFromArgb(0xff777777))),
|
||||||
|
0xff777777);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(LstarFromArgb(0xffbbbbbb))),
|
||||||
|
0xffbbbbbb);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(LstarFromArgb(0xfffefefe))),
|
||||||
|
0xfffefefe);
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(IntFromLstar(LstarFromArgb(0xffffffff))),
|
||||||
|
0xffffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, YFromLstar) {
|
||||||
|
EXPECT_THAT(YFromLstar(0.0), DoubleNear(0.0, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(0.1), DoubleNear(0.0110705, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(0.2), DoubleNear(0.0221411, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(0.3), DoubleNear(0.0332116, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(0.4), DoubleNear(0.0442822, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(0.5), DoubleNear(0.0553528, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(1.0), DoubleNear(0.1107056, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(2.0), DoubleNear(0.2214112, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(3.0), DoubleNear(0.3321169, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(4.0), DoubleNear(0.4428225, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(5.0), DoubleNear(0.5535282, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(8.0), DoubleNear(0.8856451, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(10.0), DoubleNear(1.1260199, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(15.0), DoubleNear(1.9085832, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(20.0), DoubleNear(2.9890524, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(25.0), DoubleNear(4.4154767, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(30.0), DoubleNear(6.2359055, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(40.0), DoubleNear(11.2509737, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(50.0), DoubleNear(18.4186518, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(60.0), DoubleNear(28.1233342, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(70.0), DoubleNear(40.7494157, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(80.0), DoubleNear(56.6812907, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(90.0), DoubleNear(76.3033539, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(95.0), DoubleNear(87.6183294, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(99.0), DoubleNear(97.4360239, 1e-5));
|
||||||
|
EXPECT_THAT(YFromLstar(100.0), DoubleNear(100.0, 1e-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, LstarFromY) {
|
||||||
|
EXPECT_THAT(LstarFromY(0.0), DoubleNear(0.0, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(0.1), DoubleNear(0.9032962, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(0.2), DoubleNear(1.8065925, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(0.3), DoubleNear(2.7098888, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(0.4), DoubleNear(3.6131851, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(0.5), DoubleNear(4.5164814, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(0.8856451), DoubleNear(8.0, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(1.0), DoubleNear(8.9914424, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(2.0), DoubleNear(15.4872443, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(3.0), DoubleNear(20.0438970, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(4.0), DoubleNear(23.6714419, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(5.0), DoubleNear(26.7347653, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(10.0), DoubleNear(37.8424304, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(15.0), DoubleNear(45.6341970, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(20.0), DoubleNear(51.8372115, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(25.0), DoubleNear(57.0754208, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(30.0), DoubleNear(61.6542222, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(40.0), DoubleNear(69.4695307, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(50.0), DoubleNear(76.0692610, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(60.0), DoubleNear(81.8381891, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(70.0), DoubleNear(86.9968642, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(80.0), DoubleNear(91.6848609, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(90.0), DoubleNear(95.9967686, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(95.0), DoubleNear(98.0335184, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(99.0), DoubleNear(99.6120372, 1e-5));
|
||||||
|
EXPECT_THAT(LstarFromY(100.0), DoubleNear(100.0, 1e-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, YLstarRoundtripProperty) {
|
||||||
|
// Confirms that Y -> L* -> Y preserves original value.
|
||||||
|
for (double y = 0.0; y <= 100.0; y += 0.1) {
|
||||||
|
double lstar = LstarFromY(y);
|
||||||
|
double reconstructedY = YFromLstar(lstar);
|
||||||
|
EXPECT_THAT(reconstructedY, DoubleNear(y, 1e-8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilsTest, LstarYRoundtripProperty) {
|
||||||
|
// Confirms that L* -> Y -> L* preserves original value.
|
||||||
|
for (double lstar = 0.0; lstar <= 100.0; lstar += 0.1) {
|
||||||
|
double y = YFromLstar(lstar);
|
||||||
|
double reconstructedLstar = LstarFromY(y);
|
||||||
|
EXPECT_THAT(reconstructedLstar, DoubleNear(lstar, 1e-8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace material_color_utilities
|
|
@ -5,6 +5,7 @@
|
||||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
|
||||||
|
#include <cpp/score/score.h>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -19,6 +20,7 @@ MprisPlayer::MprisPlayer(std::string servicename) {
|
||||||
image_path_cache = "NULL";
|
image_path_cache = "NULL";
|
||||||
PlayerProxy = sdbus::createProxy(dest, objec);
|
PlayerProxy = sdbus::createProxy(dest, objec);
|
||||||
mp2 = "org.mpris.MediaPlayer2.Player";
|
mp2 = "org.mpris.MediaPlayer2.Player";
|
||||||
|
imagecount = 0;
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +64,33 @@ void MprisPlayer::Refresh() {
|
||||||
if (current_metadata["mpris:artUrl"].get<std::string>() != image_path_cache) {
|
if (current_metadata["mpris:artUrl"].get<std::string>() != image_path_cache) {
|
||||||
image_path_cache = current_metadata["mpris:artUrl"].get<std::string>();
|
image_path_cache = current_metadata["mpris:artUrl"].get<std::string>();
|
||||||
UpdateTexture();
|
UpdateTexture();
|
||||||
|
auto img = LoadImageFromTexture(tex.tex);
|
||||||
|
|
||||||
|
std::vector <material_color_utilities::Argb> argbs;
|
||||||
|
material_color_utilities::QuantizerResult res;
|
||||||
|
if (img.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) {
|
||||||
|
auto data = (char*) img.data;
|
||||||
|
for (int i = 0; i < img.width * img.height * 4; i += 4) {
|
||||||
|
material_color_utilities::Argb color = (data[i] << 16) | (data[i + 1] << 8) | (data[i + 2]) | (data[i + 3] << 24);
|
||||||
|
argbs.push_back(color);
|
||||||
|
}
|
||||||
|
res = material_color_utilities::QuantizeCelebi(argbs, 16);
|
||||||
|
} else if (img.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) {
|
||||||
|
auto data = (char*) img.data;
|
||||||
|
for (int i = 0; i < img.width * img.height * 3; i += 3) {
|
||||||
|
material_color_utilities::Argb color = (data[i] << 16) | (data[i + 1] << 8) | (data[i + 2] | (0xFF << 24));
|
||||||
|
argbs.push_back(color);
|
||||||
|
}
|
||||||
|
res = material_color_utilities::QuantizeCelebi(argbs, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto colors = material_color_utilities::RankedSuggestions(res.color_to_count, material_color_utilities::ScoreOptions());
|
||||||
|
accent_colors.clear();
|
||||||
|
for (auto obj : colors) {
|
||||||
|
accent_colors.push_back((Color) {.r=(unsigned char) ((obj >> 16) & 0xFF), .g=(unsigned char)( (obj >> 8) & 0xFF ), .b=(unsigned char)((obj >> 0) & 0xFF), .a=(unsigned char)((obj>>24) & 0xFF) });
|
||||||
|
}
|
||||||
|
imagecount = imagecount + 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {}
|
} catch (const std::exception& e) {}
|
||||||
return;
|
return;
|
||||||
|
@ -177,6 +206,16 @@ void MprisPlayer::UpdateTexture() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color MprisPlayer::GetPrimaryColor() {
|
||||||
|
if (accent_colors.empty()) {
|
||||||
|
return BLACK;
|
||||||
|
} else {
|
||||||
|
std::cout << "here" << std::endl;
|
||||||
|
return accent_colors[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
DiscObject::DiscObject(float ia) {
|
DiscObject::DiscObject(float ia) {
|
||||||
pos = 0;
|
pos = 0;
|
||||||
prevouspos = 0;
|
prevouspos = 0;
|
||||||
|
@ -184,6 +223,7 @@ DiscObject::DiscObject(float ia) {
|
||||||
accel = 0;
|
accel = 0;
|
||||||
target = ia;
|
target = ia;
|
||||||
active = true;
|
active = true;
|
||||||
|
enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscObject::Activate() {
|
void DiscObject::Activate() {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <sdbus-c++/IConnection.h>
|
#include <sdbus-c++/IConnection.h>
|
||||||
#include <sdbus-c++/IProxy.h>
|
#include <sdbus-c++/IProxy.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include "cpp/quantize/celebi.h"
|
||||||
|
|
||||||
class RayTexture {
|
class RayTexture {
|
||||||
public:
|
public:
|
||||||
|
@ -30,6 +31,7 @@ public:
|
||||||
float accel;
|
float accel;
|
||||||
float target;
|
float target;
|
||||||
bool active;
|
bool active;
|
||||||
|
bool enabled;
|
||||||
DiscObject(float ia);
|
DiscObject(float ia);
|
||||||
void UpdatePos(float dtime);
|
void UpdatePos(float dtime);
|
||||||
void UpdatePos(float pos, float dtime);
|
void UpdatePos(float pos, float dtime);
|
||||||
|
@ -45,7 +47,9 @@ class MprisPlayer {
|
||||||
sdbus::InterfaceName mp2;
|
sdbus::InterfaceName mp2;
|
||||||
sdbus::MethodName playpause;
|
sdbus::MethodName playpause;
|
||||||
RayTexture tex;
|
RayTexture tex;
|
||||||
|
std::vector<Color> accent_colors;
|
||||||
std::string image_path_cache;
|
std::string image_path_cache;
|
||||||
|
long imagecount;
|
||||||
explicit MprisPlayer(std::string servicename);
|
explicit MprisPlayer(std::string servicename);
|
||||||
void PausePlay();
|
void PausePlay();
|
||||||
void Refresh();
|
void Refresh();
|
||||||
|
@ -57,6 +61,7 @@ class MprisPlayer {
|
||||||
std::string GetIdentity();
|
std::string GetIdentity();
|
||||||
std::unordered_map<std::string, sdbus::Variant> current_metadata;
|
std::unordered_map<std::string, sdbus::Variant> current_metadata;
|
||||||
std::unordered_map<std::string, sdbus::Variant> GetCurrentMetadata();
|
std::unordered_map<std::string, sdbus::Variant> GetCurrentMetadata();
|
||||||
|
Color GetPrimaryColor();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue