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
19namespace gfx {
20
21ColorSpace::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
33ColorSpaceRef ColorSpace::MakeNone()
34{
35 return base::make_ref<ColorSpace>(None);
36}
37
38// static
39ColorSpaceRef ColorSpace::MakeSRGB()
40{
41 return base::make_ref<ColorSpace>(sRGB);
42}
43
44// static
45ColorSpaceRef ColorSpace::MakeLinearSRGB()
46{
47 return base::make_ref<ColorSpace>(sRGB, HasGamma, 1.0);
48}
49
50// static
51ColorSpaceRef ColorSpace::MakeSRGBWithGamma(float gamma)
52{
53 return base::make_ref<ColorSpace>(sRGB, HasGamma, gamma);
54}
55
56// static
57ColorSpaceRef 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
72ColorSpaceRef 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
82ColorSpaceRef 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
92ColorSpaceRef 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
101ColorSpaceRef 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.
107static 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
119static 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
130static 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
142bool 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