blob: 1808222855c0b2bfd8a79119b56b481629b2fa04 [file] [log] [blame]
Avi Drissman3e1a26c2022-09-15 20:26:031// Copyright 2016 The Chromium Authors
ccameronefdab162016-07-25 23:00:022// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Elly Fong-Jonesa8ea66f12024-07-24 18:37:105#ifdef UNSAFE_BUFFERS_BUILD
6// TODO(crbug.com/354829279): Remove this and convert code to safer constructs.
7#pragma allow_unsafe_buffers
8#endif
9
ccameronefdab162016-07-25 23:00:0210#include "ui/gfx/icc_profile.h"
11
12#include <list>
Christopher Cameron374b6c42017-08-31 22:21:2313#include <set>
ccameronefdab162016-07-25 23:00:0214
ccameron290a91a2017-05-13 19:22:3315#include "base/command_line.h"
cfredric8fdc22432021-10-14 03:24:0016#include "base/containers/lru_cache.h"
ccameronefdab162016-07-25 23:00:0217#include "base/lazy_instance.h"
Hans Wennborg17cd6072020-04-22 15:47:0418#include "base/logging.h"
ccameronefdab162016-07-25 23:00:0219#include "base/synchronization/lock.h"
Mike Kleineb11dca2018-09-05 15:47:0620#include "third_party/skia/include/core/SkColorSpace.h"
Kevin Lubick98022952023-02-14 13:48:3421#include "third_party/skia/include/core/SkData.h"
22#include "third_party/skia/include/core/SkRefCnt.h"
Kevin Lubick8b52bc022023-04-25 20:18:0123#include "third_party/skia/include/encode/SkICC.h"
Kevin Lubickefd439a2022-07-11 13:50:4524#include "third_party/skia/modules/skcms/skcms.h"
ccameron24c87c32017-03-14 21:50:4225#include "ui/gfx/skia_color_space_util.h"
ccameronefdab162016-07-25 23:00:0226
27namespace gfx {
28
Christopher Cameron374b6c42017-08-31 22:21:2329namespace {
30
Christopher Cameron3bc3d5e2017-11-23 03:48:1131static const size_t kMaxCachedICCProfiles = 16;
32
cfredric8fdc22432021-10-14 03:24:0033// An LRU cache mapping data to ICCProfile objects, to avoid re-parsing
Christopher Cameron3bc3d5e2017-11-23 03:48:1134// profiles every time they are read.
cfredric8fdc22432021-10-14 03:24:0035using DataToProfileCacheBase = base::LRUCache<std::vector<char>, ICCProfile>;
Christopher Cameron3bc3d5e2017-11-23 03:48:1136class DataToProfileCache : public DataToProfileCacheBase {
37 public:
38 DataToProfileCache() : DataToProfileCacheBase(kMaxCachedICCProfiles) {}
39};
Christopher Camerona9b81c0b132017-12-21 00:23:5440base::LazyInstance<DataToProfileCache>::Leaky g_data_to_profile_cache =
41 LAZY_INSTANCE_INITIALIZER;
Christopher Cameron3bc3d5e2017-11-23 03:48:1142
Christopher Cameron20eeb0c2019-04-05 23:40:0043// Lock that must be held to access |g_data_to_profile_cache|.
Daniel Bratellb6c0ab8c2017-12-21 13:09:1844base::LazyInstance<base::Lock>::Leaky g_icc_profile_lock =
45 LAZY_INSTANCE_INITIALIZER;
ccameronefdab162016-07-25 23:00:0246
Christopher Cameron374b6c42017-08-31 22:21:2347} // namespace
48
Brian Osmanf836de52020-03-04 02:13:0149void ICCProfile::Internals::Initialize() {
Christopher Cameronfdc0f872017-11-09 03:22:1650 // Start out with no parametric data.
Christopher Cameron3bc3d5e2017-11-23 03:48:1151 if (data_.empty())
Brian Osmanf836de52020-03-04 02:13:0152 return;
Christopher Camerona4adb41b2017-07-13 06:44:2553
Brian Osman37233e32018-05-17 16:53:1754 // Parse the profile.
55 skcms_ICCProfile profile;
56 if (!skcms_Parse(data_.data(), data_.size(), &profile)) {
57 DLOG(ERROR) << "Failed to parse ICC profile.";
Brian Osmanf836de52020-03-04 02:13:0158 return;
Christopher Camerona4adb41b2017-07-13 06:44:2559 }
Brian Osman37233e32018-05-17 16:53:1760
Brian Osman56adc572018-07-26 17:34:4361 // We have seen many users with profiles that don't have a D50 white point.
62 // Windows appears to detect these profiles, and not use them for OS drawing.
63 // It still returns them when we query the system for the installed profile.
64 // For consistency (and to match old behavior) we reject these profiles on
65 // all platforms.
66 // https://crbug.com/847024
67 const skcms_Matrix3x3& m(profile.toXYZD50);
68 float wX = m.vals[0][0] + m.vals[0][1] + m.vals[0][2];
69 float wY = m.vals[1][0] + m.vals[1][1] + m.vals[1][2];
70 float wZ = m.vals[2][0] + m.vals[2][1] + m.vals[2][2];
Brian Osman56adc572018-07-26 17:34:4371 static const float kD50_WhitePoint[3] = { 0.96420f, 1.00000f, 0.82491f };
72 if (fabsf(wX - kD50_WhitePoint[0]) > 0.04f ||
73 fabsf(wY - kD50_WhitePoint[1]) > 0.04f ||
74 fabsf(wZ - kD50_WhitePoint[2]) > 0.04f) {
Brian Osmanf836de52020-03-04 02:13:0175 return;
Brian Osman56adc572018-07-26 17:34:4376 }
77
Brian Osmanf836de52020-03-04 02:13:0178 // At this point, the profile is considered valid. We still need to determine
79 // if it's representable with a parametric transfer function.
80 is_valid_ = true;
81
Christopher Cameronbadc0022019-04-08 18:37:0282 // Extract the primary matrix, and assume that transfer function is sRGB until
83 // we get something more precise.
84 to_XYZD50_ = profile.toXYZD50;
85 transfer_fn_ = SkNamedTransferFn::kSRGB;
86
Christopher Cameron20eeb0c2019-04-05 23:40:0087 // Coerce it into a rasterization destination (if possible). If the profile
88 // can't be approximated accurately, then use an sRGB transfer function and
89 // return failure. We will continue to use the gamut from this profile.
90 if (!skcms_MakeUsableAsDestinationWithSingleCurve(&profile)) {
Christopher Cameronbadc0022019-04-08 18:37:0291 DLOG(ERROR) << "Parsed ICC profile but can't make usable as destination, "
92 "using sRGB gamma";
Brian Osmanf836de52020-03-04 02:13:0193 return;
Christopher Cameron20eeb0c2019-04-05 23:40:0094 }
Christopher Camerona4adb41b2017-07-13 06:44:2595
Christopher Cameron20eeb0c2019-04-05 23:40:0096 // If SkColorSpace will treat the gamma as that of sRGB, then use the named
97 // constants.
98 sk_sp<SkColorSpace> sk_color_space = SkColorSpace::Make(profile);
Christopher Cameronbadc0022019-04-08 18:37:0299 if (!sk_color_space) {
100 DLOG(ERROR) << "Parsed ICC profile but cannot create SkColorSpace from it, "
101 "using sRGB gamma.";
Brian Osmanf836de52020-03-04 02:13:01102 return;
Christopher Cameron20eeb0c2019-04-05 23:40:00103 }
Brian Osmanf836de52020-03-04 02:13:01104
105 // We were able to get a parametric representation of the transfer function.
106 is_parametric_ = true;
107
Christopher Cameronbadc0022019-04-08 18:37:02108 if (sk_color_space->gammaCloseToSRGB())
Brian Osmanf836de52020-03-04 02:13:01109 return;
ccamerone35e2322017-11-02 23:33:46110
Brian Osman37233e32018-05-17 16:53:17111 // We assume that if we accurately approximated the profile, then the
112 // single-curve version (which may have higher error) is also okay. If we
113 // want to maintain the distinction between accurate and inaccurate profiles,
114 // we could check to see if the single-curve version is/ approximately equal
115 // to the original (or to the multi-channel approximation).
Christopher Cameron20eeb0c2019-04-05 23:40:00116 transfer_fn_ = profile.trc[0].parametric;
Christopher Cameron374b6c42017-08-31 22:21:23117}
Reilly Grantc46032eb2017-08-30 22:53:52118
ccameronefdab162016-07-25 23:00:02119ICCProfile::ICCProfile() = default;
120ICCProfile::ICCProfile(ICCProfile&& other) = default;
121ICCProfile::ICCProfile(const ICCProfile& other) = default;
122ICCProfile& ICCProfile::operator=(ICCProfile&& other) = default;
123ICCProfile& ICCProfile::operator=(const ICCProfile& other) = default;
124ICCProfile::~ICCProfile() = default;
125
126bool ICCProfile::operator==(const ICCProfile& other) const {
Christopher Cameron43f17052017-11-11 01:52:01127 if (!internals_ && !other.internals_)
128 return true;
Christopher Camerondeea2522017-11-16 07:10:52129 if (internals_ && other.internals_) {
Christopher Cameron20eeb0c2019-04-05 23:40:00130 return internals_->data_ == other.internals_->data_;
Christopher Camerondeea2522017-11-16 07:10:52131 }
Christopher Cameron43f17052017-11-11 01:52:01132 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
Christopher Cameron3bc3d5e2017-11-23 03:48:11143std::vector<char> ICCProfile::GetData() const {
144 return internals_ ? internals_->data_ : std::vector<char>();
145}
146
ccameronefdab162016-07-25 23:00:02147// static
Christopher Cameron20eeb0c2019-04-05 23:40:00148ICCProfile ICCProfile::FromData(const void* data_as_void, size_t size) {
Christopher Cameron43f17052017-11-11 01:52:01149 const char* data_as_byte = reinterpret_cast<const char*>(data_as_void);
Christopher Cameron3bc3d5e2017-11-23 03:48:11150 std::vector<char> data(data_as_byte, data_as_byte + size);
Reilly Grantc46032eb2017-08-30 22:53:52151
Daniel Bratellb6c0ab8c2017-12-21 13:09:18152 base::AutoLock lock(g_icc_profile_lock.Get());
ccameronefdab162016-07-25 23:00:02153
Christopher Cameron43f17052017-11-11 01:52:01154 // See if there is already an entry with the same data. If so, return that
Christopher Cameron3bc3d5e2017-11-23 03:48:11155 // entry. If not, parse the data.
156 ICCProfile icc_profile;
157 auto found_by_data = g_data_to_profile_cache.Get().Get(data);
158 if (found_by_data != g_data_to_profile_cache.Get().end()) {
159 icc_profile = found_by_data->second;
160 } else {
Christopher Cameron20eeb0c2019-04-05 23:40:00161 icc_profile.internals_ = base::MakeRefCounted<Internals>(std::move(data));
Christopher Cameron43f17052017-11-11 01:52:01162 }
163
Christopher Cameron3bc3d5e2017-11-23 03:48:11164 // Insert the profile into all caches.
Christopher Cameron3bc3d5e2017-11-23 03:48:11165 g_data_to_profile_cache.Get().Put(icc_profile.internals_->data_, icc_profile);
Christopher Camerondeea2522017-11-16 07:10:52166
Christopher Cameron3bc3d5e2017-11-23 03:48:11167 return icc_profile;
ccameronefdab162016-07-25 23:00:02168}
169
Christopher Cameronfdc0f872017-11-09 03:22:16170ColorSpace ICCProfile::GetColorSpace() const {
Zhenyao Mo44c8d1832019-12-10 06:43:10171 if (!internals_ || !internals_->is_valid_)
Christopher Cameron43f17052017-11-11 01:52:01172 return ColorSpace();
Christopher Cameronfdc0f872017-11-09 03:22:16173
Zhenyao Mo44c8d1832019-12-10 06:43:10174 return ColorSpace(ColorSpace::PrimaryID::CUSTOM,
175 ColorSpace::TransferID::CUSTOM, ColorSpace::MatrixID::RGB,
176 ColorSpace::RangeID::FULL, &internals_->to_XYZD50_,
177 &internals_->transfer_fn_);
Christopher Cameron20eeb0c2019-04-05 23:40:00178}
179
Christopher Camerona808f1f2019-05-02 20:29:53180ColorSpace ICCProfile::GetPrimariesOnlyColorSpace() const {
Zhenyao Mo44c8d1832019-12-10 06:43:10181 if (!internals_ || !internals_->is_valid_)
182 return ColorSpace();
183
Christopher Cameron6c9cfff02022-01-25 01:29:03184 return ColorSpace(ColorSpace::PrimaryID::CUSTOM, ColorSpace::TransferID::SRGB,
Zhenyao Mo44c8d1832019-12-10 06:43:10185 ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL,
186 &internals_->to_XYZD50_, nullptr);
Christopher Camerona808f1f2019-05-02 20:29:53187}
188
Christopher Cameron20eeb0c2019-04-05 23:40:00189bool ICCProfile::IsColorSpaceAccurate() const {
190 if (!internals_)
191 return false;
192
193 if (!internals_->is_valid_)
194 return false;
195
196 return internals_->is_parametric_;
ccameron24c87c32017-03-14 21:50:42197}
198
Christopher Cameron3bc3d5e2017-11-23 03:48:11199// static
Christopher Cameron20eeb0c2019-04-05 23:40:00200ICCProfile ICCProfile::FromColorSpace(const ColorSpace& color_space) {
Christopher Cameron3bc3d5e2017-11-23 03:48:11201 if (!color_space.IsValid()) {
202 return ICCProfile();
Christopher Cameron43f17052017-11-11 01:52:01203 }
Zhenyao Mo44c8d1832019-12-10 06:43:10204 if (color_space.GetMatrixID() != ColorSpace::MatrixID::RGB) {
Christopher Camerondeea2522017-11-16 07:10:52205 DLOG(ERROR) << "Not creating non-RGB ICCProfile";
Christopher Cameron3bc3d5e2017-11-23 03:48:11206 return ICCProfile();
Christopher Camerondeea2522017-11-16 07:10:52207 }
Zhenyao Mo44c8d1832019-12-10 06:43:10208 if (color_space.GetRangeID() != ColorSpace::RangeID::FULL) {
Christopher Camerondeea2522017-11-16 07:10:52209 DLOG(ERROR) << "Not creating non-full-range ICCProfile";
Christopher Cameron3bc3d5e2017-11-23 03:48:11210 return ICCProfile();
211 }
Brian Osmanb76ea802019-02-01 19:06:23212 skcms_Matrix3x3 to_XYZD50_matrix;
Christopher Cameron3bc3d5e2017-11-23 03:48:11213 color_space.GetPrimaryMatrix(&to_XYZD50_matrix);
Brian Osmanb76ea802019-02-01 19:06:23214 skcms_TransferFunction fn;
Christopher Cameron3bc3d5e2017-11-23 03:48:11215 if (!color_space.GetTransferFunction(&fn)) {
Christopher Camerondeea2522017-11-16 07:10:52216 DLOG(ERROR) << "Failed to get ColorSpace transfer function for ICCProfile.";
Christopher Cameron3bc3d5e2017-11-23 03:48:11217 return ICCProfile();
Christopher Camerondeea2522017-11-16 07:10:52218 }
Brian Osmanb76ea802019-02-01 19:06:23219 sk_sp<SkData> data = SkWriteICCProfile(fn, to_XYZD50_matrix);
Christopher Camerondeea2522017-11-16 07:10:52220 if (!data) {
221 DLOG(ERROR) << "Failed to create SkICC.";
Christopher Cameron3bc3d5e2017-11-23 03:48:11222 return ICCProfile();
Christopher Camerondeea2522017-11-16 07:10:52223 }
Christopher Cameron20eeb0c2019-04-05 23:40:00224 return FromData(data->data(), data->size());
Christopher Cameron3bc3d5e2017-11-23 03:48:11225}
226
Christopher Cameron20eeb0c2019-04-05 23:40:00227ICCProfile::Internals::Internals(std::vector<char> data)
228 : data_(std::move(data)) {
Christopher Camerona4adb41b2017-07-13 06:44:25229 // Parse the ICC profile
Brian Osmanf836de52020-03-04 02:13:01230 Initialize();
Christopher Cameron374b6c42017-08-31 22:21:23231}
232
Christopher Cameron43f17052017-11-11 01:52:01233ICCProfile::Internals::~Internals() {}
234
ccameronefdab162016-07-25 23:00:02235} // namespace gfx