1 | // LAF Gfx Library |
2 | // Copyright (c) 2018-2020 Igara Studio S.A. |
3 | // |
4 | // This file is released under the terms of the MIT license. |
5 | // Read LICENSE.txt for more information. |
6 | |
7 | #ifdef HAVE_CONFIG_H |
8 | #include "config.h" |
9 | #endif |
10 | |
11 | #include "gfx/color_space.h" |
12 | |
13 | #include <algorithm> |
14 | #include <cmath> |
15 | #include <cstdint> |
16 | #include <string> |
17 | #include <vector> |
18 | |
19 | namespace gfx { |
20 | |
21 | ColorSpace::ColorSpace(const Type type, |
22 | const Flag flags, |
23 | const float gamma, |
24 | std::vector<uint8_t>&& data) |
25 | : m_type(type), |
26 | m_flags(flags), |
27 | m_gamma(gamma), |
28 | m_data(std::move(data)) |
29 | { |
30 | } |
31 | |
32 | // static |
33 | ColorSpaceRef ColorSpace::MakeNone() |
34 | { |
35 | return base::make_ref<ColorSpace>(None); |
36 | } |
37 | |
38 | // static |
39 | ColorSpaceRef ColorSpace::MakeSRGB() |
40 | { |
41 | return base::make_ref<ColorSpace>(sRGB); |
42 | } |
43 | |
44 | // static |
45 | ColorSpaceRef ColorSpace::MakeLinearSRGB() |
46 | { |
47 | return base::make_ref<ColorSpace>(sRGB, HasGamma, 1.0); |
48 | } |
49 | |
50 | // static |
51 | ColorSpaceRef ColorSpace::MakeSRGBWithGamma(float gamma) |
52 | { |
53 | return base::make_ref<ColorSpace>(sRGB, HasGamma, gamma); |
54 | } |
55 | |
56 | // static |
57 | ColorSpaceRef ColorSpace::MakeRGB(const ColorSpaceTransferFn& fn, |
58 | const ColorSpacePrimaries& p) |
59 | { |
60 | std::vector<uint8_t> data(sizeof(ColorSpaceTransferFn) + sizeof(ColorSpacePrimaries)); |
61 | std::copy(((const uint8_t*)&fn), |
62 | ((const uint8_t*)&fn) + sizeof(ColorSpaceTransferFn), |
63 | data.begin()); |
64 | std::copy(((const uint8_t*)&p), |
65 | ((const uint8_t*)&p) + sizeof(ColorSpacePrimaries), |
66 | data.begin() + sizeof(ColorSpaceTransferFn)); |
67 | return base::make_ref<ColorSpace>( |
68 | RGB, Flag(HasTransferFn | HasPrimaries), 1.0, std::move(data)); |
69 | } |
70 | |
71 | // static |
72 | ColorSpaceRef ColorSpace::MakeRGBWithSRGBGamut(const ColorSpaceTransferFn& fn) |
73 | { |
74 | std::vector<uint8_t> data(sizeof(ColorSpaceTransferFn)); |
75 | std::copy(((const uint8_t*)&fn), |
76 | ((const uint8_t*)&fn) + sizeof(ColorSpaceTransferFn), |
77 | data.begin()); |
78 | return base::make_ref<ColorSpace>(sRGB, HasTransferFn, 1.0, std::move(data)); |
79 | } |
80 | |
81 | // static |
82 | ColorSpaceRef ColorSpace::MakeRGBWithSRGBGamma(const ColorSpacePrimaries& p) |
83 | { |
84 | std::vector<uint8_t> data(sizeof(ColorSpacePrimaries)); |
85 | std::copy(((const uint8_t*)&p), |
86 | ((const uint8_t*)&p) + sizeof(ColorSpacePrimaries), |
87 | data.begin()); |
88 | return base::make_ref<ColorSpace>(sRGB, HasPrimaries, 1.0, std::move(data)); |
89 | } |
90 | |
91 | // static |
92 | ColorSpaceRef ColorSpace::MakeICC(const void* data, size_t n) |
93 | { |
94 | std::vector<uint8_t> newData(n); |
95 | std::copy(((const uint8_t*)data), |
96 | ((const uint8_t*)data)+n, newData.begin()); |
97 | return base::make_ref<ColorSpace>(ICC, HasICC, 1.0, std::move(newData)); |
98 | } |
99 | |
100 | // static |
101 | ColorSpaceRef ColorSpace::MakeICC(std::vector<uint8_t>&& data) |
102 | { |
103 | return base::make_ref<ColorSpace>(ICC, HasICC, 1.0, std::move(data)); |
104 | } |
105 | |
106 | // Based on code in skia/src/core/SkICC.cpp by Google Inc. |
107 | static bool nearly_equal(float x, float y) { |
108 | // A note on why I chose this tolerance: transfer_fn_almost_equal() uses a |
109 | // tolerance of 0.001, which doesn't seem to be enough to distinguish |
110 | // between similar transfer functions, for example: gamma2.2 and sRGB. |
111 | // |
112 | // If the tolerance is 0.0f, then this we can't distinguish between two |
113 | // different encodings of what is clearly the same colorspace. Some |
114 | // experimentation with example files lead to this number: |
115 | static constexpr float kTolerance = 1.0f / (1 << 11); |
116 | return fabsf(x - y) <= kTolerance; |
117 | } |
118 | |
119 | static bool nearly_equal(const ColorSpaceTransferFn& u, |
120 | const ColorSpaceTransferFn& v) { |
121 | return nearly_equal(u.g, v.g) |
122 | && nearly_equal(u.a, v.a) |
123 | && nearly_equal(u.b, v.b) |
124 | && nearly_equal(u.c, v.c) |
125 | && nearly_equal(u.d, v.d) |
126 | && nearly_equal(u.e, v.e) |
127 | && nearly_equal(u.f, v.f); |
128 | } |
129 | |
130 | static bool nearly_equal(const ColorSpacePrimaries& u, |
131 | const ColorSpacePrimaries& v) { |
132 | return nearly_equal(u.wx, v.wx) |
133 | && nearly_equal(u.wy, v.wy) |
134 | && nearly_equal(u.rx, v.ry) |
135 | && nearly_equal(u.ry, v.ry) |
136 | && nearly_equal(u.gx, v.gy) |
137 | && nearly_equal(u.gy, v.gy) |
138 | && nearly_equal(u.bx, v.by) |
139 | && nearly_equal(u.by, v.by); |
140 | } |
141 | |
142 | bool ColorSpace::nearlyEqual(const ColorSpace& that) const |
143 | { |
144 | if (m_type != that.m_type) |
145 | return false; |
146 | else if (m_type == None) |
147 | return true; |
148 | else if (m_type == sRGB || |
149 | m_type == RGB) { |
150 | // Gamma |
151 | if (has(HasGamma) && that.has(HasGamma)) { |
152 | if (!nearly_equal(gamma(), that.gamma())) |
153 | return false; |
154 | } |
155 | else if (has(HasGamma) != that.has(HasGamma)) { |
156 | return false; |
157 | } |
158 | // Transfer function |
159 | if (has(HasTransferFn) && that.has(HasTransferFn)) { |
160 | if (!nearly_equal(*transferFn(), *that.transferFn())) |
161 | return false; |
162 | } |
163 | else if (has(HasTransferFn) != that.has(HasTransferFn)) { |
164 | return false; |
165 | } |
166 | // Primaries |
167 | if (has(HasPrimaries) && that.has(HasPrimaries)) { |
168 | if (!nearly_equal(*primaries(), *that.primaries())) |
169 | return false; |
170 | } |
171 | else if (has(HasPrimaries) != that.has(HasPrimaries)) { |
172 | return false; |
173 | } |
174 | return true; |
175 | } |
176 | else if (m_type == ICC) { |
177 | return (m_data == that.m_data); |
178 | } |
179 | return false; |
180 | } |
181 | |
182 | } // namespace gfx |
183 | |