blob: ff768dbdd349924f313a0e1e8b65e2c82c0bea8c [file] [log] [blame]
ccameronefdab162016-07-25 23:00:021// 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>
Christopher Cameron374b6c42017-08-31 22:21:238#include <set>
ccameronefdab162016-07-25 23:00:029
ccameron290a91a2017-05-13 19:22:3310#include "base/command_line.h"
ccameronefdab162016-07-25 23:00:0211#include "base/containers/mru_cache.h"
12#include "base/lazy_instance.h"
Christopher Cameron374b6c42017-08-31 22:21:2313#include "base/metrics/histogram_macros.h"
ccameronefdab162016-07-25 23:00:0214#include "base/synchronization/lock.h"
ccameronb3206e22017-03-25 02:09:1215#include "third_party/skia/include/core/SkColorSpaceXform.h"
ccameron549738b2017-01-28 17:39:3216#include "third_party/skia/include/core/SkICC.h"
ccameron24c87c32017-03-14 21:50:4217#include "ui/gfx/skia_color_space_util.h"
ccameronefdab162016-07-25 23:00:0218
19namespace gfx {
20
ccameron9b2a0b1b2017-02-02 23:29:1421const uint64_t ICCProfile::test_id_adobe_rgb_ = 1;
22const uint64_t ICCProfile::test_id_color_spin_ = 2;
23const uint64_t ICCProfile::test_id_generic_rgb_ = 3;
24const uint64_t ICCProfile::test_id_srgb_ = 4;
ccameron9b2a0b1b2017-02-02 23:29:1425
Christopher Cameron374b6c42017-08-31 22:21:2326namespace {
27
Christopher Cameron43f17052017-11-11 01:52:0128const uint64_t kEmptyProfileId = 5;
29
30// Start from-ICC-data IDs at the end of the hard-coded test id list above.
31uint64_t g_next_unused_id = 10;
32
33using ProfileCacheBase = base::MRUCache<uint64_t, ICCProfile>;
34class ProfileCache : public ProfileCacheBase {
35 public:
36 static const size_t kMaxCachedICCProfiles = 16;
37 ProfileCache() : ProfileCacheBase(kMaxCachedICCProfiles) {}
38};
39base::LazyInstance<ProfileCache>::DestructorAtExit g_cache =
40 LAZY_INSTANCE_INITIALIZER;
41
42// Lock that must be held to access |g_cache| and |g_next_unused_id|.
43base::LazyInstance<base::Lock>::DestructorAtExit g_lock =
scottmg5e65e3a2017-03-08 08:48:4644 LAZY_INSTANCE_INITIALIZER;
ccameronefdab162016-07-25 23:00:0245
Christopher Cameron374b6c42017-08-31 22:21:2346} // namespace
47
Christopher Cameron43f17052017-11-11 01:52:0148ICCProfile::Internals::AnalyzeResult ICCProfile::Internals::Initialize() {
Christopher Cameronfdc0f872017-11-09 03:22:1649 // Start out with no parametric data.
Christopher Camerona4adb41b2017-07-13 06:44:2550
51 // Parse the profile and attempt to create a SkColorSpaceXform out of it.
52 sk_sp<SkColorSpace> sk_srgb_color_space = SkColorSpace::MakeSRGB();
Christopher Cameronfdc0f872017-11-09 03:22:1653 sk_sp<SkICC> sk_icc = SkICC::Make(data_.data(), data_.size());
Christopher Camerona4adb41b2017-07-13 06:44:2554 if (!sk_icc) {
55 DLOG(ERROR) << "Failed to parse ICC profile to SkICC.";
Christopher Cameron374b6c42017-08-31 22:21:2356 return kICCFailedToParse;
Christopher Camerona4adb41b2017-07-13 06:44:2557 }
Christopher Cameronfdc0f872017-11-09 03:22:1658 sk_color_space_ = SkColorSpace::MakeICC(data_.data(), data_.size());
59 if (!sk_color_space_) {
Christopher Camerona4adb41b2017-07-13 06:44:2560 DLOG(ERROR) << "Failed to parse ICC profile to SkColorSpace.";
Christopher Cameron374b6c42017-08-31 22:21:2361 return kICCFailedToExtractSkColorSpace;
Christopher Camerona4adb41b2017-07-13 06:44:2562 }
63 std::unique_ptr<SkColorSpaceXform> sk_color_space_xform =
Christopher Cameronfdc0f872017-11-09 03:22:1664 SkColorSpaceXform::New(sk_srgb_color_space.get(), sk_color_space_.get());
Christopher Camerona4adb41b2017-07-13 06:44:2565 if (!sk_color_space_xform) {
66 DLOG(ERROR) << "Parsed ICC profile but can't create SkColorSpaceXform.";
Christopher Cameron374b6c42017-08-31 22:21:2367 return kICCFailedToCreateXform;
Christopher Camerona4adb41b2017-07-13 06:44:2568 }
69
Christopher Cameronfdc0f872017-11-09 03:22:1670 // Because this SkColorSpace can be used to construct a transform, we can use
71 // it to create a LUT based color transform, at the very least. If we fail to
72 // get any better approximation, we'll use sRGB as our approximation.
73 ColorSpace::CreateSRGB().GetPrimaryMatrix(&to_XYZD50_);
74 ColorSpace::CreateSRGB().GetTransferFunction(&transfer_fn_);
Christopher Camerona4adb41b2017-07-13 06:44:2575
ccamerone35e2322017-11-02 23:33:4676 // If our SkColorSpace representation is sRGB then return that.
Christopher Cameronfdc0f872017-11-09 03:22:1677 if (sk_color_space_->isSRGB())
ccamerone35e2322017-11-02 23:33:4678 return kICCExtractedSRGBColorSpace;
ccamerone35e2322017-11-02 23:33:4679
Christopher Cameronfdc0f872017-11-09 03:22:1680 // A primary matrix is required for our parametric representations. Use it if
81 // it exists.
Christopher Camerona4adb41b2017-07-13 06:44:2582 SkMatrix44 to_XYZD50_matrix;
83 if (!sk_icc->toXYZD50(&to_XYZD50_matrix)) {
84 DLOG(ERROR) << "Failed to extract ICC profile primary matrix.";
Christopher Cameron374b6c42017-08-31 22:21:2385 return kICCFailedToExtractMatrix;
Christopher Camerona4adb41b2017-07-13 06:44:2586 }
Christopher Cameronfdc0f872017-11-09 03:22:1687 to_XYZD50_ = to_XYZD50_matrix;
Christopher Camerona4adb41b2017-07-13 06:44:2588
Christopher Cameronfdc0f872017-11-09 03:22:1689 // Try to directly extract a numerical transfer function. Use it if it
90 // exists.
Christopher Camerona4adb41b2017-07-13 06:44:2591 SkColorSpaceTransferFn exact_tr_fn;
92 if (sk_icc->isNumericalTransferFn(&exact_tr_fn)) {
Christopher Cameronfdc0f872017-11-09 03:22:1693 transfer_fn_ = exact_tr_fn;
Christopher Cameron374b6c42017-08-31 22:21:2394 return kICCExtractedMatrixAndAnalyticTrFn;
Christopher Camerona4adb41b2017-07-13 06:44:2595 }
96
Christopher Cameron374b6c42017-08-31 22:21:2397 // Attempt to fit a parametric transfer function to the table data in the
98 // profile.
99 SkColorSpaceTransferFn approx_tr_fn;
Christopher Cameronfdc0f872017-11-09 03:22:16100 if (!SkApproximateTransferFn(sk_icc, &transfer_fn_error_, &approx_tr_fn)) {
Christopher Cameron374b6c42017-08-31 22:21:23101 DLOG(ERROR) << "Failed approximate transfer function.";
102 return kICCFailedToConvergeToApproximateTrFn;
103 }
104
105 // If this converged, but has too high error, use the sRGB transfer function
106 // from above.
107 const float kMaxError = 2.f / 256.f;
Christopher Cameronfdc0f872017-11-09 03:22:16108 if (transfer_fn_error_ >= kMaxError) {
Christopher Cameron374b6c42017-08-31 22:21:23109 DLOG(ERROR) << "Failed to accurately approximate transfer function, error: "
Christopher Cameronfdc0f872017-11-09 03:22:16110 << 256.f * transfer_fn_error_ << "/256";
Christopher Cameron374b6c42017-08-31 22:21:23111 return kICCFailedToApproximateTrFnAccurately;
112 };
113
114 // If the error is sufficiently low, declare that the approximation is
115 // accurate.
Christopher Cameronfdc0f872017-11-09 03:22:16116 transfer_fn_ = approx_tr_fn;
Christopher Cameron374b6c42017-08-31 22:21:23117 return kICCExtractedMatrixAndApproximatedTrFn;
118}
Reilly Grantc46032eb2017-08-30 22:53:52119
ccameronefdab162016-07-25 23:00:02120ICCProfile::ICCProfile() = default;
121ICCProfile::ICCProfile(ICCProfile&& other) = default;
122ICCProfile::ICCProfile(const ICCProfile& other) = default;
123ICCProfile& ICCProfile::operator=(ICCProfile&& other) = default;
124ICCProfile& ICCProfile::operator=(const ICCProfile& other) = default;
125ICCProfile::~ICCProfile() = default;
126
127bool ICCProfile::operator==(const ICCProfile& other) const {
Christopher Cameron43f17052017-11-11 01:52:01128 if (!internals_ && !other.internals_)
129 return true;
130 if (internals_ && other.internals_)
131 return internals_->data_ == other.internals_->data_;
132 return false;
ccameronefdab162016-07-25 23:00:02133}
134
ccameronc21ca23b2017-01-20 03:34:01135bool ICCProfile::operator!=(const ICCProfile& other) const {
136 return !(*this == other);
137}
138
Christopher Cameron43f17052017-11-11 01:52:01139bool ICCProfile::IsValid() const {
140 return internals_ ? internals_->is_valid_ : false;
141}
142
ccameronefdab162016-07-25 23:00:02143// static
ccameron549738b2017-01-28 17:39:32144ICCProfile ICCProfile::FromData(const void* data, size_t size) {
ccameron9b2a0b1b2017-02-02 23:29:14145 return FromDataWithId(data, size, 0);
146}
147
148// static
Christopher Cameron43f17052017-11-11 01:52:01149ICCProfile ICCProfile::FromDataWithId(const void* data_as_void,
ccameron9b2a0b1b2017-02-02 23:29:14150 size_t size,
151 uint64_t new_profile_id) {
Christopher Cameron43f17052017-11-11 01:52:01152 const char* data_as_byte = reinterpret_cast<const char*>(data_as_void);
153 std::vector<char> new_profile_data(data_as_byte, data_as_byte + size);
Reilly Grantc46032eb2017-08-30 22:53:52154
Christopher Cameron43f17052017-11-11 01:52:01155 base::AutoLock lock(g_lock.Get());
ccameronefdab162016-07-25 23:00:02156
Christopher Cameron43f17052017-11-11 01:52:01157 if (new_profile_id) {
158 // If this has specified an id, see if we have an entry for it (and ensure
159 // that that entry have the same data).
160 auto found = g_cache.Get().Get(new_profile_id);
161 if (found != g_cache.Get().end()) {
162 const ICCProfile& cached_profile = found->second;
163 DCHECK(new_profile_data == cached_profile.internals_->data_);
164 return cached_profile;
165 }
166 }
ccameronefdab162016-07-25 23:00:02167
Christopher Cameron43f17052017-11-11 01:52:01168 // See if there is already an entry with the same data. If so, return that
169 // entry (even if that means ignoring the profile id that we were provided).
170 for (const auto& iter : g_cache.Get()) {
171 const ICCProfile& iter_profile = iter.second;
172 if (new_profile_data == iter_profile.internals_->data_)
173 return iter_profile;
174 }
175
176 // Create a new id for this data if one was not specified. Always ensure that
177 // the emptry profile have the same id.
178 if (!new_profile_id) {
179 if (size == 0)
180 new_profile_id = kEmptyProfileId;
181 else
182 new_profile_id = g_next_unused_id++;
183 }
184
185 // Create a new profile for this data.
186 ICCProfile new_profile;
187 new_profile.internals_ = base::MakeRefCounted<Internals>(
188 std::move(new_profile_data), new_profile_id);
189 g_cache.Get().Put(new_profile_id, new_profile);
190 return new_profile;
ccameronefdab162016-07-25 23:00:02191}
192
Christopher Cameronfdc0f872017-11-09 03:22:16193ColorSpace ICCProfile::GetColorSpace() const {
Christopher Cameron43f17052017-11-11 01:52:01194 if (!internals_)
195 return ColorSpace();
Christopher Cameronfdc0f872017-11-09 03:22:16196
Christopher Cameron43f17052017-11-11 01:52:01197 TouchCacheEntry();
198
199 if (!internals_->is_valid_)
Christopher Cameronfdc0f872017-11-09 03:22:16200 return ColorSpace();
201
202 gfx::ColorSpace color_space;
Christopher Cameron43f17052017-11-11 01:52:01203 if (internals_->is_parametric_) {
Christopher Cameronfdc0f872017-11-09 03:22:16204 color_space = GetParametricColorSpace();
Christopher Cameron43f17052017-11-11 01:52:01205 color_space.icc_profile_sk_color_space_ = internals_->sk_color_space_;
Christopher Cameronfdc0f872017-11-09 03:22:16206 } else {
Christopher Cameron43f17052017-11-11 01:52:01207 color_space = ColorSpace::CreateCustom(internals_->to_XYZD50_,
208 ColorSpace::TransferID::ICC_BASED);
209 color_space.icc_profile_id_ = internals_->id_;
210 color_space.icc_profile_sk_color_space_ = internals_->sk_color_space_;
Christopher Cameronfdc0f872017-11-09 03:22:16211 }
212 return color_space;
ccamerone35e2322017-11-02 23:33:46213}
suzyhc3de81a2017-01-27 00:28:10214
Christopher Cameronfdc0f872017-11-09 03:22:16215ColorSpace ICCProfile::GetParametricColorSpace() const {
Christopher Cameron43f17052017-11-11 01:52:01216 if (!internals_)
217 return ColorSpace();
Christopher Cameronfdc0f872017-11-09 03:22:16218
Christopher Cameron43f17052017-11-11 01:52:01219 TouchCacheEntry();
220
221 if (!internals_->is_valid_)
Christopher Cameronfdc0f872017-11-09 03:22:16222 return ColorSpace();
223
224 ColorSpace color_space =
Christopher Cameron43f17052017-11-11 01:52:01225 internals_->sk_color_space_->isSRGB()
Christopher Cameronfdc0f872017-11-09 03:22:16226 ? ColorSpace::CreateSRGB()
Christopher Cameron43f17052017-11-11 01:52:01227 : ColorSpace::CreateCustom(internals_->to_XYZD50_,
228 internals_->transfer_fn_);
229 if (internals_->is_parametric_)
230 color_space.icc_profile_id_ = internals_->id_;
Christopher Cameronfdc0f872017-11-09 03:22:16231 return color_space;
ccameron24c87c32017-03-14 21:50:42232}
233
ccameron3b6fe6d2017-02-17 06:21:58234// static
235bool ICCProfile::FromId(uint64_t id,
ccameron3b6fe6d2017-02-17 06:21:58236 ICCProfile* icc_profile) {
Christopher Cameron43f17052017-11-11 01:52:01237 base::AutoLock lock(g_lock.Get());
238
239 auto found = g_cache.Get().Get(id);
240 if (found != g_cache.Get().end()) {
241 *icc_profile = found->second;
242 return true;
243 }
244 *icc_profile = ICCProfile();
245 return false;
ccameron3b6fe6d2017-02-17 06:21:58246}
247
Christopher Cameron43f17052017-11-11 01:52:01248void ICCProfile::TouchCacheEntry() const {
249 if (!internals_)
250 return;
251 base::AutoLock lock(g_lock.Get());
252
253 // Query for an existing cache entry.
254 auto found = g_cache.Get().Get(internals_->id_);
255 if (found != g_cache.Get().end())
256 return;
257
258 // Check if there are any cache entries with the same data, and refuse to add
259 // a duplicate entry with the same data but a different id.
260 // TODO(ccameron): This is a bit odd, but this preserves existing behavior.
261 for (const auto& iter : g_cache.Get()) {
262 const ICCProfile& iter_profile = iter.second;
263 if (internals_->data_ == iter_profile.internals_->data_)
264 return;
265 }
266
267 // Insert a new cache entry if none existed.
268 g_cache.Get().Put(internals_->id_, *this);
269}
270
271ICCProfile::Internals::Internals(std::vector<char> data, uint64_t id)
272 : id_(id), data_(std::move(data)) {
Christopher Cameron374b6c42017-08-31 22:21:23273 // Early out for empty entries.
274 if (data_.empty())
ccameron549738b2017-01-28 17:39:32275 return;
suzyhc3de81a2017-01-27 00:28:10276
Christopher Camerona4adb41b2017-07-13 06:44:25277 // Parse the ICC profile
Christopher Cameronfdc0f872017-11-09 03:22:16278 analyze_result_ = Initialize();
ccamerone35e2322017-11-02 23:33:46279 switch (analyze_result_) {
280 case kICCExtractedSRGBColorSpace:
281 case kICCExtractedMatrixAndAnalyticTrFn:
282 case kICCExtractedMatrixAndApproximatedTrFn:
283 // Successfully and accurately extracted color space.
Christopher Cameronfdc0f872017-11-09 03:22:16284 is_valid_ = true;
285 is_parametric_ = true;
ccamerone35e2322017-11-02 23:33:46286 break;
ccamerone35e2322017-11-02 23:33:46287 case kICCFailedToConvergeToApproximateTrFn:
288 case kICCFailedToApproximateTrFnAccurately:
289 // Successfully but extracted a color space, but it isn't accurate enough.
Christopher Cameronfdc0f872017-11-09 03:22:16290 is_valid_ = true;
291 is_parametric_ = false;
ccamerone35e2322017-11-02 23:33:46292 break;
Christopher Cameronf02091992017-11-09 23:12:22293 case kICCFailedToExtractRawTrFn:
294 case kICCFailedToExtractMatrix:
ccamerone35e2322017-11-02 23:33:46295 case kICCFailedToParse:
296 case kICCFailedToExtractSkColorSpace:
297 case kICCFailedToCreateXform:
298 // Can't even use this color space as a LUT.
Christopher Cameronfdc0f872017-11-09 03:22:16299 is_valid_ = false;
300 is_parametric_ = false;
ccamerone35e2322017-11-02 23:33:46301 break;
302 }
Christopher Cameron374b6c42017-08-31 22:21:23303}
304
Christopher Cameron43f17052017-11-11 01:52:01305ICCProfile::Internals::~Internals() {}
306
Christopher Cameron374b6c42017-08-31 22:21:23307void ICCProfile::HistogramDisplay(int64_t display_id) const {
Christopher Cameron43f17052017-11-11 01:52:01308 if (!internals_)
Christopher Cameron374b6c42017-08-31 22:21:23309 return;
Christopher Cameron43f17052017-11-11 01:52:01310 internals_->HistogramDisplay(display_id);
311}
312
313void ICCProfile::Internals::HistogramDisplay(int64_t display_id) {
314 // Ensure that we histogram this profile only once per display id.
315 if (histogrammed_display_ids_.count(display_id))
316 return;
317 histogrammed_display_ids_.insert(display_id);
Christopher Cameron374b6c42017-08-31 22:21:23318
319 UMA_HISTOGRAM_ENUMERATION("Blink.ColorSpace.Destination.ICCResult",
320 analyze_result_, kICCProfileAnalyzeLast);
321
322 // Add histograms for numerical approximation.
323 bool nonlinear_fit_converged =
324 analyze_result_ == kICCExtractedMatrixAndApproximatedTrFn ||
325 analyze_result_ == kICCFailedToApproximateTrFnAccurately;
326 bool nonlinear_fit_did_not_converge =
327 analyze_result_ == kICCFailedToConvergeToApproximateTrFn;
328 if (nonlinear_fit_converged || nonlinear_fit_did_not_converge) {
329 UMA_HISTOGRAM_BOOLEAN("Blink.ColorSpace.Destination.NonlinearFitConverged",
330 nonlinear_fit_converged);
331 }
332 if (nonlinear_fit_converged) {
333 UMA_HISTOGRAM_CUSTOM_COUNTS(
334 "Blink.ColorSpace.Destination.NonlinearFitError",
Christopher Cameronfdc0f872017-11-09 03:22:16335 static_cast<int>(transfer_fn_error_ * 255), 0, 127, 16);
ccameron549738b2017-01-28 17:39:32336 }
ccameronefdab162016-07-25 23:00:02337}
338
ccameronefdab162016-07-25 23:00:02339} // namespace gfx