blob: 4443d7a9bcd82e746c0d0c2ec2f1c092df214f6c [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"
Brian Osman37233e32018-05-17 16:53:1716#include "third_party/skia/third_party/skcms/skcms.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 Cameron3bc3d5e2017-11-23 03:48:1123static const size_t kMaxCachedICCProfiles = 16;
24
Christopher Camerondeea2522017-11-16 07:10:5225// An MRU cache mapping ColorSpace objects to the ICCProfile that created them.
Christopher Cameron3bc3d5e2017-11-23 03:48:1126// This cache is necessary only on macOS, for power consumption reasons. In
27// particular:
28// * IOSurfaces specify their output color space by raw ICC profile data.
29// * If the IOSurface ICC profile does not exactly match the output monitor's
30// ICC profile, there is a substantial power cost.
31// * This structure allows us to retrieve the exact ICC profile data that
32// produced a given ColorSpace.
33using SpaceToProfileCacheBase = base::MRUCache<ColorSpace, ICCProfile>;
34class SpaceToProfileCache : public SpaceToProfileCacheBase {
Christopher Cameron43f17052017-11-11 01:52:0135 public:
Christopher Cameron3bc3d5e2017-11-23 03:48:1136 SpaceToProfileCache() : SpaceToProfileCacheBase(kMaxCachedICCProfiles) {}
Christopher Cameron43f17052017-11-11 01:52:0137};
Christopher Camerona9b81c0b132017-12-21 00:23:5438base::LazyInstance<SpaceToProfileCache>::Leaky g_space_to_profile_cache_mac =
39 LAZY_INSTANCE_INITIALIZER;
Christopher Cameron3bc3d5e2017-11-23 03:48:1140
41// An MRU cache mapping data to ICCProfile objects, to avoid re-parsing
42// profiles every time they are read.
43using DataToProfileCacheBase = base::MRUCache<std::vector<char>, ICCProfile>;
44class DataToProfileCache : public DataToProfileCacheBase {
45 public:
46 DataToProfileCache() : DataToProfileCacheBase(kMaxCachedICCProfiles) {}
47};
Christopher Camerona9b81c0b132017-12-21 00:23:5448base::LazyInstance<DataToProfileCache>::Leaky g_data_to_profile_cache =
49 LAZY_INSTANCE_INITIALIZER;
Christopher Cameron3bc3d5e2017-11-23 03:48:1150
51// An MRU cache mapping IDs to ICCProfile objects. This is necessary for
52// constructing LUT-based color transforms. In particular, it is used to look
53// up the SkColorSpace for ColorSpace objects that are not parametric, so that
54// that SkColorSpace may be used to construct the LUT.
55using IdToProfileCacheBase = base::MRUCache<uint64_t, ICCProfile>;
56class IdToProfileCache : public IdToProfileCacheBase {
57 public:
58 IdToProfileCache() : IdToProfileCacheBase(kMaxCachedICCProfiles) {}
59};
Christopher Camerona9b81c0b132017-12-21 00:23:5460base::LazyInstance<IdToProfileCache>::Leaky g_id_to_profile_cache =
Christopher Cameron43f17052017-11-11 01:52:0161 LAZY_INSTANCE_INITIALIZER;
62
Christopher Camerondeea2522017-11-16 07:10:5263// The next id to assign to a color profile.
64uint64_t g_next_unused_id = 1;
65
Christopher Cameron3bc3d5e2017-11-23 03:48:1166// Lock that must be held to access |g_space_to_profile_cache_mac| and
67// |g_next_unused_id|.
Daniel Bratellb6c0ab8c2017-12-21 13:09:1868base::LazyInstance<base::Lock>::Leaky g_icc_profile_lock =
69 LAZY_INSTANCE_INITIALIZER;
ccameronefdab162016-07-25 23:00:0270
Christopher Cameron374b6c42017-08-31 22:21:2371} // namespace
72
Christopher Cameron43f17052017-11-11 01:52:0173ICCProfile::Internals::AnalyzeResult ICCProfile::Internals::Initialize() {
Christopher Cameronfdc0f872017-11-09 03:22:1674 // Start out with no parametric data.
Christopher Cameron3bc3d5e2017-11-23 03:48:1175 if (data_.empty())
76 return kICCNoProfile;
Christopher Camerona4adb41b2017-07-13 06:44:2577
Brian Osman37233e32018-05-17 16:53:1778 // Parse the profile.
79 skcms_ICCProfile profile;
80 if (!skcms_Parse(data_.data(), data_.size(), &profile)) {
81 DLOG(ERROR) << "Failed to parse ICC profile.";
Christopher Cameron374b6c42017-08-31 22:21:2382 return kICCFailedToParse;
Christopher Camerona4adb41b2017-07-13 06:44:2583 }
Brian Osman37233e32018-05-17 16:53:1784
85 // Coerce it into a rasterization destination (if possible). If the profile
86 // can't be approximated accurately, skcms will not allow transforming to it,
87 // and this will fail.
88 if (!skcms_MakeUsableAsDestinationWithSingleCurve(&profile)) {
89 DLOG(ERROR) << "Parsed ICC profile but can't make usable as destination.";
90 return kICCFailedToMakeUsable;
Christopher Camerona4adb41b2017-07-13 06:44:2591 }
92
Brian Osman37233e32018-05-17 16:53:1793 // Create an SkColorSpace from the profile. This should always succeed after
94 // calling MakeUsableAsDestinationWithSingleCurve.
95 sk_color_space_ = SkColorSpace::Make(profile);
96 DCHECK(sk_color_space_);
Christopher Camerona4adb41b2017-07-13 06:44:2597
Brian Osman37233e32018-05-17 16:53:1798 // Extract the primary matrix and transfer function
99 to_XYZD50_.set3x3RowMajorf(&profile.toXYZD50.vals[0][0]);
100 memcpy(&transfer_fn_, &profile.trc[0].parametric, sizeof(transfer_fn_));
ccamerone35e2322017-11-02 23:33:46101
Brian Osman37233e32018-05-17 16:53:17102 // 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 return kICCExtractedMatrixAndTrFn;
Christopher Cameron374b6c42017-08-31 22:21:23108}
Reilly Grantc46032eb2017-08-30 22:53:52109
ccameronefdab162016-07-25 23:00:02110ICCProfile::ICCProfile() = default;
111ICCProfile::ICCProfile(ICCProfile&& other) = default;
112ICCProfile::ICCProfile(const ICCProfile& other) = default;
113ICCProfile& ICCProfile::operator=(ICCProfile&& other) = default;
114ICCProfile& ICCProfile::operator=(const ICCProfile& other) = default;
115ICCProfile::~ICCProfile() = default;
116
117bool ICCProfile::operator==(const ICCProfile& other) const {
Christopher Cameron43f17052017-11-11 01:52:01118 if (!internals_ && !other.internals_)
119 return true;
Christopher Camerondeea2522017-11-16 07:10:52120 if (internals_ && other.internals_) {
121 return internals_->data_ == other.internals_->data_ &&
122 internals_->id_ == other.internals_->id_;
123 }
Christopher Cameron43f17052017-11-11 01:52:01124 return false;
ccameronefdab162016-07-25 23:00:02125}
126
ccameronc21ca23b2017-01-20 03:34:01127bool ICCProfile::operator!=(const ICCProfile& other) const {
128 return !(*this == other);
129}
130
Christopher Cameron43f17052017-11-11 01:52:01131bool ICCProfile::IsValid() const {
132 return internals_ ? internals_->is_valid_ : false;
133}
134
Christopher Cameron3bc3d5e2017-11-23 03:48:11135std::vector<char> ICCProfile::GetData() const {
136 return internals_ ? internals_->data_ : std::vector<char>();
137}
138
ccameronefdab162016-07-25 23:00:02139// static
ccameron549738b2017-01-28 17:39:32140ICCProfile ICCProfile::FromData(const void* data, size_t size) {
ccameron9b2a0b1b2017-02-02 23:29:14141 return FromDataWithId(data, size, 0);
142}
143
144// static
Christopher Cameron43f17052017-11-11 01:52:01145ICCProfile ICCProfile::FromDataWithId(const void* data_as_void,
ccameron9b2a0b1b2017-02-02 23:29:14146 size_t size,
147 uint64_t new_profile_id) {
Christopher Cameron43f17052017-11-11 01:52:01148 const char* data_as_byte = reinterpret_cast<const char*>(data_as_void);
Christopher Cameron3bc3d5e2017-11-23 03:48:11149 std::vector<char> data(data_as_byte, data_as_byte + size);
Reilly Grantc46032eb2017-08-30 22:53:52150
Daniel Bratellb6c0ab8c2017-12-21 13:09:18151 base::AutoLock lock(g_icc_profile_lock.Get());
ccameronefdab162016-07-25 23:00:02152
Christopher Cameron43f17052017-11-11 01:52:01153 // See if there is already an entry with the same data. If so, return that
Christopher Cameron3bc3d5e2017-11-23 03:48:11154 // entry. If not, parse the data.
155 ICCProfile icc_profile;
156 auto found_by_data = g_data_to_profile_cache.Get().Get(data);
157 if (found_by_data != g_data_to_profile_cache.Get().end()) {
158 icc_profile = found_by_data->second;
159 } else {
160 icc_profile.internals_ =
161 base::MakeRefCounted<Internals>(std::move(data), new_profile_id);
Christopher Cameron43f17052017-11-11 01:52:01162 }
163
Christopher Cameron3bc3d5e2017-11-23 03:48:11164 // Insert the profile into all caches.
165 ColorSpace color_space = icc_profile.GetColorSpace();
166 if (color_space.IsValid())
167 g_space_to_profile_cache_mac.Get().Put(color_space, icc_profile);
168 if (icc_profile.internals_->id_)
169 g_id_to_profile_cache.Get().Put(icc_profile.internals_->id_, icc_profile);
170 g_data_to_profile_cache.Get().Put(icc_profile.internals_->data_, icc_profile);
Christopher Camerondeea2522017-11-16 07:10:52171
Christopher Cameron3bc3d5e2017-11-23 03:48:11172 return icc_profile;
ccameronefdab162016-07-25 23:00:02173}
174
Christopher Cameronfdc0f872017-11-09 03:22:16175ColorSpace ICCProfile::GetColorSpace() const {
Christopher Cameron43f17052017-11-11 01:52:01176 if (!internals_)
177 return ColorSpace();
Christopher Cameronfdc0f872017-11-09 03:22:16178
Christopher Cameron43f17052017-11-11 01:52:01179 if (!internals_->is_valid_)
Christopher Cameronfdc0f872017-11-09 03:22:16180 return ColorSpace();
181
Christopher Cameron3bc3d5e2017-11-23 03:48:11182 // TODO(ccameron): Compute a reasonable approximation instead of always
183 // falling back to sRGB.
Christopher Cameronfdc0f872017-11-09 03:22:16184 ColorSpace color_space =
Christopher Cameron43f17052017-11-11 01:52:01185 internals_->sk_color_space_->isSRGB()
Christopher Cameronfdc0f872017-11-09 03:22:16186 ? ColorSpace::CreateSRGB()
Christopher Cameron43f17052017-11-11 01:52:01187 : ColorSpace::CreateCustom(internals_->to_XYZD50_,
188 internals_->transfer_fn_);
Brian Osman8c2c27cb2018-04-23 22:50:09189 color_space.icc_profile_id_ = internals_->id_;
Christopher Cameronfdc0f872017-11-09 03:22:16190 return color_space;
ccameron24c87c32017-03-14 21:50:42191}
192
Christopher Cameron3bc3d5e2017-11-23 03:48:11193// static
194ICCProfile ICCProfile::FromParametricColorSpace(const ColorSpace& color_space) {
195 if (!color_space.IsValid()) {
196 return ICCProfile();
Christopher Cameron43f17052017-11-11 01:52:01197 }
Christopher Cameron3bc3d5e2017-11-23 03:48:11198 if (color_space.matrix_ != ColorSpace::MatrixID::RGB) {
Christopher Camerondeea2522017-11-16 07:10:52199 DLOG(ERROR) << "Not creating non-RGB ICCProfile";
Christopher Cameron3bc3d5e2017-11-23 03:48:11200 return ICCProfile();
Christopher Camerondeea2522017-11-16 07:10:52201 }
Christopher Cameron3bc3d5e2017-11-23 03:48:11202 if (color_space.range_ != ColorSpace::RangeID::FULL) {
Christopher Camerondeea2522017-11-16 07:10:52203 DLOG(ERROR) << "Not creating non-full-range ICCProfile";
Christopher Cameron3bc3d5e2017-11-23 03:48:11204 return ICCProfile();
205 }
206 if (color_space.icc_profile_id_) {
207 DLOG(ERROR) << "Not creating non-parametric ICCProfile";
208 return ICCProfile();
Christopher Cameron43f17052017-11-11 01:52:01209 }
210
Christopher Camerondeea2522017-11-16 07:10:52211 SkMatrix44 to_XYZD50_matrix;
Christopher Cameron3bc3d5e2017-11-23 03:48:11212 color_space.GetPrimaryMatrix(&to_XYZD50_matrix);
Christopher Camerondeea2522017-11-16 07:10:52213 SkColorSpaceTransferFn fn;
Christopher Cameron3bc3d5e2017-11-23 03:48:11214 if (!color_space.GetTransferFunction(&fn)) {
Christopher Camerondeea2522017-11-16 07:10:52215 DLOG(ERROR) << "Failed to get ColorSpace transfer function for ICCProfile.";
Christopher Cameron3bc3d5e2017-11-23 03:48:11216 return ICCProfile();
Christopher Camerondeea2522017-11-16 07:10:52217 }
218 sk_sp<SkData> data = SkICC::WriteToICC(fn, to_XYZD50_matrix);
219 if (!data) {
220 DLOG(ERROR) << "Failed to create SkICC.";
Christopher Cameron3bc3d5e2017-11-23 03:48:11221 return ICCProfile();
Christopher Camerondeea2522017-11-16 07:10:52222 }
Christopher Cameron3bc3d5e2017-11-23 03:48:11223 return FromDataWithId(data->data(), data->size(), 0);
224}
225
226// static
227ICCProfile ICCProfile::FromCacheMac(const ColorSpace& color_space) {
Daniel Bratellb6c0ab8c2017-12-21 13:09:18228 base::AutoLock lock(g_icc_profile_lock.Get());
Christopher Cameron3bc3d5e2017-11-23 03:48:11229 auto found_by_space = g_space_to_profile_cache_mac.Get().Get(color_space);
230 if (found_by_space != g_space_to_profile_cache_mac.Get().end())
231 return found_by_space->second;
232
233 if (color_space.icc_profile_id_) {
234 DLOG(ERROR) << "Failed to find id-based ColorSpace in ICCProfile cache";
235 }
236 return ICCProfile();
237}
238
239// static
240sk_sp<SkColorSpace> ICCProfile::GetSkColorSpaceFromId(uint64_t id) {
Daniel Bratellb6c0ab8c2017-12-21 13:09:18241 base::AutoLock lock(g_icc_profile_lock.Get());
Christopher Cameron3bc3d5e2017-11-23 03:48:11242 auto found = g_id_to_profile_cache.Get().Get(id);
243 if (found == g_id_to_profile_cache.Get().end()) {
244 DLOG(ERROR) << "Failed to find ICC profile with SkColorSpace from id.";
245 return nullptr;
246 }
247 return found->second.internals_->sk_color_space_;
Christopher Cameron43f17052017-11-11 01:52:01248}
249
250ICCProfile::Internals::Internals(std::vector<char> data, uint64_t id)
Christopher Camerondeea2522017-11-16 07:10:52251 : data_(std::move(data)), id_(id) {
Christopher Cameron374b6c42017-08-31 22:21:23252 // Early out for empty entries.
253 if (data_.empty())
ccameron549738b2017-01-28 17:39:32254 return;
suzyhc3de81a2017-01-27 00:28:10255
Christopher Camerona4adb41b2017-07-13 06:44:25256 // Parse the ICC profile
Christopher Cameronfdc0f872017-11-09 03:22:16257 analyze_result_ = Initialize();
ccamerone35e2322017-11-02 23:33:46258 switch (analyze_result_) {
Brian Osman37233e32018-05-17 16:53:17259 case kICCExtractedMatrixAndTrFn:
ccamerone35e2322017-11-02 23:33:46260 // Successfully and accurately extracted color space.
Christopher Cameronfdc0f872017-11-09 03:22:16261 is_valid_ = true;
262 is_parametric_ = true;
ccamerone35e2322017-11-02 23:33:46263 break;
ccamerone35e2322017-11-02 23:33:46264 case kICCFailedToParse:
Christopher Cameron3bc3d5e2017-11-23 03:48:11265 case kICCNoProfile:
Brian Osman37233e32018-05-17 16:53:17266 case kICCFailedToMakeUsable:
ccamerone35e2322017-11-02 23:33:46267 // Can't even use this color space as a LUT.
Christopher Cameronfdc0f872017-11-09 03:22:16268 is_valid_ = false;
269 is_parametric_ = false;
ccamerone35e2322017-11-02 23:33:46270 break;
271 }
Christopher Camerondeea2522017-11-16 07:10:52272
273 if (id_) {
274 // If |id_| has been set here, then it was specified via sending an
275 // ICCProfile over IPC. Ensure that the computation of |is_valid_| and
276 // |is_parametric_| match the analysis done in the sending process.
277 DCHECK(is_valid_ && !is_parametric_);
278 } else {
279 // If this profile is not parametric, assign it an id so that we can look it
280 // up from a ColorSpace. This path should only be hit in the browser
281 // process.
282 if (is_valid_ && !is_parametric_) {
283 id_ = g_next_unused_id++;
284 }
285 }
Christopher Cameron374b6c42017-08-31 22:21:23286}
287
Christopher Cameron43f17052017-11-11 01:52:01288ICCProfile::Internals::~Internals() {}
289
Christopher Cameron374b6c42017-08-31 22:21:23290void ICCProfile::HistogramDisplay(int64_t display_id) const {
Christopher Cameron3bc3d5e2017-11-23 03:48:11291 if (!internals_) {
292 // If this is an uninitialized profile, histogram it using an empty profile,
293 // so that we only histogram this display as empty once.
294 FromData(nullptr, 0).HistogramDisplay(display_id);
295 } else {
296 internals_->HistogramDisplay(display_id);
297 }
Christopher Cameron43f17052017-11-11 01:52:01298}
299
300void ICCProfile::Internals::HistogramDisplay(int64_t display_id) {
301 // Ensure that we histogram this profile only once per display id.
302 if (histogrammed_display_ids_.count(display_id))
303 return;
304 histogrammed_display_ids_.insert(display_id);
Christopher Cameron374b6c42017-08-31 22:21:23305
306 UMA_HISTOGRAM_ENUMERATION("Blink.ColorSpace.Destination.ICCResult",
Brian Osman37233e32018-05-17 16:53:17307 analyze_result_);
ccameronefdab162016-07-25 23:00:02308}
309
ccameronefdab162016-07-25 23:00:02310} // namespace gfx