| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/gfx/icc_profile.h" |
| |
| #include <list> |
| #include <set> |
| |
| #include "base/command_line.h" |
| #include "base/containers/lru_cache.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/synchronization/lock.h" |
| #include "third_party/skia/include/core/SkColorSpace.h" |
| #include "third_party/skia/include/core/SkData.h" |
| #include "third_party/skia/include/core/SkRefCnt.h" |
| #include "third_party/skia/include/encode/SkICC.h" |
| #include "third_party/skia/modules/skcms/skcms.h" |
| #include "ui/gfx/skia_color_space_util.h" |
| |
| namespace gfx { |
| |
| namespace { |
| |
| static const size_t kMaxCachedICCProfiles = 16; |
| |
| // An LRU cache mapping data to ICCProfile objects, to avoid re-parsing |
| // profiles every time they are read. |
| using DataToProfileCacheBase = base::LRUCache<std::vector<char>, ICCProfile>; |
| class DataToProfileCache : public DataToProfileCacheBase { |
| public: |
| DataToProfileCache() : DataToProfileCacheBase(kMaxCachedICCProfiles) {} |
| }; |
| base::LazyInstance<DataToProfileCache>::Leaky g_data_to_profile_cache = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| // Lock that must be held to access |g_data_to_profile_cache|. |
| base::LazyInstance<base::Lock>::Leaky g_icc_profile_lock = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| } // namespace |
| |
| void ICCProfile::Internals::Initialize() { |
| // Start out with no parametric data. |
| if (data_.empty()) |
| return; |
| |
| // Parse the profile. |
| skcms_ICCProfile profile; |
| if (!skcms_Parse(data_.data(), data_.size(), &profile)) { |
| DLOG(ERROR) << "Failed to parse ICC profile."; |
| return; |
| } |
| |
| // We have seen many users with profiles that don't have a D50 white point. |
| // Windows appears to detect these profiles, and not use them for OS drawing. |
| // It still returns them when we query the system for the installed profile. |
| // For consistency (and to match old behavior) we reject these profiles on |
| // all platforms. |
| // https://crbug.com/847024 |
| const skcms_Matrix3x3& m(profile.toXYZD50); |
| float wX = m.vals[0][0] + m.vals[0][1] + m.vals[0][2]; |
| float wY = m.vals[1][0] + m.vals[1][1] + m.vals[1][2]; |
| float wZ = m.vals[2][0] + m.vals[2][1] + m.vals[2][2]; |
| static const float kD50_WhitePoint[3] = { 0.96420f, 1.00000f, 0.82491f }; |
| if (fabsf(wX - kD50_WhitePoint[0]) > 0.04f || |
| fabsf(wY - kD50_WhitePoint[1]) > 0.04f || |
| fabsf(wZ - kD50_WhitePoint[2]) > 0.04f) { |
| return; |
| } |
| |
| // At this point, the profile is considered valid. We still need to determine |
| // if it's representable with a parametric transfer function. |
| is_valid_ = true; |
| |
| // Extract the primary matrix, and assume that transfer function is sRGB until |
| // we get something more precise. |
| to_XYZD50_ = profile.toXYZD50; |
| transfer_fn_ = SkNamedTransferFn::kSRGB; |
| |
| // Coerce it into a rasterization destination (if possible). If the profile |
| // can't be approximated accurately, then use an sRGB transfer function and |
| // return failure. We will continue to use the gamut from this profile. |
| if (!skcms_MakeUsableAsDestinationWithSingleCurve(&profile)) { |
| DLOG(ERROR) << "Parsed ICC profile but can't make usable as destination, " |
| "using sRGB gamma"; |
| return; |
| } |
| |
| // If SkColorSpace will treat the gamma as that of sRGB, then use the named |
| // constants. |
| sk_sp<SkColorSpace> sk_color_space = SkColorSpace::Make(profile); |
| if (!sk_color_space) { |
| DLOG(ERROR) << "Parsed ICC profile but cannot create SkColorSpace from it, " |
| "using sRGB gamma."; |
| return; |
| } |
| |
| // We were able to get a parametric representation of the transfer function. |
| is_parametric_ = true; |
| |
| if (sk_color_space->gammaCloseToSRGB()) |
| return; |
| |
| // We assume that if we accurately approximated the profile, then the |
| // single-curve version (which may have higher error) is also okay. If we |
| // want to maintain the distinction between accurate and inaccurate profiles, |
| // we could check to see if the single-curve version is/ approximately equal |
| // to the original (or to the multi-channel approximation). |
| transfer_fn_ = profile.trc[0].parametric; |
| } |
| |
| ICCProfile::ICCProfile() = default; |
| ICCProfile::ICCProfile(ICCProfile&& other) = default; |
| ICCProfile::ICCProfile(const ICCProfile& other) = default; |
| ICCProfile& ICCProfile::operator=(ICCProfile&& other) = default; |
| ICCProfile& ICCProfile::operator=(const ICCProfile& other) = default; |
| ICCProfile::~ICCProfile() = default; |
| |
| bool ICCProfile::operator==(const ICCProfile& other) const { |
| if (!internals_ && !other.internals_) |
| return true; |
| if (internals_ && other.internals_) { |
| return internals_->data_ == other.internals_->data_; |
| } |
| return false; |
| } |
| |
| bool ICCProfile::operator!=(const ICCProfile& other) const { |
| return !(*this == other); |
| } |
| |
| bool ICCProfile::IsValid() const { |
| return internals_ ? internals_->is_valid_ : false; |
| } |
| |
| std::vector<char> ICCProfile::GetData() const { |
| return internals_ ? internals_->data_ : std::vector<char>(); |
| } |
| |
| // static |
| ICCProfile ICCProfile::FromData(const void* data_as_void, size_t size) { |
| const char* data_as_byte = reinterpret_cast<const char*>(data_as_void); |
| std::vector<char> data(data_as_byte, data_as_byte + size); |
| |
| base::AutoLock lock(g_icc_profile_lock.Get()); |
| |
| // See if there is already an entry with the same data. If so, return that |
| // entry. If not, parse the data. |
| ICCProfile icc_profile; |
| auto found_by_data = g_data_to_profile_cache.Get().Get(data); |
| if (found_by_data != g_data_to_profile_cache.Get().end()) { |
| icc_profile = found_by_data->second; |
| } else { |
| icc_profile.internals_ = base::MakeRefCounted<Internals>(std::move(data)); |
| } |
| |
| // Insert the profile into all caches. |
| g_data_to_profile_cache.Get().Put(icc_profile.internals_->data_, icc_profile); |
| |
| return icc_profile; |
| } |
| |
| ColorSpace ICCProfile::GetColorSpace() const { |
| if (!internals_ || !internals_->is_valid_) |
| return ColorSpace(); |
| |
| return ColorSpace(ColorSpace::PrimaryID::CUSTOM, |
| ColorSpace::TransferID::CUSTOM, ColorSpace::MatrixID::RGB, |
| ColorSpace::RangeID::FULL, &internals_->to_XYZD50_, |
| &internals_->transfer_fn_); |
| } |
| |
| ColorSpace ICCProfile::GetPrimariesOnlyColorSpace() const { |
| if (!internals_ || !internals_->is_valid_) |
| return ColorSpace(); |
| |
| return ColorSpace(ColorSpace::PrimaryID::CUSTOM, ColorSpace::TransferID::SRGB, |
| ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL, |
| &internals_->to_XYZD50_, nullptr); |
| } |
| |
| bool ICCProfile::IsColorSpaceAccurate() const { |
| if (!internals_) |
| return false; |
| |
| if (!internals_->is_valid_) |
| return false; |
| |
| return internals_->is_parametric_; |
| } |
| |
| // static |
| ICCProfile ICCProfile::FromColorSpace(const ColorSpace& color_space) { |
| if (!color_space.IsValid()) { |
| return ICCProfile(); |
| } |
| if (color_space.GetMatrixID() != ColorSpace::MatrixID::RGB) { |
| DLOG(ERROR) << "Not creating non-RGB ICCProfile"; |
| return ICCProfile(); |
| } |
| if (color_space.GetRangeID() != ColorSpace::RangeID::FULL) { |
| DLOG(ERROR) << "Not creating non-full-range ICCProfile"; |
| return ICCProfile(); |
| } |
| skcms_Matrix3x3 to_XYZD50_matrix; |
| color_space.GetPrimaryMatrix(&to_XYZD50_matrix); |
| skcms_TransferFunction fn; |
| if (!color_space.GetTransferFunction(&fn)) { |
| DLOG(ERROR) << "Failed to get ColorSpace transfer function for ICCProfile."; |
| return ICCProfile(); |
| } |
| sk_sp<SkData> data = SkWriteICCProfile(fn, to_XYZD50_matrix); |
| if (!data) { |
| DLOG(ERROR) << "Failed to create SkICC."; |
| return ICCProfile(); |
| } |
| return FromData(data->data(), data->size()); |
| } |
| |
| ICCProfile::Internals::Internals(std::vector<char> data) |
| : data_(std::move(data)) { |
| // Parse the ICC profile |
| Initialize(); |
| } |
| |
| ICCProfile::Internals::~Internals() {} |
| |
| } // namespace gfx |