├── .clang-format ├── .clang-tidy ├── .gitignore ├── .vscode └── launch.json ├── CMake └── cotire.cmake ├── CMakeLists.txt ├── LICENSE ├── README.md ├── ext ├── catch.hpp └── picojson.h ├── include └── colorsystem.hpp └── test ├── CMakeLists.txt ├── TestUtilities.hpp ├── aces2065.cpp ├── cielab.cpp ├── colorsystem.cpp ├── common.hpp ├── illuminants.cpp ├── macbeth.cpp ├── main.cpp ├── matrices.cpp ├── oklab.cpp ├── screen.cpp ├── spectrum.cpp └── units.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: WebKit 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveAssignments: true 7 | AlignConsecutiveDeclarations: true 8 | AlignEscapedNewlinesLeft: true 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: true 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: false 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Custom 37 | # BreakBeforeInheritanceComma: false 38 | BreakBeforeTernaryOperators: true 39 | BreakConstructorInitializersBeforeComma: false 40 | ColumnLimit: 120 41 | CommentPragmas: '^ IWYU pragma:' 42 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 43 | ConstructorInitializerIndentWidth: 4 44 | ContinuationIndentWidth: 4 45 | Cpp11BracedListStyle: true 46 | DerivePointerAlignment: false 47 | DisableFormat: false 48 | ExperimentalAutoDetectBinPacking: false 49 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 50 | IncludeCategories: 51 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 52 | Priority: 2 53 | - Regex: '^(<|"(gtest|isl|json)/)' 54 | Priority: 3 55 | - Regex: '.*' 56 | Priority: 1 57 | IndentCaseLabels: false 58 | IndentWidth: 4 59 | IndentWrappedFunctionNames: false 60 | KeepEmptyLinesAtTheStartOfBlocks: false 61 | MacroBlockBegin: '' 62 | MacroBlockEnd: '' 63 | MaxEmptyLinesToKeep: 1 64 | NamespaceIndentation: Inner 65 | ObjCBlockIndentWidth: 4 66 | ObjCSpaceAfterProperty: true 67 | ObjCSpaceBeforeProtocolList: true 68 | PenaltyBreakBeforeFirstCallParameter: 19 69 | PenaltyBreakComment: 300 70 | PenaltyBreakFirstLessLess: 120 71 | PenaltyBreakString: 1000 72 | PenaltyExcessCharacter: 1000000 73 | PenaltyReturnTypeOnItsOwnLine: 60 74 | PointerAlignment: Right 75 | ReflowComments: true 76 | SortIncludes: true 77 | SpaceAfterCStyleCast: false 78 | SpaceBeforeAssignmentOperators: true 79 | SpaceBeforeParens: ControlStatements 80 | SpaceInEmptyParentheses: false 81 | SpacesBeforeTrailingComments: 1 82 | SpacesInAngles: false 83 | SpacesInContainerLiterals: true 84 | SpacesInCStyleCastParentheses: false 85 | SpacesInParentheses: false 86 | SpacesInSquareBrackets: false 87 | Standard: Cpp11 88 | TabWidth: 8 89 | UseTab: Never 90 | ... 91 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*,modernize-*,performance-*,readability-*' 3 | WarningsAsErrors: '' 4 | HeaderFilterRegex: '' 5 | AnalyzeTemporaryDtors: true 6 | CheckOptions: 7 | - key: google-readability-braces-around-statements.ShortStatementLines 8 | value: '1' 9 | - key: google-readability-function-size.StatementThreshold 10 | value: '800' 11 | - key: google-readability-namespace-comments.ShortNamespaceLines 12 | value: '10' 13 | - key: google-readability-namespace-comments.SpacesBeforeComments 14 | value: '2' 15 | - key: modernize-loop-convert.MaxCopySize 16 | value: '16' 17 | - key: modernize-loop-convert.MinConfidence 18 | value: reasonable 19 | - key: modernize-loop-convert.NamingStyle 20 | value: CamelCase 21 | - key: modernize-pass-by-value.IncludeStyle 22 | value: llvm 23 | - key: modernize-replace-auto-ptr.IncludeStyle 24 | value: llvm 25 | - key: modernize-use-nullptr.NullMacros 26 | value: 'NULL' 27 | ... 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | 31 | # CMake 32 | build 33 | CMakeFiles 34 | 35 | # logs 36 | *.log 37 | 38 | CMake 39 | CMakeCache.txt 40 | .vscode 41 | .vs 42 | 43 | build 44 | sample 45 | ext -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "C++ Launch (Windows)", 6 | "type": "cppvsdbg", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/build/test/Debug/colortest.exe", 9 | "args": [], 10 | "stopAtEntry": false, 11 | "cwd": "${workspaceRoot}", 12 | "environment": [], 13 | "externalConsole": false 14 | }, 15 | { 16 | "name": "C++ Attach (Windows)", 17 | "type": "cppvsdbg", 18 | "request": "attach", 19 | "processId": "${command:pickProcess}" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | project (COLORSYSTEM 4 | LANGUAGES CXX C 5 | VERSION 0.0.1) 6 | 7 | list (INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/CMake) 8 | include (GNUInstallDirs) 9 | include (cotire OPTIONAL) 10 | add_library (ColorSystem INTERFACE) 11 | target_include_directories (ColorSystem INTERFACE 12 | $ 13 | $) 14 | 15 | enable_testing () 16 | 17 | add_subdirectory (test) 18 | 19 | install (DIRECTORY include DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hajime UCHIMURA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ColorSystem 2 | HDR generation color system implementation. 3 | 4 | # Features 5 | * tristimulus 6 | * colorspace matrix 7 | * OETF/EOTF(sRGB,BT709,BT2084,S-Log2) 8 | * Yxy, Yu'v' 9 | * Delta u'v', E76, E00 10 | * Bradford adaptation 11 | * Popular Observers(1931,JuddVos,2012) 12 | * Popular gamuts(Bt.709,Bt.2020,DCI-P3,S-Gamut3/cine,AdobeRGB,ACES2065,ACEScg) 13 | * Popular illuminants(A,B,C,D50/55/60/65/75,E,F2/7/11) 14 | * Popular spectrum illuminants(D65) 15 | * whitepoint from blackbody 16 | * spectrum support 17 | * color correction solver 18 | 19 | # TODO 20 | - [ ] other OETF/EOTFs (HLG,BT1886,...) 21 | - [ ] everything primitive 22 | 23 | # contact information 24 | Hajime UCHIMURA / nikutama@gmail.com 25 | If you gain some profit with this code I always welcome your gifts or presents :) -------------------------------------------------------------------------------- /ext/picojson.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009-2010 Cybozu Labs, Inc. 3 | * Copyright 2011-2014 Kazuho Oku 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | #ifndef picojson_h 29 | #define picojson_h 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | // for isnan/isinf 45 | #if __cplusplus>=201103L 46 | # include 47 | #else 48 | extern "C" { 49 | # ifdef _MSC_VER 50 | # include 51 | # elif defined(__INTEL_COMPILER) 52 | # include 53 | # else 54 | # include 55 | # endif 56 | } 57 | #endif 58 | 59 | #ifndef PICOJSON_USE_RVALUE_REFERENCE 60 | # if (defined(__cpp_rvalue_references) && __cpp_rvalue_references >= 200610) || (defined(_MSC_VER) && _MSC_VER >= 1600) 61 | # define PICOJSON_USE_RVALUE_REFERENCE 1 62 | # else 63 | # define PICOJSON_USE_RVALUE_REFERENCE 0 64 | # endif 65 | #endif//PICOJSON_USE_RVALUE_REFERENCE 66 | 67 | 68 | // experimental support for int64_t (see README.mkdn for detail) 69 | #ifdef PICOJSON_USE_INT64 70 | # define __STDC_FORMAT_MACROS 71 | # include 72 | # include 73 | #endif 74 | 75 | // to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 76 | #ifndef PICOJSON_USE_LOCALE 77 | # define PICOJSON_USE_LOCALE 1 78 | #endif 79 | #if PICOJSON_USE_LOCALE 80 | extern "C" { 81 | # include 82 | } 83 | #endif 84 | 85 | #ifndef PICOJSON_ASSERT 86 | # define PICOJSON_ASSERT(e) do { if (! (e)) throw std::runtime_error(#e); } while (0) 87 | #endif 88 | 89 | #ifdef _MSC_VER 90 | #define SNPRINTF _snprintf_s 91 | #pragma warning(push) 92 | #pragma warning(disable : 4244) // conversion from int to char 93 | #pragma warning(disable : 4127) // conditional expression is constant 94 | #pragma warning(disable : 4702) // unreachable code 95 | #else 96 | #define SNPRINTF snprintf 97 | #endif 98 | 99 | namespace picojson { 100 | 101 | enum { 102 | null_type, 103 | boolean_type, 104 | number_type, 105 | string_type, 106 | array_type, 107 | object_type 108 | #ifdef PICOJSON_USE_INT64 109 | , int64_type 110 | #endif 111 | }; 112 | 113 | enum { 114 | INDENT_WIDTH = 2 115 | }; 116 | 117 | struct null {}; 118 | 119 | class value { 120 | public: 121 | typedef std::vector array; 122 | typedef std::map object; 123 | union _storage { 124 | bool boolean_; 125 | double number_; 126 | #ifdef PICOJSON_USE_INT64 127 | int64_t int64_; 128 | #endif 129 | std::string* string_; 130 | array* array_; 131 | object* object_; 132 | }; 133 | protected: 134 | int type_; 135 | _storage u_; 136 | public: 137 | value(); 138 | value(int type, bool); 139 | explicit value(bool b); 140 | #ifdef PICOJSON_USE_INT64 141 | explicit value(int64_t i); 142 | #endif 143 | explicit value(double n); 144 | explicit value(const std::string& s); 145 | explicit value(const array& a); 146 | explicit value(const object& o); 147 | explicit value(const char* s); 148 | value(const char* s, size_t len); 149 | ~value(); 150 | value(const value& x); 151 | value& operator=(const value& x); 152 | #if PICOJSON_USE_RVALUE_REFERENCE 153 | value(value&& x)throw(); 154 | value& operator=(value&& x)throw(); 155 | #endif 156 | void swap(value& x)throw(); 157 | template bool is() const; 158 | template const T& get() const; 159 | template T& get(); 160 | bool evaluate_as_boolean() const; 161 | const value& get(size_t idx) const; 162 | const value& get(const std::string& key) const; 163 | value& get(size_t idx); 164 | value& get(const std::string& key); 165 | 166 | bool contains(size_t idx) const; 167 | bool contains(const std::string& key) const; 168 | std::string to_str() const; 169 | template void serialize(Iter os, bool prettify = false) const; 170 | std::string serialize(bool prettify = false) const; 171 | private: 172 | template value(const T*); // intentionally defined to block implicit conversion of pointer to bool 173 | template static void _indent(Iter os, int indent); 174 | template void _serialize(Iter os, int indent) const; 175 | std::string _serialize(int indent) const; 176 | }; 177 | 178 | typedef value::array array; 179 | typedef value::object object; 180 | 181 | inline value::value() : type_(null_type) {} 182 | 183 | inline value::value(int type, bool) : type_(type) { 184 | switch (type) { 185 | #define INIT(p, v) case p##type: u_.p = v; break 186 | INIT(boolean_, false); 187 | INIT(number_, 0.0); 188 | #ifdef PICOJSON_USE_INT64 189 | INIT(int64_, 0); 190 | #endif 191 | INIT(string_, new std::string()); 192 | INIT(array_, new array()); 193 | INIT(object_, new object()); 194 | #undef INIT 195 | default: break; 196 | } 197 | } 198 | 199 | inline value::value(bool b) : type_(boolean_type) { 200 | u_.boolean_ = b; 201 | } 202 | 203 | #ifdef PICOJSON_USE_INT64 204 | inline value::value(int64_t i) : type_(int64_type) { 205 | u_.int64_ = i; 206 | } 207 | #endif 208 | 209 | inline value::value(double n) : type_(number_type) { 210 | if ( 211 | #ifdef _MSC_VER 212 | ! _finite(n) 213 | #elif __cplusplus>=201103L || !(defined(isnan) && defined(isinf)) 214 | std::isnan(n) || std::isinf(n) 215 | #else 216 | isnan(n) || isinf(n) 217 | #endif 218 | ) { 219 | throw std::overflow_error(""); 220 | } 221 | u_.number_ = n; 222 | } 223 | 224 | inline value::value(const std::string& s) : type_(string_type) { 225 | u_.string_ = new std::string(s); 226 | } 227 | 228 | inline value::value(const array& a) : type_(array_type) { 229 | u_.array_ = new array(a); 230 | } 231 | 232 | inline value::value(const object& o) : type_(object_type) { 233 | u_.object_ = new object(o); 234 | } 235 | 236 | inline value::value(const char* s) : type_(string_type) { 237 | u_.string_ = new std::string(s); 238 | } 239 | 240 | inline value::value(const char* s, size_t len) : type_(string_type) { 241 | u_.string_ = new std::string(s, len); 242 | } 243 | 244 | inline value::~value() { 245 | switch (type_) { 246 | #define DEINIT(p) case p##type: delete u_.p; break 247 | DEINIT(string_); 248 | DEINIT(array_); 249 | DEINIT(object_); 250 | #undef DEINIT 251 | default: break; 252 | } 253 | } 254 | 255 | inline value::value(const value& x) : type_(x.type_) { 256 | switch (type_) { 257 | #define INIT(p, v) case p##type: u_.p = v; break 258 | INIT(string_, new std::string(*x.u_.string_)); 259 | INIT(array_, new array(*x.u_.array_)); 260 | INIT(object_, new object(*x.u_.object_)); 261 | #undef INIT 262 | default: 263 | u_ = x.u_; 264 | break; 265 | } 266 | } 267 | 268 | inline value& value::operator=(const value& x) { 269 | if (this != &x) { 270 | value t(x); 271 | swap(t); 272 | } 273 | return *this; 274 | } 275 | 276 | #if PICOJSON_USE_RVALUE_REFERENCE 277 | inline value::value(value&& x)throw() : type_(null_type) { 278 | swap(x); 279 | } 280 | inline value& value::operator=(value&& x)throw() { 281 | swap(x); 282 | return *this; 283 | } 284 | #endif 285 | inline void value::swap(value& x)throw() { 286 | std::swap(type_, x.type_); 287 | std::swap(u_, x.u_); 288 | } 289 | 290 | #define IS(ctype, jtype) \ 291 | template <> inline bool value::is() const { \ 292 | return type_ == jtype##_type; \ 293 | } 294 | IS(null, null) 295 | IS(bool, boolean) 296 | #ifdef PICOJSON_USE_INT64 297 | IS(int64_t, int64) 298 | #endif 299 | IS(std::string, string) 300 | IS(array, array) 301 | IS(object, object) 302 | #undef IS 303 | template <> inline bool value::is() const { 304 | return type_ == number_type 305 | #ifdef PICOJSON_USE_INT64 306 | || type_ == int64_type 307 | #endif 308 | ; 309 | } 310 | 311 | #define GET(ctype, var) \ 312 | template <> inline const ctype& value::get() const { \ 313 | PICOJSON_ASSERT("type mismatch! call is() before get()" \ 314 | && is()); \ 315 | return var; \ 316 | } \ 317 | template <> inline ctype& value::get() { \ 318 | PICOJSON_ASSERT("type mismatch! call is() before get()" \ 319 | && is()); \ 320 | return var; \ 321 | } 322 | GET(bool, u_.boolean_) 323 | GET(std::string, *u_.string_) 324 | GET(array, *u_.array_) 325 | GET(object, *u_.object_) 326 | #ifdef PICOJSON_USE_INT64 327 | GET(double, (type_ == int64_type && (const_cast(this)->type_ = number_type, const_cast(this)->u_.number_ = u_.int64_), u_.number_)) 328 | GET(int64_t, u_.int64_) 329 | #else 330 | GET(double, u_.number_) 331 | #endif 332 | #undef GET 333 | 334 | inline bool value::evaluate_as_boolean() const { 335 | switch (type_) { 336 | case null_type: 337 | return false; 338 | case boolean_type: 339 | return u_.boolean_; 340 | case number_type: 341 | return u_.number_ != 0; 342 | #ifdef PICOJSON_USE_INT64 343 | case int64_type: 344 | return u_.int64_ != 0; 345 | #endif 346 | case string_type: 347 | return ! u_.string_->empty(); 348 | default: 349 | return true; 350 | } 351 | } 352 | 353 | inline const value& value::get(size_t idx) const { 354 | static value s_null; 355 | PICOJSON_ASSERT(is()); 356 | return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; 357 | } 358 | 359 | inline value& value::get(size_t idx) { 360 | static value s_null; 361 | PICOJSON_ASSERT(is()); 362 | return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; 363 | } 364 | 365 | inline const value& value::get(const std::string& key) const { 366 | static value s_null; 367 | PICOJSON_ASSERT(is()); 368 | object::const_iterator i = u_.object_->find(key); 369 | return i != u_.object_->end() ? i->second : s_null; 370 | } 371 | 372 | inline value& value::get(const std::string& key) { 373 | static value s_null; 374 | PICOJSON_ASSERT(is()); 375 | object::iterator i = u_.object_->find(key); 376 | return i != u_.object_->end() ? i->second : s_null; 377 | } 378 | 379 | inline bool value::contains(size_t idx) const { 380 | PICOJSON_ASSERT(is()); 381 | return idx < u_.array_->size(); 382 | } 383 | 384 | inline bool value::contains(const std::string& key) const { 385 | PICOJSON_ASSERT(is()); 386 | object::const_iterator i = u_.object_->find(key); 387 | return i != u_.object_->end(); 388 | } 389 | 390 | inline std::string value::to_str() const { 391 | switch (type_) { 392 | case null_type: return "null"; 393 | case boolean_type: return u_.boolean_ ? "true" : "false"; 394 | #ifdef PICOJSON_USE_INT64 395 | case int64_type: { 396 | char buf[sizeof("-9223372036854775808")]; 397 | SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); 398 | return buf; 399 | } 400 | #endif 401 | case number_type: { 402 | char buf[256]; 403 | double tmp; 404 | SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); 405 | #if PICOJSON_USE_LOCALE 406 | char *decimal_point = localeconv()->decimal_point; 407 | if (strcmp(decimal_point, ".") != 0) { 408 | size_t decimal_point_len = strlen(decimal_point); 409 | for (char *p = buf; *p != '\0'; ++p) { 410 | if (strncmp(p, decimal_point, decimal_point_len) == 0) { 411 | return std::string(buf, p) + "." + (p + decimal_point_len); 412 | } 413 | } 414 | } 415 | #endif 416 | return buf; 417 | } 418 | case string_type: return *u_.string_; 419 | case array_type: return "array"; 420 | case object_type: return "object"; 421 | default: PICOJSON_ASSERT(0); 422 | #ifdef _MSC_VER 423 | __assume(0); 424 | #endif 425 | } 426 | return std::string(); 427 | } 428 | 429 | template void copy(const std::string& s, Iter oi) { 430 | std::copy(s.begin(), s.end(), oi); 431 | } 432 | 433 | template void serialize_str(const std::string& s, Iter oi) { 434 | *oi++ = '"'; 435 | for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { 436 | switch (*i) { 437 | #define MAP(val, sym) case val: copy(sym, oi); break 438 | MAP('"', "\\\""); 439 | MAP('\\', "\\\\"); 440 | MAP('/', "\\/"); 441 | MAP('\b', "\\b"); 442 | MAP('\f', "\\f"); 443 | MAP('\n', "\\n"); 444 | MAP('\r', "\\r"); 445 | MAP('\t', "\\t"); 446 | #undef MAP 447 | default: 448 | if (static_cast(*i) < 0x20 || *i == 0x7f) { 449 | char buf[7]; 450 | SNPRINTF(buf, sizeof(buf), "\\u%04x", *i & 0xff); 451 | copy(buf, buf + 6, oi); 452 | } else { 453 | *oi++ = *i; 454 | } 455 | break; 456 | } 457 | } 458 | *oi++ = '"'; 459 | } 460 | 461 | template void value::serialize(Iter oi, bool prettify) const { 462 | return _serialize(oi, prettify ? 0 : -1); 463 | } 464 | 465 | inline std::string value::serialize(bool prettify) const { 466 | return _serialize(prettify ? 0 : -1); 467 | } 468 | 469 | template void value::_indent(Iter oi, int indent) { 470 | *oi++ = '\n'; 471 | for (int i = 0; i < indent * INDENT_WIDTH; ++i) { 472 | *oi++ = ' '; 473 | } 474 | } 475 | 476 | template void value::_serialize(Iter oi, int indent) const { 477 | switch (type_) { 478 | case string_type: 479 | serialize_str(*u_.string_, oi); 480 | break; 481 | case array_type: { 482 | *oi++ = '['; 483 | if (indent != -1) { 484 | ++indent; 485 | } 486 | for (array::const_iterator i = u_.array_->begin(); 487 | i != u_.array_->end(); 488 | ++i) { 489 | if (i != u_.array_->begin()) { 490 | *oi++ = ','; 491 | } 492 | if (indent != -1) { 493 | _indent(oi, indent); 494 | } 495 | i->_serialize(oi, indent); 496 | } 497 | if (indent != -1) { 498 | --indent; 499 | if (! u_.array_->empty()) { 500 | _indent(oi, indent); 501 | } 502 | } 503 | *oi++ = ']'; 504 | break; 505 | } 506 | case object_type: { 507 | *oi++ = '{'; 508 | if (indent != -1) { 509 | ++indent; 510 | } 511 | for (object::const_iterator i = u_.object_->begin(); 512 | i != u_.object_->end(); 513 | ++i) { 514 | if (i != u_.object_->begin()) { 515 | *oi++ = ','; 516 | } 517 | if (indent != -1) { 518 | _indent(oi, indent); 519 | } 520 | serialize_str(i->first, oi); 521 | *oi++ = ':'; 522 | if (indent != -1) { 523 | *oi++ = ' '; 524 | } 525 | i->second._serialize(oi, indent); 526 | } 527 | if (indent != -1) { 528 | --indent; 529 | if (! u_.object_->empty()) { 530 | _indent(oi, indent); 531 | } 532 | } 533 | *oi++ = '}'; 534 | break; 535 | } 536 | default: 537 | copy(to_str(), oi); 538 | break; 539 | } 540 | if (indent == 0) { 541 | *oi++ = '\n'; 542 | } 543 | } 544 | 545 | inline std::string value::_serialize(int indent) const { 546 | std::string s; 547 | _serialize(std::back_inserter(s), indent); 548 | return s; 549 | } 550 | 551 | template class input { 552 | protected: 553 | Iter cur_, end_; 554 | int last_ch_; 555 | bool ungot_; 556 | int line_; 557 | public: 558 | input(const Iter& first, const Iter& last) : cur_(first), end_(last), last_ch_(-1), ungot_(false), line_(1) {} 559 | int getc() { 560 | if (ungot_) { 561 | ungot_ = false; 562 | return last_ch_; 563 | } 564 | if (cur_ == end_) { 565 | last_ch_ = -1; 566 | return -1; 567 | } 568 | if (last_ch_ == '\n') { 569 | line_++; 570 | } 571 | last_ch_ = *cur_ & 0xff; 572 | ++cur_; 573 | return last_ch_; 574 | } 575 | void ungetc() { 576 | if (last_ch_ != -1) { 577 | PICOJSON_ASSERT(! ungot_); 578 | ungot_ = true; 579 | } 580 | } 581 | Iter cur() const { return cur_; } 582 | int line() const { return line_; } 583 | void skip_ws() { 584 | while (1) { 585 | int ch = getc(); 586 | if (! (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { 587 | ungetc(); 588 | break; 589 | } 590 | } 591 | } 592 | bool expect(int expect) { 593 | skip_ws(); 594 | if (getc() != expect) { 595 | ungetc(); 596 | return false; 597 | } 598 | return true; 599 | } 600 | bool match(const std::string& pattern) { 601 | for (std::string::const_iterator pi(pattern.begin()); 602 | pi != pattern.end(); 603 | ++pi) { 604 | if (getc() != *pi) { 605 | ungetc(); 606 | return false; 607 | } 608 | } 609 | return true; 610 | } 611 | }; 612 | 613 | template inline int _parse_quadhex(input &in) { 614 | int uni_ch = 0, hex; 615 | for (int i = 0; i < 4; i++) { 616 | if ((hex = in.getc()) == -1) { 617 | return -1; 618 | } 619 | if ('0' <= hex && hex <= '9') { 620 | hex -= '0'; 621 | } else if ('A' <= hex && hex <= 'F') { 622 | hex -= 'A' - 0xa; 623 | } else if ('a' <= hex && hex <= 'f') { 624 | hex -= 'a' - 0xa; 625 | } else { 626 | in.ungetc(); 627 | return -1; 628 | } 629 | uni_ch = uni_ch * 16 + hex; 630 | } 631 | return uni_ch; 632 | } 633 | 634 | template inline bool _parse_codepoint(String& out, input& in) { 635 | int uni_ch; 636 | if ((uni_ch = _parse_quadhex(in)) == -1) { 637 | return false; 638 | } 639 | if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { 640 | if (0xdc00 <= uni_ch) { 641 | // a second 16-bit of a surrogate pair appeared 642 | return false; 643 | } 644 | // first 16-bit of surrogate pair, get the next one 645 | if (in.getc() != '\\' || in.getc() != 'u') { 646 | in.ungetc(); 647 | return false; 648 | } 649 | int second = _parse_quadhex(in); 650 | if (! (0xdc00 <= second && second <= 0xdfff)) { 651 | return false; 652 | } 653 | uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); 654 | uni_ch += 0x10000; 655 | } 656 | if (uni_ch < 0x80) { 657 | out.push_back(uni_ch); 658 | } else { 659 | if (uni_ch < 0x800) { 660 | out.push_back(0xc0 | (uni_ch >> 6)); 661 | } else { 662 | if (uni_ch < 0x10000) { 663 | out.push_back(0xe0 | (uni_ch >> 12)); 664 | } else { 665 | out.push_back(0xf0 | (uni_ch >> 18)); 666 | out.push_back(0x80 | ((uni_ch >> 12) & 0x3f)); 667 | } 668 | out.push_back(0x80 | ((uni_ch >> 6) & 0x3f)); 669 | } 670 | out.push_back(0x80 | (uni_ch & 0x3f)); 671 | } 672 | return true; 673 | } 674 | 675 | template inline bool _parse_string(String& out, input& in) { 676 | while (1) { 677 | int ch = in.getc(); 678 | if (ch < ' ') { 679 | in.ungetc(); 680 | return false; 681 | } else if (ch == '"') { 682 | return true; 683 | } else if (ch == '\\') { 684 | if ((ch = in.getc()) == -1) { 685 | return false; 686 | } 687 | switch (ch) { 688 | #define MAP(sym, val) case sym: out.push_back(val); break 689 | MAP('"', '\"'); 690 | MAP('\\', '\\'); 691 | MAP('/', '/'); 692 | MAP('b', '\b'); 693 | MAP('f', '\f'); 694 | MAP('n', '\n'); 695 | MAP('r', '\r'); 696 | MAP('t', '\t'); 697 | #undef MAP 698 | case 'u': 699 | if (! _parse_codepoint(out, in)) { 700 | return false; 701 | } 702 | break; 703 | default: 704 | return false; 705 | } 706 | } else { 707 | out.push_back(ch); 708 | } 709 | } 710 | return false; 711 | } 712 | 713 | template inline bool _parse_array(Context& ctx, input& in) { 714 | if (! ctx.parse_array_start()) { 715 | return false; 716 | } 717 | size_t idx = 0; 718 | if (in.expect(']')) { 719 | return ctx.parse_array_stop(idx); 720 | } 721 | do { 722 | if (! ctx.parse_array_item(in, idx)) { 723 | return false; 724 | } 725 | idx++; 726 | } while (in.expect(',')); 727 | return in.expect(']') && ctx.parse_array_stop(idx); 728 | } 729 | 730 | template inline bool _parse_object(Context& ctx, input& in) { 731 | if (! ctx.parse_object_start()) { 732 | return false; 733 | } 734 | if (in.expect('}')) { 735 | return true; 736 | } 737 | do { 738 | std::string key; 739 | if (! in.expect('"') 740 | || ! _parse_string(key, in) 741 | || ! in.expect(':')) { 742 | return false; 743 | } 744 | if (! ctx.parse_object_item(in, key)) { 745 | return false; 746 | } 747 | } while (in.expect(',')); 748 | return in.expect('}'); 749 | } 750 | 751 | template inline std::string _parse_number(input& in) { 752 | std::string num_str; 753 | while (1) { 754 | int ch = in.getc(); 755 | if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' 756 | || ch == 'e' || ch == 'E') { 757 | num_str.push_back(ch); 758 | } else if (ch == '.') { 759 | #if PICOJSON_USE_LOCALE 760 | num_str += localeconv()->decimal_point; 761 | #else 762 | num_str.push_back('.'); 763 | #endif 764 | } else { 765 | in.ungetc(); 766 | break; 767 | } 768 | } 769 | return num_str; 770 | } 771 | 772 | template inline bool _parse(Context& ctx, input& in) { 773 | in.skip_ws(); 774 | int ch = in.getc(); 775 | switch (ch) { 776 | #define IS(ch, text, op) case ch: \ 777 | if (in.match(text) && op) { \ 778 | return true; \ 779 | } else { \ 780 | return false; \ 781 | } 782 | IS('n', "ull", ctx.set_null()); 783 | IS('f', "alse", ctx.set_bool(false)); 784 | IS('t', "rue", ctx.set_bool(true)); 785 | #undef IS 786 | case '"': 787 | return ctx.parse_string(in); 788 | case '[': 789 | return _parse_array(ctx, in); 790 | case '{': 791 | return _parse_object(ctx, in); 792 | default: 793 | if (('0' <= ch && ch <= '9') || ch == '-') { 794 | double f; 795 | char *endp; 796 | in.ungetc(); 797 | std::string num_str = _parse_number(in); 798 | if (num_str.empty()) { 799 | return false; 800 | } 801 | #ifdef PICOJSON_USE_INT64 802 | { 803 | errno = 0; 804 | intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); 805 | if (errno == 0 806 | && std::numeric_limits::min() <= ival 807 | && ival <= std::numeric_limits::max() 808 | && endp == num_str.c_str() + num_str.size()) { 809 | ctx.set_int64(ival); 810 | return true; 811 | } 812 | } 813 | #endif 814 | f = strtod(num_str.c_str(), &endp); 815 | if (endp == num_str.c_str() + num_str.size()) { 816 | ctx.set_number(f); 817 | return true; 818 | } 819 | return false; 820 | } 821 | break; 822 | } 823 | in.ungetc(); 824 | return false; 825 | } 826 | 827 | class deny_parse_context { 828 | public: 829 | bool set_null() { return false; } 830 | bool set_bool(bool) { return false; } 831 | #ifdef PICOJSON_USE_INT64 832 | bool set_int64(int64_t) { return false; } 833 | #endif 834 | bool set_number(double) { return false; } 835 | template bool parse_string(input&) { return false; } 836 | bool parse_array_start() { return false; } 837 | template bool parse_array_item(input&, size_t) { 838 | return false; 839 | } 840 | bool parse_array_stop(size_t) { return false; } 841 | bool parse_object_start() { return false; } 842 | template bool parse_object_item(input&, const std::string&) { 843 | return false; 844 | } 845 | }; 846 | 847 | class default_parse_context { 848 | protected: 849 | value* out_; 850 | public: 851 | default_parse_context(value* out) : out_(out) {} 852 | bool set_null() { 853 | *out_ = value(); 854 | return true; 855 | } 856 | bool set_bool(bool b) { 857 | *out_ = value(b); 858 | return true; 859 | } 860 | #ifdef PICOJSON_USE_INT64 861 | bool set_int64(int64_t i) { 862 | *out_ = value(i); 863 | return true; 864 | } 865 | #endif 866 | bool set_number(double f) { 867 | *out_ = value(f); 868 | return true; 869 | } 870 | template bool parse_string(input& in) { 871 | *out_ = value(string_type, false); 872 | return _parse_string(out_->get(), in); 873 | } 874 | bool parse_array_start() { 875 | *out_ = value(array_type, false); 876 | return true; 877 | } 878 | template bool parse_array_item(input& in, size_t) { 879 | array& a = out_->get(); 880 | a.push_back(value()); 881 | default_parse_context ctx(&a.back()); 882 | return _parse(ctx, in); 883 | } 884 | bool parse_array_stop(size_t) { return true; } 885 | bool parse_object_start() { 886 | *out_ = value(object_type, false); 887 | return true; 888 | } 889 | template bool parse_object_item(input& in, const std::string& key) { 890 | object& o = out_->get(); 891 | default_parse_context ctx(&o[key]); 892 | return _parse(ctx, in); 893 | } 894 | private: 895 | default_parse_context(const default_parse_context&); 896 | default_parse_context& operator=(const default_parse_context&); 897 | }; 898 | 899 | class null_parse_context { 900 | public: 901 | struct dummy_str { 902 | void push_back(int) {} 903 | }; 904 | public: 905 | null_parse_context() {} 906 | bool set_null() { return true; } 907 | bool set_bool(bool) { return true; } 908 | #ifdef PICOJSON_USE_INT64 909 | bool set_int64(int64_t) { return true; } 910 | #endif 911 | bool set_number(double) { return true; } 912 | template bool parse_string(input& in) { 913 | dummy_str s; 914 | return _parse_string(s, in); 915 | } 916 | bool parse_array_start() { return true; } 917 | template bool parse_array_item(input& in, size_t) { 918 | return _parse(*this, in); 919 | } 920 | bool parse_array_stop(size_t) { return true; } 921 | bool parse_object_start() { return true; } 922 | template bool parse_object_item(input& in, const std::string&) { 923 | return _parse(*this, in); 924 | } 925 | private: 926 | null_parse_context(const null_parse_context&); 927 | null_parse_context& operator=(const null_parse_context&); 928 | }; 929 | 930 | // obsolete, use the version below 931 | template inline std::string parse(value& out, Iter& pos, const Iter& last) { 932 | std::string err; 933 | pos = parse(out, pos, last, &err); 934 | return err; 935 | } 936 | 937 | template inline Iter _parse(Context& ctx, const Iter& first, const Iter& last, std::string* err) { 938 | input in(first, last); 939 | if (! _parse(ctx, in) && err != NULL) { 940 | char buf[64]; 941 | SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); 942 | *err = buf; 943 | while (1) { 944 | int ch = in.getc(); 945 | if (ch == -1 || ch == '\n') { 946 | break; 947 | } else if (ch >= ' ') { 948 | err->push_back(ch); 949 | } 950 | } 951 | } 952 | return in.cur(); 953 | } 954 | 955 | template inline Iter parse(value& out, const Iter& first, const Iter& last, std::string* err) { 956 | default_parse_context ctx(&out); 957 | return _parse(ctx, first, last, err); 958 | } 959 | 960 | inline std::string parse(value& out, const std::string& s) { 961 | std::string err; 962 | parse(out, s.begin(), s.end(), &err); 963 | return err; 964 | } 965 | 966 | inline std::string parse(value& out, std::istream& is) { 967 | std::string err; 968 | parse(out, std::istreambuf_iterator(is.rdbuf()), 969 | std::istreambuf_iterator(), &err); 970 | return err; 971 | } 972 | 973 | template struct last_error_t { 974 | static std::string s; 975 | }; 976 | template std::string last_error_t::s; 977 | 978 | inline void set_last_error(const std::string& s) { 979 | last_error_t::s = s; 980 | } 981 | 982 | inline const std::string& get_last_error() { 983 | return last_error_t::s; 984 | } 985 | 986 | inline bool operator==(const value& x, const value& y) { 987 | if (x.is()) 988 | return y.is(); 989 | #define PICOJSON_CMP(type) \ 990 | if (x.is()) \ 991 | return y.is() && x.get() == y.get() 992 | PICOJSON_CMP(bool); 993 | PICOJSON_CMP(double); 994 | PICOJSON_CMP(std::string); 995 | PICOJSON_CMP(array); 996 | PICOJSON_CMP(object); 997 | #undef PICOJSON_CMP 998 | PICOJSON_ASSERT(0); 999 | #ifdef _MSC_VER 1000 | __assume(0); 1001 | #endif 1002 | return false; 1003 | } 1004 | 1005 | inline bool operator!=(const value& x, const value& y) { 1006 | return ! (x == y); 1007 | } 1008 | } 1009 | 1010 | #if !PICOJSON_USE_RVALUE_REFERENCE 1011 | namespace std { 1012 | template<> inline void swap(picojson::value& x, picojson::value& y) 1013 | { 1014 | x.swap(y); 1015 | } 1016 | } 1017 | #endif 1018 | 1019 | inline std::istream& operator>>(std::istream& is, picojson::value& x) 1020 | { 1021 | picojson::set_last_error(std::string()); 1022 | std::string err = picojson::parse(x, is); 1023 | if (! err.empty()) { 1024 | picojson::set_last_error(err); 1025 | is.setstate(std::ios::failbit); 1026 | } 1027 | return is; 1028 | } 1029 | 1030 | inline std::ostream& operator<<(std::ostream& os, const picojson::value& x) 1031 | { 1032 | x.serialize(std::ostream_iterator(os)); 1033 | return os; 1034 | } 1035 | #ifdef _MSC_VER 1036 | #pragma warning(pop) 1037 | #endif 1038 | 1039 | #endif 1040 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | set (HEADER_FILES common.hpp TestUtilities.hpp) 4 | 5 | set (SOURCE_FILES colorsystem.cpp 6 | spectrum.cpp 7 | matrices.cpp 8 | cielab.cpp 9 | illuminants.cpp 10 | aces2065.cpp 11 | macbeth.cpp 12 | units.cpp 13 | screen.cpp 14 | oklab.cpp 15 | ) 16 | 17 | add_library (colortest_objs OBJECT ${SOURCE_FILES} ${HEADER_FILES}) 18 | target_compile_features (colortest_objs PUBLIC cxx_std_14) 19 | target_include_directories (colortest_objs PUBLIC $ 20 | ${COLORSYSTEM_SOURCE_DIR}/ext) 21 | set_target_properties (colortest_objs PROPERTIES 22 | COTIRE_PREFIX_HEADER_INIT common.hpp 23 | COTIRE_PREFIX_HEADER_INCLUDE_PATH ${COLORSYSTEM_SOURCE_DIR}/ext) 24 | 25 | if (COMMAND cotire) 26 | cotire (colortest_objs) 27 | endif () 28 | 29 | add_executable (colortest main.cpp $) 30 | target_link_libraries (colortest PRIVATE ColorSystem) 31 | target_include_directories (colortest PRIVATE ${COLORSYSTEM_SOURCE_DIR}/ext) 32 | target_compile_features (colortest PRIVATE cxx_std_14) 33 | 34 | add_test (NAME colortest 35 | COMMAND colortest 36 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 37 | -------------------------------------------------------------------------------- /test/TestUtilities.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef TestUtilities_hpp__c71466562e3e42a28053db4f68d636b9 3 | #define TestUtilities_hpp__c71466562e3e42a28053db4f68d636b9 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | struct putFloat_ 12 | { 13 | const float v_; 14 | explicit putFloat_(float v) : v_{v} 15 | { 16 | /* NO-OP */ 17 | } 18 | std::ostream &write(std::ostream &o) const 19 | { 20 | auto f = o.setf(std::ios::fixed, std::ios::floatfield); 21 | o.width(12); 22 | auto prec = o.precision(6); 23 | o << v_; 24 | o.precision(prec); 25 | o.setf(f, std::ios::floatfield); 26 | return o; 27 | } 28 | }; 29 | 30 | inline std::ostream &operator<<(std::ostream &o, const putFloat_ &value) 31 | { 32 | return value.write(o); 33 | } 34 | 35 | inline putFloat_ putFloat(float v) 36 | { 37 | return putFloat_{v}; 38 | } 39 | 40 | inline std::ostream &writeTo(std::ostream &o, const ColorSystem::Vector3 &v) 41 | { 42 | o << "(" << putFloat(v[0]) << " " << putFloat(v[1]) << " " << putFloat(v[2]) << ")"; 43 | return o; 44 | } 45 | 46 | inline std::ostream &operator<<(std::ostream &o, const ColorSystem::Vector3 &v) 47 | { 48 | return writeTo(o, v); 49 | } 50 | 51 | inline std::ostream &writeTo(std::ostream &o, const ColorSystem::Matrix3 &m) 52 | { 53 | o << "|" << putFloat(m[0]) << " " << putFloat(m[1]) << " " << putFloat(m[2]) << "|\n" 54 | << "|" << putFloat(m[3]) << " " << putFloat(m[4]) << " " << putFloat(m[5]) << "|\n" 55 | << "|" << putFloat(m[6]) << " " << putFloat(m[7]) << " " << putFloat(m[8]) << "|\n"; 56 | return o; 57 | } 58 | 59 | inline std::ostream &operator<<(std::ostream &o, const ColorSystem::Matrix3 &m) 60 | { 61 | return writeTo(o, m); 62 | } 63 | 64 | inline std::ostream &writeTo(std::ostream &o, const ColorSystem::Tristimulus &s) 65 | { 66 | return writeTo(o, s.v_); 67 | } 68 | 69 | inline std::ostream &operator<<(std::ostream &o, const ColorSystem::Tristimulus &s) 70 | { 71 | return writeTo(o, s); 72 | } 73 | 74 | namespace Catch 75 | { 76 | /// ColorSpace::Vector3 specialized string converter. 77 | template <> 78 | struct StringMaker 79 | { 80 | static std::string convert(const ColorSystem::Vector3 &v) 81 | { 82 | std::ostringstream O; 83 | writeTo(O, v); 84 | return O.str(); 85 | } 86 | }; 87 | /// ColorSpace::Matrix3 specialized string converter. 88 | template <> 89 | struct StringMaker 90 | { 91 | static std::string convert(const ColorSystem::Matrix3 &v) 92 | { 93 | std::ostringstream O; 94 | writeTo(O, v); 95 | return O.str(); 96 | } 97 | }; 98 | 99 | /// ColorSpace::Tristimulus specialized string converter. 100 | template <> 101 | struct StringMaker 102 | { 103 | static std::string convert(const ColorSystem::Tristimulus &v) 104 | { 105 | std::ostringstream O; 106 | writeTo(O, v); 107 | return O.str(); 108 | } 109 | }; 110 | } 111 | 112 | template 113 | class ApproxEquals final : public Catch::MatcherBase 114 | { 115 | const double epsilon_ = std::numeric_limits::epsilon() * 100; 116 | const T_ & expected_; 117 | 118 | public: 119 | ApproxEquals(const T_ &expected, double eps) 120 | : expected_{expected}, epsilon_{eps} 121 | { 122 | /* NO-OP */ 123 | } 124 | 125 | bool match(const T_ &value) const override 126 | { 127 | if (value.size() != expected_.size()) 128 | { 129 | return false; 130 | } 131 | for (int_fast32_t i = 0; i < value.size(); ++i) 132 | { 133 | if (value[i] != Approx(expected_[i]).margin(epsilon_)) 134 | { 135 | return false; 136 | } 137 | } 138 | return true; 139 | } 140 | 141 | std::string describe() const override 142 | { 143 | std::ostringstream O; 144 | O << "matches to\n" 145 | << expected_ << " with epsilon = " << epsilon_; 146 | return O.str(); 147 | } 148 | }; 149 | 150 | inline ApproxEquals IsApproxEquals(const ColorSystem::Matrix3 &m, double eps) 151 | { 152 | return {m, eps}; 153 | } 154 | 155 | inline ApproxEquals IsApproxEquals(const ColorSystem::Vector3 &v, double eps) 156 | { 157 | return {v, eps}; 158 | } 159 | 160 | inline ApproxEquals IsApproxEquals(const ColorSystem::Tristimulus &s, double eps) 161 | { 162 | return {s, eps}; 163 | } 164 | #endif /* TestUtilities_hpp__c71466562e3e42a28053db4f68d636b9 */ 165 | -------------------------------------------------------------------------------- /test/aces2065.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "common.hpp" 3 | 4 | #include "TestUtilities.hpp" 5 | 6 | #include 7 | 8 | TEST_CASE("ACES2065") 9 | { 10 | // http://www.oscars.org/science-technology/aces/aces-documentation 11 | const float EPS = 1e-5f; 12 | SECTION("toXYZ") 13 | { 14 | const ColorSystem::Matrix3 &ACES2065_to_XYZ = ColorSystem::ACES2065.toXYZ(); 15 | const ColorSystem::Matrix3 expected{0.9525523959f, 0.0000000000f, 0.0000936786f, 0.3439664498f, 0.7281660966f, -0.0721325464f, 0.0000000000f, 0.0000000000f, 1.0088251844f}; 16 | REQUIRE_THAT(ACES2065_to_XYZ, IsApproxEquals(expected, EPS)); 17 | } 18 | SECTION("fromXYZ") 19 | { 20 | const ColorSystem::Matrix3 &ACES2065_from_XYZ = ColorSystem::ACES2065.fromXYZ(); 21 | const ColorSystem::Matrix3 expected{1.0498110175f, 0.0000000000f, -0.0000974845f, -0.4959030231f, 1.3733130458f, 0.0982400361f, 0.0000000000f, 0.0000000000f, 0.9912520182f}; 22 | REQUIRE_THAT(ACES2065_from_XYZ, IsApproxEquals(expected, EPS)); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /test/cielab.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | #include "TestUtilities.hpp" 4 | 5 | #include 6 | 7 | TEST_CASE("CIELAB") 8 | { 9 | SECTION("toLAB") 10 | { 11 | const ColorSystem::Tristimulus XYZ1(0.4f, 0.5f, 0.6f); 12 | const ColorSystem::Tristimulus XYZ2(0.7f, 0.6f, 0.5f); 13 | const float EPS = 1e-1f; // float is low precision... maybe? 14 | auto const & LAB1 = XYZ1.toCIELAB(); 15 | auto const & LAB2 = XYZ2.toCIELAB(); 16 | REQUIRE_THAT(LAB1, IsApproxEquals(ColorSystem::Tristimulus{76.0693f, -23.9455f, -21.1024f}, EPS)); 17 | REQUIRE_THAT(LAB2, IsApproxEquals(ColorSystem::Tristimulus{81.8382f, 27.6605f, -0.5517f}, EPS)); 18 | } 19 | SECTION("delta") 20 | { 21 | //http://qiita.com/tibigame/items/40ab217c863a20cdb264 22 | const float EPS = 1e-6f; 23 | const ColorSystem::Tristimulus LAB1(50.f, 2.6772f, -79.7751f); 24 | const ColorSystem::Tristimulus LAB2(50.f, 0.0000f, -82.7485f); 25 | const float de00 = ColorSystem::Delta::E00(LAB1, LAB2); 26 | REQUIRE(de00 == Approx(2.0424596801565738f).margin(EPS)); 27 | } 28 | SECTION("delta2") 29 | { 30 | const float EPS = 1e-6f; 31 | const float de00 = ColorSystem::Delta::E00( 32 | ColorSystem::Tristimulus(50.f, 3.1571f, -77.2803f), 33 | ColorSystem::Tristimulus(50.f, 0.0000f, -82.7485f)); 34 | REQUIRE(de00 == Approx(2.8615f).margin(EPS)); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /test/colorsystem.cpp: -------------------------------------------------------------------------------- 1 | 2 | // "colorsystem" 3 | // copyright 2017 (c) Hajime UCHIMURA / @nikq 4 | // all rights reserved 5 | #include "common.hpp" 6 | 7 | #include "TestUtilities.hpp" 8 | 9 | #include 10 | 11 | namespace 12 | { 13 | const float epsilon = 0.000001f; 14 | } 15 | 16 | TEST_CASE("toXYZ", "[XYZ]") 17 | { 18 | SECTION("adobe.toXYZ") 19 | { 20 | const ColorSystem::Matrix3 expected{0.576669f, 0.185558f, 0.188229f, 0.297345f, 0.627363f, 0.075291f, 0.027031f, 0.070689f, 0.991337f}; 21 | auto const & xyz = ColorSystem::AdobeRGB.toXYZ(); 22 | REQUIRE_THAT(xyz, IsApproxEquals(expected, epsilon)); 23 | } 24 | SECTION("adobe.toXYZ (white)") 25 | { 26 | const ColorSystem::Tristimulus white(1, 1, 1); 27 | auto const & v3 = ColorSystem::AdobeRGB.toXYZ(white).vec3(); 28 | REQUIRE_THAT(v3, IsApproxEquals(ColorSystem::Vector3{0.950456f, 1.000000f, 1.089058f}, epsilon)); 29 | } 30 | } 31 | 32 | TEST_CASE("fromXYZ", "[XYZ]") 33 | { 34 | SECTION("adobe.fromXYZ") 35 | { 36 | const ColorSystem::Matrix3 expected{2.041588f, -0.565007f, -0.344731f, -0.969244f, 1.875968f, 0.041555f, 0.013444f, -0.118362f, 1.015175f}; 37 | auto const & m = ColorSystem::AdobeRGB.fromXYZ(); 38 | REQUIRE_THAT(m, IsApproxEquals(expected, epsilon)); 39 | } 40 | SECTION("adobe.fromXYZ (white)") 41 | { 42 | ColorSystem::Tristimulus white(1, 1, 1); 43 | auto const & v = ColorSystem::AdobeRGB.fromXYZ(white).vec3(); 44 | REQUIRE_THAT(v, IsApproxEquals(ColorSystem::Vector3{1.131850f, 0.948279f, 0.910257f}, epsilon)); 45 | } 46 | } 47 | 48 | TEST_CASE("BT709") 49 | { 50 | ColorSystem::Tristimulus white(1, 1, 1); 51 | 52 | auto const &monitor = ColorSystem::OTF::toScreen(ColorSystem::OTF::BT709, ColorSystem::AdobeRGB.fromXYZ(white)); 53 | 54 | // 1.062991,0.974048,0.954468 55 | REQUIRE_THAT(monitor, IsApproxEquals(ColorSystem::Tristimulus{1.0f, 0.974048f, 0.954468f}, epsilon)); 56 | } 57 | 58 | TEST_CASE("toYxy") 59 | { 60 | SECTION("Convert from X") 61 | { 62 | ColorSystem::Tristimulus X(1.f, 0.f, 0.f); 63 | auto const & X_Yxy = X.toYxy(); 64 | REQUIRE_THAT(X_Yxy, IsApproxEquals(ColorSystem::Tristimulus{0.0000f, 1.0000f, 0.0000f}, epsilon)); 65 | } 66 | SECTION("Convert from Y") 67 | { 68 | ColorSystem::Tristimulus Y(0.f, 1.f, 0.f); 69 | auto const & Y_Yxy = Y.toYxy(); 70 | REQUIRE_THAT(Y_Yxy, IsApproxEquals(ColorSystem::Tristimulus{1.0000f, 0.0000f, 1.0000f}, epsilon)); 71 | } 72 | SECTION("Convert from Z") 73 | { 74 | ColorSystem::Tristimulus Z(0.f, 0.f, 1.f); 75 | auto const & Z_Yxy = Z.toYxy(); 76 | REQUIRE_THAT(Z_Yxy, IsApproxEquals(ColorSystem::Tristimulus{0.0000f, 0.0000f, 0.0000f}, epsilon)); 77 | } 78 | SECTION("Convert from 5R") 79 | { 80 | ColorSystem::Tristimulus R(19.58f, 11.39f, 4.90f); // 5R 81 | auto const & R_Yxy = R.toYxy(); 82 | REQUIRE_THAT(R_Yxy, IsApproxEquals(ColorSystem::Tristimulus{11.39f, 0.54586f, 0.31754f}, 1e-5f)); 83 | } 84 | } 85 | 86 | TEST_CASE("fromYxy") 87 | { 88 | SECTION("Convert from D65 w") 89 | { 90 | ColorSystem::Tristimulus W(1.f, 0.3127f, 0.3290f); //D65 w 91 | auto const & W_XYZ = W.fromYxy(); 92 | 93 | REQUIRE_THAT(W_XYZ, IsApproxEquals(ColorSystem::Tristimulus{0.95046f, 1.00000f, 1.08906f}, 1e-5f)); 94 | } 95 | SECTION("Convert from 5R") 96 | { 97 | ColorSystem::Tristimulus R(11.39f, 0.54586f, 0.31754f); // 5R 98 | auto const & R_XYZ = R.fromYxy(); 99 | 100 | REQUIRE_THAT(R_XYZ, IsApproxEquals(ColorSystem::Tristimulus{19.58f, 11.39f, 4.90f}, 1e-3f)); 101 | } 102 | } 103 | 104 | TEST_CASE("fromCT") 105 | { 106 | SECTION("1931 6500K = 0.3127 0.3290, WP approx") 107 | { 108 | ColorSystem::Tristimulus W = ColorSystem::Tristimulus::fromPlanckianLocus(6504.f); 109 | auto const & W_Yxy = W.toYxy(); 110 | printf("%f,%f,%f\n", W_Yxy[0], W_Yxy[1], W_Yxy[2]); 111 | REQUIRE_THAT(W_Yxy, IsApproxEquals(ColorSystem::Tristimulus{1.00f, 0.3127f, 0.3290f}, 1e-2f)); // this approximation is not precise. 112 | } 113 | SECTION("1931 6500K = 0.3127 0.3290, our approx") 114 | { 115 | ColorSystem::Tristimulus W = ColorSystem::Tristimulus::fromCT(6504.f); 116 | auto const & W_Yxy = W.toYxy(); 117 | printf("%f,%f,%f\n", W_Yxy[0], W_Yxy[1], W_Yxy[2]); 118 | REQUIRE_THAT(W_Yxy, IsApproxEquals(ColorSystem::Tristimulus{1.00f, 0.3127f, 0.3290f}, 1e-2f)); // this approximation is not precise. 119 | } 120 | } -------------------------------------------------------------------------------- /test/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef common_hpp__3e4a26a3f2b54c5ab0cffb73751f7dfd 3 | #define common_hpp__3e4a26a3f2b54c5ab0cffb73751f7dfd 1 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #endif /* common_hpp__3e4a26a3f2b54c5ab0cffb73751f7dfd */ 15 | -------------------------------------------------------------------------------- /test/illuminants.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "common.hpp" 3 | 4 | #include "TestUtilities.hpp" 5 | 6 | #include 7 | 8 | TEST_CASE("illuminants") 9 | { 10 | const float EPS = 1e-4f; 11 | const ColorSystem::Tristimulus d65_Yxy = ColorSystem::Illuminant_D65.toYxy(); 12 | REQUIRE(d65_Yxy[1] == Approx(0.3127f).margin(EPS)); 13 | REQUIRE(d65_Yxy[2] == Approx(0.3290f).margin(EPS)); 14 | 15 | const ColorSystem::Tristimulus E_Yxy = ColorSystem::Illuminant_E.toYxy(); 16 | REQUIRE(E_Yxy[1] == Approx(1.f / 3.f).margin(EPS)); 17 | REQUIRE(E_Yxy[2] == Approx(1.f / 3.f).margin(EPS)); 18 | 19 | const ColorSystem::Tristimulus d50_Yxy = ColorSystem::Illuminant_D50.toYxy(); 20 | REQUIRE(d50_Yxy[1] == Approx(0.345703f).margin(EPS)); 21 | REQUIRE(d50_Yxy[2] == Approx(0.358539f).margin(EPS)); 22 | 23 | const ColorSystem::Tristimulus d60_Yxy = ColorSystem::Illuminant_D60.toYxy(); 24 | REQUIRE(d60_Yxy[1] == Approx(0.32168f).margin(EPS)); 25 | REQUIRE(d60_Yxy[2] == Approx(0.33767f).margin(EPS)); 26 | } 27 | -------------------------------------------------------------------------------- /test/macbeth.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "common.hpp" 3 | 4 | #include "TestUtilities.hpp" 5 | 6 | #include 7 | 8 | TEST_CASE("Macbeth") 9 | { 10 | SECTION("ChartToXYZ") 11 | { 12 | for( const ColorSystem::Spectrum& s : ColorSystem::Macbeth::Patch ) 13 | { 14 | const ColorSystem::Tristimulus& xyz = ColorSystem::CIE1931.fromSpectrum( s * ColorSystem::CIE_D65 ); 15 | const ColorSystem::Tristimulus& Yxy = xyz.toYxy(); 16 | printf("%f,%f,%f\n",Yxy[0],Yxy[1],Yxy[2]); // TODO: check with D50 values. 17 | } 18 | 19 | std::vector v = ColorSystem::Macbeth::reference( ColorSystem::CIE_D65, ColorSystem::CIE1931 ); 20 | for( const auto &p:v){ 21 | const ColorSystem::Tristimulus& Yxy = p.toYxy(); 22 | printf("%f,%f,%f / %f,%f,%f\n",p[0],p[1],p[2],Yxy[0],Yxy[1],Yxy[2]); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #define CATCH_CONFIG_MAIN 1 3 | #include 4 | -------------------------------------------------------------------------------- /test/matrices.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "common.hpp" 3 | 4 | #include "TestUtilities.hpp" 5 | 6 | #include 7 | 8 | TEST_CASE("Matrices") 9 | { 10 | SECTION("gamut_convert") 11 | { 12 | const float EPS = 1e-6f; 13 | const ColorSystem::Matrix3 &ACES2065_to_ACEScg(ColorSystem::GamutConvert(ColorSystem::ACES2065, ColorSystem::ACEScg)); 14 | const ColorSystem::Matrix3 expected{ 15 | 1.4514393161f, -0.2365107469f, -0.2149285693f, 16 | -0.0765537734f, 1.1762296998f, -0.0996759264f, 17 | 0.0083161484f, -0.0060324498f, 0.9977163014f}; 18 | REQUIRE_THAT(ACES2065_to_ACEScg, IsApproxEquals(expected, EPS)); 19 | } 20 | 21 | SECTION("bradford") 22 | { 23 | //http://w3.kcua.ac.jp/~fujiwara/infosci/colorspace/bradford.html 24 | const float EPS = 1e-2f; // because some constants are differ from reference. 25 | const ColorSystem::Matrix3 &adopt_D65_to_D50(ColorSystem::Bradford(ColorSystem::Illuminant_D65, ColorSystem::Illuminant_D50)); 26 | const ColorSystem::Matrix3 expected{ 27 | 1.047886f, 0.022919f, -0.050216f, 28 | 0.029582f, 0.990484f, -0.017079f, 29 | -0.009252f, 0.015073f, 0.751678f}; 30 | REQUIRE_THAT(adopt_D65_to_D50, IsApproxEquals(expected, EPS)); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /test/oklab.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | #include "TestUtilities.hpp" 4 | 5 | #include 6 | 7 | void createPairs(const float x, const float y, const float z, const float l, const float a, const float b, 8 | ColorSystem::Tristimulus &xyz, ColorSystem::Tristimulus &lab, ColorSystem::Tristimulus &xyz_lab, 9 | ColorSystem::Tristimulus &lab_xyz) 10 | { 11 | xyz = ColorSystem::Tristimulus(x, y, z); 12 | lab = ColorSystem::Tristimulus(l, a, b); 13 | xyz_lab = ColorSystem::XYZ_to_OKLAB(xyz); 14 | lab_xyz = ColorSystem::OKLAB_to_XYZ(lab); 15 | }; 16 | 17 | TEST_CASE("OKLAB") 18 | { 19 | const float EPS = 1e-1f; 20 | SECTION("test1") 21 | { 22 | ColorSystem::Tristimulus xyz, lab, xyz_lab, lab_xyz; 23 | createPairs(0.950, 1.000, 1.089, 1.000, 0.000, 0.000, xyz, lab, xyz_lab, lab_xyz); 24 | REQUIRE_THAT(xyz, IsApproxEquals(lab_xyz, EPS)); 25 | REQUIRE_THAT(lab, IsApproxEquals(xyz_lab, EPS)); 26 | } 27 | SECTION("test2") 28 | { 29 | ColorSystem::Tristimulus xyz, lab, xyz_lab, lab_xyz; 30 | createPairs(1.000, 0.000, 0.000, 0.450, 1.236, -0.019, xyz, lab, xyz_lab, lab_xyz); 31 | REQUIRE_THAT(xyz, IsApproxEquals(lab_xyz, EPS)); 32 | REQUIRE_THAT(lab, IsApproxEquals(xyz_lab, EPS)); 33 | } 34 | SECTION("test3") 35 | { 36 | ColorSystem::Tristimulus xyz, lab, xyz_lab, lab_xyz; 37 | createPairs(0.000, 1.000, 0.000, 0.922, -0.671, 0.263, xyz, lab, xyz_lab, lab_xyz); 38 | REQUIRE_THAT(xyz, IsApproxEquals(lab_xyz, EPS)); 39 | REQUIRE_THAT(lab, IsApproxEquals(xyz_lab, EPS)); 40 | } 41 | SECTION("test4") 42 | { 43 | ColorSystem::Tristimulus xyz, lab, xyz_lab, lab_xyz; 44 | createPairs(0.000, 0.000, 1.000, 0.153, -1.415, -0.449, xyz, lab, xyz_lab, lab_xyz); 45 | REQUIRE_THAT(xyz, IsApproxEquals(lab_xyz, EPS)); 46 | REQUIRE_THAT(lab, IsApproxEquals(xyz_lab, EPS)); 47 | } 48 | } -------------------------------------------------------------------------------- /test/screen.cpp: -------------------------------------------------------------------------------- 1 | 2 | // "colorsystem" 3 | // copyright 2017 (c) Hajime UCHIMURA / @nikq 4 | // all rights reserved 5 | #include "common.hpp" 6 | 7 | #include "TestUtilities.hpp" 8 | 9 | #include 10 | 11 | namespace 12 | { 13 | const float epsilon = 0.000001f; 14 | } 15 | 16 | TEST_CASE("oetf", "") 17 | { 18 | SECTION("sRGB") 19 | { 20 | const ColorSystem::Tristimulus v0(0.f, 0.f, 0.f); 21 | const ColorSystem::Tristimulus v1(0.25f, 0.5f, 0.75f); 22 | const ColorSystem::Tristimulus v2(1.0f, 1.0f, 1.0f); 23 | const ColorSystem::Tristimulus s0 = ColorSystem::OTF::toScreen(ColorSystem::OTF::SRGB, v0); 24 | const ColorSystem::Tristimulus s1 = ColorSystem::OTF::toScreen(ColorSystem::OTF::SRGB, v1); 25 | const ColorSystem::Tristimulus s2 = ColorSystem::OTF::toScreen(ColorSystem::OTF::SRGB, v2); 26 | REQUIRE_THAT(s0, IsApproxEquals(v0, epsilon)); 27 | REQUIRE_THAT(s2, IsApproxEquals(v2, epsilon)); 28 | const ColorSystem::Tristimulus c0 = ColorSystem::OTF::toScene(ColorSystem::OTF::SRGB, s0); 29 | const ColorSystem::Tristimulus c1 = ColorSystem::OTF::toScene(ColorSystem::OTF::SRGB, s1); 30 | const ColorSystem::Tristimulus c2 = ColorSystem::OTF::toScene(ColorSystem::OTF::SRGB, s2); 31 | REQUIRE_THAT(v0, IsApproxEquals(c0, epsilon)); 32 | REQUIRE_THAT(v1, IsApproxEquals(c1, epsilon)); 33 | REQUIRE_THAT(v2, IsApproxEquals(c2, epsilon)); 34 | } 35 | SECTION("BT709") 36 | { 37 | const ColorSystem::Tristimulus v0(0.f, 0.f, 0.f); 38 | const ColorSystem::Tristimulus v1(0.25f, 0.5f, 0.75f); 39 | const ColorSystem::Tristimulus v2(1.0f, 1.0f, 1.0f); 40 | const ColorSystem::Tristimulus s0 = ColorSystem::OTF::toScreen(ColorSystem::OTF::BT709, v0); 41 | const ColorSystem::Tristimulus s1 = ColorSystem::OTF::toScreen(ColorSystem::OTF::BT709, v1); 42 | const ColorSystem::Tristimulus s2 = ColorSystem::OTF::toScreen(ColorSystem::OTF::BT709, v2); 43 | const ColorSystem::Tristimulus c0 = ColorSystem::OTF::toScene(ColorSystem::OTF::BT709, s0); 44 | const ColorSystem::Tristimulus c1 = ColorSystem::OTF::toScene(ColorSystem::OTF::BT709, s1); 45 | const ColorSystem::Tristimulus c2 = ColorSystem::OTF::toScene(ColorSystem::OTF::BT709, s2); 46 | REQUIRE_THAT(v0, IsApproxEquals(c0, epsilon)); 47 | REQUIRE_THAT(v1, IsApproxEquals(c1, epsilon)); 48 | REQUIRE_THAT(v2, IsApproxEquals(c2, epsilon)); 49 | } 50 | SECTION("HLG") 51 | { 52 | const ColorSystem::Tristimulus v0(0.f, 0.f, 0.f); 53 | const ColorSystem::Tristimulus v1(0.25f, 0.5f, 0.75f); 54 | const ColorSystem::Tristimulus v2(1.0f, 2.2f, 4.4f); 55 | const ColorSystem::Tristimulus s0 = ColorSystem::OTF::toScreen(ColorSystem::OTF::HLG, v0); 56 | const ColorSystem::Tristimulus s1 = ColorSystem::OTF::toScreen(ColorSystem::OTF::HLG, v1); 57 | const ColorSystem::Tristimulus s2 = ColorSystem::OTF::toScreen(ColorSystem::OTF::HLG, v2); 58 | REQUIRE_THAT(s0, IsApproxEquals(v0, epsilon)); 59 | const ColorSystem::Tristimulus c0 = ColorSystem::OTF::toScene(ColorSystem::OTF::HLG, s0); 60 | const ColorSystem::Tristimulus c1 = ColorSystem::OTF::toScene(ColorSystem::OTF::HLG, s1); 61 | const ColorSystem::Tristimulus c2 = ColorSystem::OTF::toScene(ColorSystem::OTF::HLG, s2); 62 | REQUIRE_THAT(v0, IsApproxEquals(c0, epsilon)); 63 | REQUIRE_THAT(v1, IsApproxEquals(c1, epsilon)); 64 | REQUIRE_THAT(v2, IsApproxEquals(c2, epsilon)); 65 | } 66 | } -------------------------------------------------------------------------------- /test/spectrum.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "common.hpp" 3 | 4 | #include "TestUtilities.hpp" 5 | 6 | #include 7 | 8 | TEST_CASE("Spectrum") 9 | { 10 | SECTION("blackbody") 11 | { 12 | const double planck6000 = ColorSystem::Spectrum::planck(6000., 380 * 1e-9); 13 | REQUIRE(planck6000 == Approx(27366).margin(1e2)); 14 | } 15 | SECTION("toXYZ") 16 | { 17 | const ColorSystem::Spectrum & BB65(ColorSystem::Spectrum::blackbody(6504.f)); 18 | const ColorSystem::Spectrum & E(ColorSystem::Spectrum::E(1.f)); 19 | const ColorSystem::Tristimulus D65_Yxy = ColorSystem::CIE1931.fromSpectrum(ColorSystem::CIE_D65).toYxy(); 20 | const ColorSystem::Tristimulus BB65_Yxy = ColorSystem::CIE1931.fromSpectrum(BB65).toYxy(); 21 | const ColorSystem::Tristimulus E_Yxy = ColorSystem::CIE1931.fromSpectrum(E).toYxy(); 22 | REQUIRE(D65_Yxy[1] == Approx(ColorSystem::Illuminant_D65.toYxy()[1]).margin(1e-4f)); 23 | REQUIRE(D65_Yxy[2] == Approx(ColorSystem::Illuminant_D65.toYxy()[2]).margin(1e-4f)); 24 | REQUIRE(BB65_Yxy[1] == Approx(ColorSystem::Illuminant_D65.toYxy()[1]).margin(1e-2f)); // precision problem... 25 | REQUIRE(BB65_Yxy[2] == Approx(ColorSystem::Illuminant_D65.toYxy()[2]).margin(1e-2f)); 26 | REQUIRE(E_Yxy[1] == Approx(ColorSystem::Illuminant_E.toYxy()[1]).margin(1e-5f)); 27 | REQUIRE(E_Yxy[2] == Approx(ColorSystem::Illuminant_E.toYxy()[2]).margin(1e-5f)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/units.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "common.hpp" 3 | 4 | #include "TestUtilities.hpp" 5 | 6 | #include 7 | 8 | TEST_CASE("Units") 9 | { 10 | SECTION("lumens") 11 | { 12 | const float lumens555 = ColorSystem::CIE1931.lumensFromMonochromaticFlux( 555.0f, 1.0f );// 13 | REQUIRE( lumens555 == Approx(683.0f).epsilon(1e-3f) ); 14 | } 15 | SECTION("candellas") 16 | { 17 | const ColorSystem::Tristimulus cd_m2 = ColorSystem::CIE1931.candellasFromMonochromaticRadiance( 555.0f, 1.0f ); 18 | REQUIRE_THAT(cd_m2, IsApproxEquals(ColorSystem::Tristimulus{349.730225f,683.0f,3.927249f}, 1e-3f)); 19 | } 20 | } 21 | --------------------------------------------------------------------------------