1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/gfx/icc_profile.h"
6
7 #include <list>
8 #include <set>
9
10 #include "base/command_line.h"
11 #include "base/containers/mru_cache.h"
12 #include "base/lazy_instance.h"
13 #include "base/synchronization/lock.h"
14 #include "third_party/skia/include/core/SkColorSpace.h"
15 #include "third_party/skia/include/third_party/skcms/skcms.h"
16 #include "ui/gfx/skia_color_space_util.h"
17
18 namespace gfx {
19
20 namespace {
21
22 static const size_t kMaxCachedICCProfiles = 16;
23
24 // An MRU cache mapping data to ICCProfile objects, to avoid re-parsing
25 // profiles every time they are read.
26 using DataToProfileCacheBase = base::MRUCache<std::vector<char>, ICCProfile>;
27 class DataToProfileCache : public DataToProfileCacheBase {
28 public:
DataToProfileCache()29 DataToProfileCache() : DataToProfileCacheBase(kMaxCachedICCProfiles) {}
30 };
31 base::LazyInstance<DataToProfileCache>::Leaky g_data_to_profile_cache =
32 LAZY_INSTANCE_INITIALIZER;
33
34 // Lock that must be held to access |g_data_to_profile_cache|.
35 base::LazyInstance<base::Lock>::Leaky g_icc_profile_lock =
36 LAZY_INSTANCE_INITIALIZER;
37
38 } // namespace
39
Initialize()40 void ICCProfile::Internals::Initialize() {
41 // Start out with no parametric data.
42 if (data_.empty())
43 return;
44
45 // Parse the profile.
46 skcms_ICCProfile profile;
47 if (!skcms_Parse(data_.data(), data_.size(), &profile)) {
48 DLOG(ERROR) << "Failed to parse ICC profile.";
49 return;
50 }
51
52 // We have seen many users with profiles that don't have a D50 white point.
53 // Windows appears to detect these profiles, and not use them for OS drawing.
54 // It still returns them when we query the system for the installed profile.
55 // For consistency (and to match old behavior) we reject these profiles on
56 // all platforms.
57 // https://crbug.com/847024
58 const skcms_Matrix3x3& m(profile.toXYZD50);
59 float wX = m.vals[0][0] + m.vals[0][1] + m.vals[0][2];
60 float wY = m.vals[1][0] + m.vals[1][1] + m.vals[1][2];
61 float wZ = m.vals[2][0] + m.vals[2][1] + m.vals[2][2];
62 static const float kD50_WhitePoint[3] = { 0.96420f, 1.00000f, 0.82491f };
63 if (fabsf(wX - kD50_WhitePoint[0]) > 0.04f ||
64 fabsf(wY - kD50_WhitePoint[1]) > 0.04f ||
65 fabsf(wZ - kD50_WhitePoint[2]) > 0.04f) {
66 return;
67 }
68
69 // At this point, the profile is considered valid. We still need to determine
70 // if it's representable with a parametric transfer function.
71 is_valid_ = true;
72
73 // Extract the primary matrix, and assume that transfer function is sRGB until
74 // we get something more precise.
75 to_XYZD50_ = profile.toXYZD50;
76 transfer_fn_ = SkNamedTransferFn::kSRGB;
77
78 // Coerce it into a rasterization destination (if possible). If the profile
79 // can't be approximated accurately, then use an sRGB transfer function and
80 // return failure. We will continue to use the gamut from this profile.
81 if (!skcms_MakeUsableAsDestinationWithSingleCurve(&profile)) {
82 DLOG(ERROR) << "Parsed ICC profile but can't make usable as destination, "
83 "using sRGB gamma";
84 return;
85 }
86
87 // If SkColorSpace will treat the gamma as that of sRGB, then use the named
88 // constants.
89 sk_sp<SkColorSpace> sk_color_space = SkColorSpace::Make(profile);
90 if (!sk_color_space) {
91 DLOG(ERROR) << "Parsed ICC profile but cannot create SkColorSpace from it, "
92 "using sRGB gamma.";
93 return;
94 }
95
96 // We were able to get a parametric representation of the transfer function.
97 is_parametric_ = true;
98
99 if (sk_color_space->gammaCloseToSRGB())
100 return;
101
102 // We assume that if we accurately approximated the profile, then the
103 // single-curve version (which may have higher error) is also okay. If we
104 // want to maintain the distinction between accurate and inaccurate profiles,
105 // we could check to see if the single-curve version is/ approximately equal
106 // to the original (or to the multi-channel approximation).
107 transfer_fn_ = profile.trc[0].parametric;
108 }
109
110 ICCProfile::ICCProfile() = default;
111 ICCProfile::ICCProfile(ICCProfile&& other) = default;
112 ICCProfile::ICCProfile(const ICCProfile& other) = default;
113 ICCProfile& ICCProfile::operator=(ICCProfile&& other) = default;
114 ICCProfile& ICCProfile::operator=(const ICCProfile& other) = default;
115 ICCProfile::~ICCProfile() = default;
116
operator ==(const ICCProfile & other) const117 bool ICCProfile::operator==(const ICCProfile& other) const {
118 if (!internals_ && !other.internals_)
119 return true;
120 if (internals_ && other.internals_) {
121 return internals_->data_ == other.internals_->data_;
122 }
123 return false;
124 }
125
operator !=(const ICCProfile & other) const126 bool ICCProfile::operator!=(const ICCProfile& other) const {
127 return !(*this == other);
128 }
129
IsValid() const130 bool ICCProfile::IsValid() const {
131 return internals_ ? internals_->is_valid_ : false;
132 }
133
GetData() const134 std::vector<char> ICCProfile::GetData() const {
135 return internals_ ? internals_->data_ : std::vector<char>();
136 }
137
138 // static
FromData(const void * data_as_void,size_t size)139 ICCProfile ICCProfile::FromData(const void* data_as_void, size_t size) {
140 const char* data_as_byte = reinterpret_cast<const char*>(data_as_void);
141 std::vector<char> data(data_as_byte, data_as_byte + size);
142
143 base::AutoLock lock(g_icc_profile_lock.Get());
144
145 // See if there is already an entry with the same data. If so, return that
146 // entry. If not, parse the data.
147 ICCProfile icc_profile;
148 auto found_by_data = g_data_to_profile_cache.Get().Get(data);
149 if (found_by_data != g_data_to_profile_cache.Get().end()) {
150 icc_profile = found_by_data->second;
151 } else {
152 icc_profile.internals_ = base::MakeRefCounted<Internals>(std::move(data));
153 }
154
155 // Insert the profile into all caches.
156 g_data_to_profile_cache.Get().Put(icc_profile.internals_->data_, icc_profile);
157
158 return icc_profile;
159 }
160
GetColorSpace() const161 ColorSpace ICCProfile::GetColorSpace() const {
162 if (!internals_ || !internals_->is_valid_)
163 return ColorSpace();
164
165 return ColorSpace(ColorSpace::PrimaryID::CUSTOM,
166 ColorSpace::TransferID::CUSTOM, ColorSpace::MatrixID::RGB,
167 ColorSpace::RangeID::FULL, &internals_->to_XYZD50_,
168 &internals_->transfer_fn_);
169 }
170
GetPrimariesOnlyColorSpace() const171 ColorSpace ICCProfile::GetPrimariesOnlyColorSpace() const {
172 if (!internals_ || !internals_->is_valid_)
173 return ColorSpace();
174
175 return ColorSpace(ColorSpace::PrimaryID::CUSTOM,
176 ColorSpace::TransferID::IEC61966_2_1,
177 ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL,
178 &internals_->to_XYZD50_, nullptr);
179 }
180
IsColorSpaceAccurate() const181 bool ICCProfile::IsColorSpaceAccurate() const {
182 if (!internals_)
183 return false;
184
185 if (!internals_->is_valid_)
186 return false;
187
188 return internals_->is_parametric_;
189 }
190
191 // static
FromColorSpace(const ColorSpace & color_space)192 ICCProfile ICCProfile::FromColorSpace(const ColorSpace& color_space) {
193 if (!color_space.IsValid()) {
194 return ICCProfile();
195 }
196 if (color_space.GetMatrixID() != ColorSpace::MatrixID::RGB) {
197 DLOG(ERROR) << "Not creating non-RGB ICCProfile";
198 return ICCProfile();
199 }
200 if (color_space.GetRangeID() != ColorSpace::RangeID::FULL) {
201 DLOG(ERROR) << "Not creating non-full-range ICCProfile";
202 return ICCProfile();
203 }
204 skcms_Matrix3x3 to_XYZD50_matrix;
205 color_space.GetPrimaryMatrix(&to_XYZD50_matrix);
206 skcms_TransferFunction fn;
207 if (!color_space.GetTransferFunction(&fn)) {
208 DLOG(ERROR) << "Failed to get ColorSpace transfer function for ICCProfile.";
209 return ICCProfile();
210 }
211 sk_sp<SkData> data = SkWriteICCProfile(fn, to_XYZD50_matrix);
212 if (!data) {
213 DLOG(ERROR) << "Failed to create SkICC.";
214 return ICCProfile();
215 }
216 return FromData(data->data(), data->size());
217 }
218
Internals(std::vector<char> data)219 ICCProfile::Internals::Internals(std::vector<char> data)
220 : data_(std::move(data)) {
221 // Parse the ICC profile
222 Initialize();
223 }
224
~Internals()225 ICCProfile::Internals::~Internals() {}
226
227 } // namespace gfx
228