blob: 6f589f3c6dd23252918884ce9bb4114614bf76d6 [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
Christopher Cameron374b6c42017-08-31 22:21:2321namespace {
22
Christopher Camerondeea2522017-11-16 07:10:5223// An MRU cache mapping ColorSpace objects to the ICCProfile that created them.
24// This cache serves two purposes.
25// Purpose 1: LUT-based color transforms.
26// For color profiles that cannot be represented analytically, this can be
27// used to look up ICCProfile that created a ColorSpace, so that its
28// SkColorSpace can be used to generate a LUT for a ColorTransform.
29// Purpose 2: Specify color profiles to IOSurfaces on Mac.
30// On Mac, IOSurfaces specify their output color space by raw ICC profile
31// data. If the IOSurface ICC profile does not exactly match the output
32// monitor's ICC profile, there is a substantial power cost. This structure
33// allows us to retrieve the exact ICC profile data that produced a given
34// ColorSpace.
35using ProfileCacheBase = base::MRUCache<ColorSpace, ICCProfile>;
Christopher Cameron43f17052017-11-11 01:52:0136class ProfileCache : public ProfileCacheBase {
37 public:
38 static const size_t kMaxCachedICCProfiles = 16;
39 ProfileCache() : ProfileCacheBase(kMaxCachedICCProfiles) {}
40};
41base::LazyInstance<ProfileCache>::DestructorAtExit g_cache =
42 LAZY_INSTANCE_INITIALIZER;
43
Christopher Camerondeea2522017-11-16 07:10:5244// The next id to assign to a color profile.
45uint64_t g_next_unused_id = 1;
46
Christopher Cameron43f17052017-11-11 01:52:0147// Lock that must be held to access |g_cache| and |g_next_unused_id|.
48base::LazyInstance<base::Lock>::DestructorAtExit g_lock =
scottmg5e65e3a2017-03-08 08:48:4649 LAZY_INSTANCE_INITIALIZER;
ccameronefdab162016-07-25 23:00:0250
Christopher Cameron374b6c42017-08-31 22:21:2351} // namespace
52
Christopher Cameron43f17052017-11-11 01:52:0153ICCProfile::Internals::AnalyzeResult ICCProfile::Internals::Initialize() {
Christopher Cameronfdc0f872017-11-09 03:22:1654 // Start out with no parametric data.
Christopher Camerona4adb41b2017-07-13 06:44:2555
56 // Parse the profile and attempt to create a SkColorSpaceXform out of it.
57 sk_sp<SkColorSpace> sk_srgb_color_space = SkColorSpace::MakeSRGB();
Christopher Cameronfdc0f872017-11-09 03:22:1658 sk_sp<SkICC> sk_icc = SkICC::Make(data_.data(), data_.size());
Christopher Camerona4adb41b2017-07-13 06:44:2559 if (!sk_icc) {
60 DLOG(ERROR) << "Failed to parse ICC profile to SkICC.";
Christopher Cameron374b6c42017-08-31 22:21:2361 return kICCFailedToParse;
Christopher Camerona4adb41b2017-07-13 06:44:2562 }
Christopher Cameronfdc0f872017-11-09 03:22:1663 sk_color_space_ = SkColorSpace::MakeICC(data_.data(), data_.size());
64 if (!sk_color_space_) {
Christopher Camerona4adb41b2017-07-13 06:44:2565 DLOG(ERROR) << "Failed to parse ICC profile to SkColorSpace.";
Christopher Cameron374b6c42017-08-31 22:21:2366 return kICCFailedToExtractSkColorSpace;
Christopher Camerona4adb41b2017-07-13 06:44:2567 }
68 std::unique_ptr<SkColorSpaceXform> sk_color_space_xform =
Christopher Cameronfdc0f872017-11-09 03:22:1669 SkColorSpaceXform::New(sk_srgb_color_space.get(), sk_color_space_.get());
Christopher Camerona4adb41b2017-07-13 06:44:2570 if (!sk_color_space_xform) {
71 DLOG(ERROR) << "Parsed ICC profile but can't create SkColorSpaceXform.";
Christopher Cameron374b6c42017-08-31 22:21:2372 return kICCFailedToCreateXform;
Christopher Camerona4adb41b2017-07-13 06:44:2573 }
74
Christopher Cameronfdc0f872017-11-09 03:22:1675 // Because this SkColorSpace can be used to construct a transform, we can use
76 // it to create a LUT based color transform, at the very least. If we fail to
77 // get any better approximation, we'll use sRGB as our approximation.
78 ColorSpace::CreateSRGB().GetPrimaryMatrix(&to_XYZD50_);
79 ColorSpace::CreateSRGB().GetTransferFunction(&transfer_fn_);
Christopher Camerona4adb41b2017-07-13 06:44:2580
ccamerone35e2322017-11-02 23:33:4681 // If our SkColorSpace representation is sRGB then return that.
Christopher Cameronfdc0f872017-11-09 03:22:1682 if (sk_color_space_->isSRGB())
ccamerone35e2322017-11-02 23:33:4683 return kICCExtractedSRGBColorSpace;
ccamerone35e2322017-11-02 23:33:4684
Christopher Cameronfdc0f872017-11-09 03:22:1685 // A primary matrix is required for our parametric representations. Use it if
86 // it exists.
Christopher Camerona4adb41b2017-07-13 06:44:2587 SkMatrix44 to_XYZD50_matrix;
88 if (!sk_icc->toXYZD50(&to_XYZD50_matrix)) {
89 DLOG(ERROR) << "Failed to extract ICC profile primary matrix.";
Christopher Cameron374b6c42017-08-31 22:21:2390 return kICCFailedToExtractMatrix;
Christopher Camerona4adb41b2017-07-13 06:44:2591 }
Christopher Cameronfdc0f872017-11-09 03:22:1692 to_XYZD50_ = to_XYZD50_matrix;
Christopher Camerona4adb41b2017-07-13 06:44:2593
Christopher Cameronfdc0f872017-11-09 03:22:1694 // Try to directly extract a numerical transfer function. Use it if it
95 // exists.
Christopher Camerona4adb41b2017-07-13 06:44:2596 SkColorSpaceTransferFn exact_tr_fn;
97 if (sk_icc->isNumericalTransferFn(&exact_tr_fn)) {
Christopher Cameronfdc0f872017-11-09 03:22:1698 transfer_fn_ = exact_tr_fn;
Christopher Cameron374b6c42017-08-31 22:21:2399 return kICCExtractedMatrixAndAnalyticTrFn;
Christopher Camerona4adb41b2017-07-13 06:44:25100 }
101
Christopher Cameron374b6c42017-08-31 22:21:23102 // Attempt to fit a parametric transfer function to the table data in the
103 // profile.
104 SkColorSpaceTransferFn approx_tr_fn;
Christopher Cameronfdc0f872017-11-09 03:22:16105 if (!SkApproximateTransferFn(sk_icc, &transfer_fn_error_, &approx_tr_fn)) {
Christopher Cameron374b6c42017-08-31 22:21:23106 DLOG(ERROR) << "Failed approximate transfer function.";
107 return kICCFailedToConvergeToApproximateTrFn;
108 }
109
110 // If this converged, but has too high error, use the sRGB transfer function
111 // from above.
112 const float kMaxError = 2.f / 256.f;
Christopher Cameronfdc0f872017-11-09 03:22:16113 if (transfer_fn_error_ >= kMaxError) {
Christopher Cameron374b6c42017-08-31 22:21:23114 DLOG(ERROR) << "Failed to accurately approximate transfer function, error: "
Christopher Cameronfdc0f872017-11-09 03:22:16115 << 256.f * transfer_fn_error_ << "/256";
Christopher Cameron374b6c42017-08-31 22:21:23116 return kICCFailedToApproximateTrFnAccurately;
117 };
118
119 // If the error is sufficiently low, declare that the approximation is
120 // accurate.
Christopher Cameronfdc0f872017-11-09 03:22:16121 transfer_fn_ = approx_tr_fn;
Christopher Cameron374b6c42017-08-31 22:21:23122 return kICCExtractedMatrixAndApproximatedTrFn;
123}
Reilly Grantc46032eb2017-08-30 22:53:52124
ccameronefdab162016-07-25 23:00:02125ICCProfile::ICCProfile() = default;
126ICCProfile::ICCProfile(ICCProfile&& other) = default;
127ICCProfile::ICCProfile(const ICCProfile& other) = default;
128ICCProfile& ICCProfile::operator=(ICCProfile&& other) = default;
129ICCProfile& ICCProfile::operator=(const ICCProfile& other) = default;
130ICCProfile::~ICCProfile() = default;
131
132bool ICCProfile::operator==(const ICCProfile& other) const {
Christopher Cameron43f17052017-11-11 01:52:01133 if (!internals_ && !other.internals_)
134 return true;
Christopher Camerondeea2522017-11-16 07:10:52135 if (internals_ && other.internals_) {
136 return internals_->data_ == other.internals_->data_ &&
137 internals_->id_ == other.internals_->id_;
138 }
Christopher Cameron43f17052017-11-11 01:52:01139 return false;
ccameronefdab162016-07-25 23:00:02140}
141
ccameronc21ca23b2017-01-20 03:34:01142bool ICCProfile::operator!=(const ICCProfile& other) const {
143 return !(*this == other);
144}
145
Christopher Cameron43f17052017-11-11 01:52:01146bool ICCProfile::IsValid() const {
147 return internals_ ? internals_->is_valid_ : false;
148}
149
ccameronefdab162016-07-25 23:00:02150// static
ccameron549738b2017-01-28 17:39:32151ICCProfile ICCProfile::FromData(const void* data, size_t size) {
ccameron9b2a0b1b2017-02-02 23:29:14152 return FromDataWithId(data, size, 0);
153}
154
155// static
Christopher Cameron43f17052017-11-11 01:52:01156ICCProfile ICCProfile::FromDataWithId(const void* data_as_void,
ccameron9b2a0b1b2017-02-02 23:29:14157 size_t size,
158 uint64_t new_profile_id) {
Christopher Cameron43f17052017-11-11 01:52:01159 const char* data_as_byte = reinterpret_cast<const char*>(data_as_void);
160 std::vector<char> new_profile_data(data_as_byte, data_as_byte + size);
Reilly Grantc46032eb2017-08-30 22:53:52161
Christopher Cameron43f17052017-11-11 01:52:01162 base::AutoLock lock(g_lock.Get());
ccameronefdab162016-07-25 23:00:02163
Christopher Cameron43f17052017-11-11 01:52:01164 // See if there is already an entry with the same data. If so, return that
Christopher Camerondeea2522017-11-16 07:10:52165 // entry.
Christopher Cameron43f17052017-11-11 01:52:01166 for (const auto& iter : g_cache.Get()) {
167 const ICCProfile& iter_profile = iter.second;
168 if (new_profile_data == iter_profile.internals_->data_)
169 return iter_profile;
170 }
171
Christopher Camerondeea2522017-11-16 07:10:52172 scoped_refptr<Internals> internals = base::MakeRefCounted<Internals>(
Christopher Cameron43f17052017-11-11 01:52:01173 std::move(new_profile_data), new_profile_id);
Christopher Camerondeea2522017-11-16 07:10:52174
175 ICCProfile new_profile;
176 new_profile.internals_ = internals;
177
178 g_cache.Get().Put(new_profile.GetColorSpace(), new_profile);
179 if (internals->is_parametric_)
180 g_cache.Get().Put(new_profile.GetParametricColorSpace(), new_profile);
Christopher Cameron43f17052017-11-11 01:52:01181 return new_profile;
ccameronefdab162016-07-25 23:00:02182}
183
Christopher Cameronfdc0f872017-11-09 03:22:16184ColorSpace ICCProfile::GetColorSpace() const {
Christopher Cameron43f17052017-11-11 01:52:01185 if (!internals_)
186 return ColorSpace();
Christopher Cameronfdc0f872017-11-09 03:22:16187
Christopher Cameron43f17052017-11-11 01:52:01188 if (!internals_->is_valid_)
Christopher Cameronfdc0f872017-11-09 03:22:16189 return ColorSpace();
190
191 gfx::ColorSpace color_space;
Christopher Cameron43f17052017-11-11 01:52:01192 if (internals_->is_parametric_) {
Christopher Cameronfdc0f872017-11-09 03:22:16193 color_space = GetParametricColorSpace();
Christopher Cameronfdc0f872017-11-09 03:22:16194 } else {
Christopher Camerondeea2522017-11-16 07:10:52195 // TODO(ccameron): Compute a reasonable approximation instead of always
196 // falling back to sRGB.
197 color_space = ColorSpace::CreateCustom(
198 internals_->to_XYZD50_, ColorSpace::TransferID::IEC61966_2_1);
Christopher Cameron43f17052017-11-11 01:52:01199 color_space.icc_profile_id_ = internals_->id_;
Christopher Cameronfdc0f872017-11-09 03:22:16200 }
201 return color_space;
ccamerone35e2322017-11-02 23:33:46202}
suzyhc3de81a2017-01-27 00:28:10203
Christopher Cameronfdc0f872017-11-09 03:22:16204ColorSpace ICCProfile::GetParametricColorSpace() const {
Christopher Cameron43f17052017-11-11 01:52:01205 if (!internals_)
206 return ColorSpace();
Christopher Cameronfdc0f872017-11-09 03:22:16207
Christopher Cameron43f17052017-11-11 01:52:01208 if (!internals_->is_valid_)
Christopher Cameronfdc0f872017-11-09 03:22:16209 return ColorSpace();
210
211 ColorSpace color_space =
Christopher Cameron43f17052017-11-11 01:52:01212 internals_->sk_color_space_->isSRGB()
Christopher Cameronfdc0f872017-11-09 03:22:16213 ? ColorSpace::CreateSRGB()
Christopher Cameron43f17052017-11-11 01:52:01214 : ColorSpace::CreateCustom(internals_->to_XYZD50_,
215 internals_->transfer_fn_);
216 if (internals_->is_parametric_)
217 color_space.icc_profile_id_ = internals_->id_;
Christopher Cameronfdc0f872017-11-09 03:22:16218 return color_space;
ccameron24c87c32017-03-14 21:50:42219}
220
Christopher Camerondeea2522017-11-16 07:10:52221// TODO(ccameron): Change this to ICCProfile::FromColorSpace.
222bool ColorSpace::GetICCProfile(ICCProfile* icc_profile) const {
223 if (!IsValid()) {
224 DLOG(ERROR) << "Cannot fetch ICCProfile for invalid space.";
225 return false;
Christopher Cameron43f17052017-11-11 01:52:01226 }
Christopher Camerondeea2522017-11-16 07:10:52227 if (matrix_ != MatrixID::RGB) {
228 DLOG(ERROR) << "Not creating non-RGB ICCProfile";
229 return false;
230 }
231 if (range_ != RangeID::FULL) {
232 DLOG(ERROR) << "Not creating non-full-range ICCProfile";
233 return false;
Christopher Cameron43f17052017-11-11 01:52:01234 }
235
Christopher Camerondeea2522017-11-16 07:10:52236 // Check for an entry in the cache for this color space.
237 {
238 base::AutoLock lock(g_lock.Get());
239 auto found = g_cache.Get().Get(*this);
240 if (found != g_cache.Get().end()) {
241 *icc_profile = found->second;
242 return true;
243 }
244 // If this was an ICC based profile and we don't have the original profile,
245 // fall through to using the inaccurate approximation.
246 if (icc_profile_id_) {
247 DLOG(ERROR) << "Failed to find id-based ColorSpace in ICCProfile cache";
248 return false;
249 }
250 }
251
252 // Otherwise, construct an ICC profile based on the best approximated
253 // primaries and matrix.
254 SkMatrix44 to_XYZD50_matrix;
255 GetPrimaryMatrix(&to_XYZD50_matrix);
256 SkColorSpaceTransferFn fn;
257 if (!GetTransferFunction(&fn)) {
258 DLOG(ERROR) << "Failed to get ColorSpace transfer function for ICCProfile.";
259 return false;
260 }
261 sk_sp<SkData> data = SkICC::WriteToICC(fn, to_XYZD50_matrix);
262 if (!data) {
263 DLOG(ERROR) << "Failed to create SkICC.";
264 return false;
265 }
266 *icc_profile = ICCProfile::FromDataWithId(data->data(), data->size(), 0);
267 DCHECK(icc_profile->IsValid());
268 return true;
Christopher Cameron43f17052017-11-11 01:52:01269}
270
271ICCProfile::Internals::Internals(std::vector<char> data, uint64_t id)
Christopher Camerondeea2522017-11-16 07:10:52272 : data_(std::move(data)), id_(id) {
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 Camerondeea2522017-11-16 07:10:52303
304 if (id_) {
305 // If |id_| has been set here, then it was specified via sending an
306 // ICCProfile over IPC. Ensure that the computation of |is_valid_| and
307 // |is_parametric_| match the analysis done in the sending process.
308 DCHECK(is_valid_ && !is_parametric_);
309 } else {
310 // If this profile is not parametric, assign it an id so that we can look it
311 // up from a ColorSpace. This path should only be hit in the browser
312 // process.
313 if (is_valid_ && !is_parametric_) {
314 id_ = g_next_unused_id++;
315 }
316 }
Christopher Cameron374b6c42017-08-31 22:21:23317}
318
Christopher Cameron43f17052017-11-11 01:52:01319ICCProfile::Internals::~Internals() {}
320
Christopher Cameron374b6c42017-08-31 22:21:23321void ICCProfile::HistogramDisplay(int64_t display_id) const {
Christopher Cameron43f17052017-11-11 01:52:01322 if (!internals_)
Christopher Cameron374b6c42017-08-31 22:21:23323 return;
Christopher Cameron43f17052017-11-11 01:52:01324 internals_->HistogramDisplay(display_id);
325}
326
327void ICCProfile::Internals::HistogramDisplay(int64_t display_id) {
328 // Ensure that we histogram this profile only once per display id.
329 if (histogrammed_display_ids_.count(display_id))
330 return;
331 histogrammed_display_ids_.insert(display_id);
Christopher Cameron374b6c42017-08-31 22:21:23332
333 UMA_HISTOGRAM_ENUMERATION("Blink.ColorSpace.Destination.ICCResult",
334 analyze_result_, kICCProfileAnalyzeLast);
335
336 // Add histograms for numerical approximation.
337 bool nonlinear_fit_converged =
338 analyze_result_ == kICCExtractedMatrixAndApproximatedTrFn ||
339 analyze_result_ == kICCFailedToApproximateTrFnAccurately;
340 bool nonlinear_fit_did_not_converge =
341 analyze_result_ == kICCFailedToConvergeToApproximateTrFn;
342 if (nonlinear_fit_converged || nonlinear_fit_did_not_converge) {
343 UMA_HISTOGRAM_BOOLEAN("Blink.ColorSpace.Destination.NonlinearFitConverged",
344 nonlinear_fit_converged);
345 }
346 if (nonlinear_fit_converged) {
347 UMA_HISTOGRAM_CUSTOM_COUNTS(
348 "Blink.ColorSpace.Destination.NonlinearFitError",
Christopher Cameronfdc0f872017-11-09 03:22:16349 static_cast<int>(transfer_fn_error_ * 255), 0, 127, 16);
ccameron549738b2017-01-28 17:39:32350 }
ccameronefdab162016-07-25 23:00:02351}
352
ccameronefdab162016-07-25 23:00:02353} // namespace gfx