123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- // Copyright 2016 The Chromium Authors. All rights reserved.
- // 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/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
|