icc_profile.cc 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. #include "ui/gfx/icc_profile.h"
  5. #include <list>
  6. #include <set>
  7. #include "base/command_line.h"
  8. #include "base/containers/lru_cache.h"
  9. #include "base/lazy_instance.h"
  10. #include "base/logging.h"
  11. #include "base/synchronization/lock.h"
  12. #include "third_party/skia/include/core/SkColorSpace.h"
  13. #include "third_party/skia/modules/skcms/skcms.h"
  14. #include "ui/gfx/skia_color_space_util.h"
  15. namespace gfx {
  16. namespace {
  17. static const size_t kMaxCachedICCProfiles = 16;
  18. // An LRU cache mapping data to ICCProfile objects, to avoid re-parsing
  19. // profiles every time they are read.
  20. using DataToProfileCacheBase = base::LRUCache<std::vector<char>, ICCProfile>;
  21. class DataToProfileCache : public DataToProfileCacheBase {
  22. public:
  23. DataToProfileCache() : DataToProfileCacheBase(kMaxCachedICCProfiles) {}
  24. };
  25. base::LazyInstance<DataToProfileCache>::Leaky g_data_to_profile_cache =
  26. LAZY_INSTANCE_INITIALIZER;
  27. // Lock that must be held to access |g_data_to_profile_cache|.
  28. base::LazyInstance<base::Lock>::Leaky g_icc_profile_lock =
  29. LAZY_INSTANCE_INITIALIZER;
  30. } // namespace
  31. void ICCProfile::Internals::Initialize() {
  32. // Start out with no parametric data.
  33. if (data_.empty())
  34. return;
  35. // Parse the profile.
  36. skcms_ICCProfile profile;
  37. if (!skcms_Parse(data_.data(), data_.size(), &profile)) {
  38. DLOG(ERROR) << "Failed to parse ICC profile.";
  39. return;
  40. }
  41. // We have seen many users with profiles that don't have a D50 white point.
  42. // Windows appears to detect these profiles, and not use them for OS drawing.
  43. // It still returns them when we query the system for the installed profile.
  44. // For consistency (and to match old behavior) we reject these profiles on
  45. // all platforms.
  46. // https://crbug.com/847024
  47. const skcms_Matrix3x3& m(profile.toXYZD50);
  48. float wX = m.vals[0][0] + m.vals[0][1] + m.vals[0][2];
  49. float wY = m.vals[1][0] + m.vals[1][1] + m.vals[1][2];
  50. float wZ = m.vals[2][0] + m.vals[2][1] + m.vals[2][2];
  51. static const float kD50_WhitePoint[3] = { 0.96420f, 1.00000f, 0.82491f };
  52. if (fabsf(wX - kD50_WhitePoint[0]) > 0.04f ||
  53. fabsf(wY - kD50_WhitePoint[1]) > 0.04f ||
  54. fabsf(wZ - kD50_WhitePoint[2]) > 0.04f) {
  55. return;
  56. }
  57. // At this point, the profile is considered valid. We still need to determine
  58. // if it's representable with a parametric transfer function.
  59. is_valid_ = true;
  60. // Extract the primary matrix, and assume that transfer function is sRGB until
  61. // we get something more precise.
  62. to_XYZD50_ = profile.toXYZD50;
  63. transfer_fn_ = SkNamedTransferFn::kSRGB;
  64. // Coerce it into a rasterization destination (if possible). If the profile
  65. // can't be approximated accurately, then use an sRGB transfer function and
  66. // return failure. We will continue to use the gamut from this profile.
  67. if (!skcms_MakeUsableAsDestinationWithSingleCurve(&profile)) {
  68. DLOG(ERROR) << "Parsed ICC profile but can't make usable as destination, "
  69. "using sRGB gamma";
  70. return;
  71. }
  72. // If SkColorSpace will treat the gamma as that of sRGB, then use the named
  73. // constants.
  74. sk_sp<SkColorSpace> sk_color_space = SkColorSpace::Make(profile);
  75. if (!sk_color_space) {
  76. DLOG(ERROR) << "Parsed ICC profile but cannot create SkColorSpace from it, "
  77. "using sRGB gamma.";
  78. return;
  79. }
  80. // We were able to get a parametric representation of the transfer function.
  81. is_parametric_ = true;
  82. if (sk_color_space->gammaCloseToSRGB())
  83. return;
  84. // We assume that if we accurately approximated the profile, then the
  85. // single-curve version (which may have higher error) is also okay. If we
  86. // want to maintain the distinction between accurate and inaccurate profiles,
  87. // we could check to see if the single-curve version is/ approximately equal
  88. // to the original (or to the multi-channel approximation).
  89. transfer_fn_ = profile.trc[0].parametric;
  90. }
  91. ICCProfile::ICCProfile() = default;
  92. ICCProfile::ICCProfile(ICCProfile&& other) = default;
  93. ICCProfile::ICCProfile(const ICCProfile& other) = default;
  94. ICCProfile& ICCProfile::operator=(ICCProfile&& other) = default;
  95. ICCProfile& ICCProfile::operator=(const ICCProfile& other) = default;
  96. ICCProfile::~ICCProfile() = default;
  97. bool ICCProfile::operator==(const ICCProfile& other) const {
  98. if (!internals_ && !other.internals_)
  99. return true;
  100. if (internals_ && other.internals_) {
  101. return internals_->data_ == other.internals_->data_;
  102. }
  103. return false;
  104. }
  105. bool ICCProfile::operator!=(const ICCProfile& other) const {
  106. return !(*this == other);
  107. }
  108. bool ICCProfile::IsValid() const {
  109. return internals_ ? internals_->is_valid_ : false;
  110. }
  111. std::vector<char> ICCProfile::GetData() const {
  112. return internals_ ? internals_->data_ : std::vector<char>();
  113. }
  114. // static
  115. ICCProfile ICCProfile::FromData(const void* data_as_void, size_t size) {
  116. const char* data_as_byte = reinterpret_cast<const char*>(data_as_void);
  117. std::vector<char> data(data_as_byte, data_as_byte + size);
  118. base::AutoLock lock(g_icc_profile_lock.Get());
  119. // See if there is already an entry with the same data. If so, return that
  120. // entry. If not, parse the data.
  121. ICCProfile icc_profile;
  122. auto found_by_data = g_data_to_profile_cache.Get().Get(data);
  123. if (found_by_data != g_data_to_profile_cache.Get().end()) {
  124. icc_profile = found_by_data->second;
  125. } else {
  126. icc_profile.internals_ = base::MakeRefCounted<Internals>(std::move(data));
  127. }
  128. // Insert the profile into all caches.
  129. g_data_to_profile_cache.Get().Put(icc_profile.internals_->data_, icc_profile);
  130. return icc_profile;
  131. }
  132. ColorSpace ICCProfile::GetColorSpace() const {
  133. if (!internals_ || !internals_->is_valid_)
  134. return ColorSpace();
  135. return ColorSpace(ColorSpace::PrimaryID::CUSTOM,
  136. ColorSpace::TransferID::CUSTOM, ColorSpace::MatrixID::RGB,
  137. ColorSpace::RangeID::FULL, &internals_->to_XYZD50_,
  138. &internals_->transfer_fn_);
  139. }
  140. ColorSpace ICCProfile::GetPrimariesOnlyColorSpace() const {
  141. if (!internals_ || !internals_->is_valid_)
  142. return ColorSpace();
  143. return ColorSpace(ColorSpace::PrimaryID::CUSTOM, ColorSpace::TransferID::SRGB,
  144. ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL,
  145. &internals_->to_XYZD50_, nullptr);
  146. }
  147. bool ICCProfile::IsColorSpaceAccurate() const {
  148. if (!internals_)
  149. return false;
  150. if (!internals_->is_valid_)
  151. return false;
  152. return internals_->is_parametric_;
  153. }
  154. // static
  155. ICCProfile ICCProfile::FromColorSpace(const ColorSpace& color_space) {
  156. if (!color_space.IsValid()) {
  157. return ICCProfile();
  158. }
  159. if (color_space.GetMatrixID() != ColorSpace::MatrixID::RGB) {
  160. DLOG(ERROR) << "Not creating non-RGB ICCProfile";
  161. return ICCProfile();
  162. }
  163. if (color_space.GetRangeID() != ColorSpace::RangeID::FULL) {
  164. DLOG(ERROR) << "Not creating non-full-range ICCProfile";
  165. return ICCProfile();
  166. }
  167. skcms_Matrix3x3 to_XYZD50_matrix;
  168. color_space.GetPrimaryMatrix(&to_XYZD50_matrix);
  169. skcms_TransferFunction fn;
  170. if (!color_space.GetTransferFunction(&fn)) {
  171. DLOG(ERROR) << "Failed to get ColorSpace transfer function for ICCProfile.";
  172. return ICCProfile();
  173. }
  174. sk_sp<SkData> data = SkWriteICCProfile(fn, to_XYZD50_matrix);
  175. if (!data) {
  176. DLOG(ERROR) << "Failed to create SkICC.";
  177. return ICCProfile();
  178. }
  179. return FromData(data->data(), data->size());
  180. }
  181. ICCProfile::Internals::Internals(std::vector<char> data)
  182. : data_(std::move(data)) {
  183. // Parse the ICC profile
  184. Initialize();
  185. }
  186. ICCProfile::Internals::~Internals() {}
  187. } // namespace gfx