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