| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/gfx/icc_profile.h" | 5 #include "ui/gfx/icc_profile.h" |
| 6 | 6 |
| 7 #include <list> | 7 #include <list> |
| 8 | 8 |
| 9 #include "base/containers/mru_cache.h" | 9 #include "base/containers/mru_cache.h" |
| 10 #include "base/lazy_instance.h" | 10 #include "base/lazy_instance.h" |
| 11 #include "base/synchronization/lock.h" | 11 #include "base/synchronization/lock.h" |
| 12 #include "third_party/skia/include/core/SkICC.h" | 12 #include "third_party/skia/include/core/SkICC.h" |
| 13 #include "ui/gfx/color_transform.h" | 13 #include "ui/gfx/color_transform.h" |
| 14 #include "ui/gfx/skia_color_space_util.h" |
| 14 | 15 |
| 15 namespace gfx { | 16 namespace gfx { |
| 16 | 17 |
| 17 const uint64_t ICCProfile::test_id_adobe_rgb_ = 1; | 18 const uint64_t ICCProfile::test_id_adobe_rgb_ = 1; |
| 18 const uint64_t ICCProfile::test_id_color_spin_ = 2; | 19 const uint64_t ICCProfile::test_id_color_spin_ = 2; |
| 19 const uint64_t ICCProfile::test_id_generic_rgb_ = 3; | 20 const uint64_t ICCProfile::test_id_generic_rgb_ = 3; |
| 20 const uint64_t ICCProfile::test_id_srgb_ = 4; | 21 const uint64_t ICCProfile::test_id_srgb_ = 4; |
| 21 const uint64_t ICCProfile::test_id_no_analytic_tr_fn_ = 5; | 22 const uint64_t ICCProfile::test_id_no_analytic_tr_fn_ = 5; |
| 23 const uint64_t ICCProfile::test_id_a2b_only_ = 6; |
| 22 | 24 |
| 23 namespace { | 25 namespace { |
| 24 const size_t kMinProfileLength = 128; | |
| 25 const size_t kMaxProfileLength = 4 * 1024 * 1024; | |
| 26 | 26 |
| 27 // Allow keeping around a maximum of 8 cached ICC profiles. Beware that | 27 // Allow keeping around a maximum of 8 cached ICC profiles. Beware that |
| 28 // we will do a linear search thorugh currently-cached ICC profiles, | 28 // we will do a linear search thorugh currently-cached ICC profiles, |
| 29 // when creating a new ICC profile. | 29 // when creating a new ICC profile. |
| 30 const size_t kMaxCachedICCProfiles = 8; | 30 const size_t kMaxCachedICCProfiles = 8; |
| 31 | 31 |
| 32 struct Cache { | 32 struct Cache { |
| 33 Cache() : id_to_icc_profile_mru(kMaxCachedICCProfiles) {} | 33 Cache() : id_to_icc_profile_mru(kMaxCachedICCProfiles) {} |
| 34 ~Cache() {} | 34 ~Cache() {} |
| 35 | 35 |
| (...skipping 28 matching lines...) Expand all Loading... |
| 64 | 64 |
| 65 // static | 65 // static |
| 66 ICCProfile ICCProfile::FromData(const void* data, size_t size) { | 66 ICCProfile ICCProfile::FromData(const void* data, size_t size) { |
| 67 return FromDataWithId(data, size, 0); | 67 return FromDataWithId(data, size, 0); |
| 68 } | 68 } |
| 69 | 69 |
| 70 // static | 70 // static |
| 71 ICCProfile ICCProfile::FromDataWithId(const void* data, | 71 ICCProfile ICCProfile::FromDataWithId(const void* data, |
| 72 size_t size, | 72 size_t size, |
| 73 uint64_t new_profile_id) { | 73 uint64_t new_profile_id) { |
| 74 if (!IsValidProfileLength(size)) { | 74 if (!size) { |
| 75 if (size != 0) | 75 DLOG(ERROR) << "Invalid empty ICC profile."; |
| 76 DLOG(ERROR) << "Invalid ICC profile length: " << size << "."; | |
| 77 return ICCProfile(); | 76 return ICCProfile(); |
| 78 } | 77 } |
| 79 | 78 |
| 80 const char* data_as_char = reinterpret_cast<const char*>(data); | 79 const char* data_as_char = reinterpret_cast<const char*>(data); |
| 81 { | 80 { |
| 82 // Linearly search the cached ICC profiles to find one with the same data. | 81 // Linearly search the cached ICC profiles to find one with the same data. |
| 83 // If it exists, re-use its id and touch it in the cache. | 82 // If it exists, re-use its id and touch it in the cache. |
| 84 Cache& cache = g_cache.Get(); | 83 Cache& cache = g_cache.Get(); |
| 85 base::AutoLock lock(cache.lock); | 84 base::AutoLock lock(cache.lock); |
| 86 for (auto iter = cache.id_to_icc_profile_mru.begin(); | 85 for (auto iter = cache.id_to_icc_profile_mru.begin(); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 122 if (id_) { | 121 if (id_) { |
| 123 Cache& cache = g_cache.Get(); | 122 Cache& cache = g_cache.Get(); |
| 124 base::AutoLock lock(cache.lock); | 123 base::AutoLock lock(cache.lock); |
| 125 auto found = cache.id_to_icc_profile_mru.Get(id_); | 124 auto found = cache.id_to_icc_profile_mru.Get(id_); |
| 126 if (found == cache.id_to_icc_profile_mru.end()) | 125 if (found == cache.id_to_icc_profile_mru.end()) |
| 127 found = cache.id_to_icc_profile_mru.Put(id_, *this); | 126 found = cache.id_to_icc_profile_mru.Put(id_, *this); |
| 128 } | 127 } |
| 129 return color_space_; | 128 return color_space_; |
| 130 } | 129 } |
| 131 | 130 |
| 131 const ColorSpace& ICCProfile::GetParametricColorSpace() const { |
| 132 // Move this ICC profile to the most recently used end of the cache, |
| 133 // inserting if needed. |
| 134 if (id_) { |
| 135 Cache& cache = g_cache.Get(); |
| 136 base::AutoLock lock(cache.lock); |
| 137 auto found = cache.id_to_icc_profile_mru.Get(id_); |
| 138 if (found == cache.id_to_icc_profile_mru.end()) |
| 139 found = cache.id_to_icc_profile_mru.Put(id_, *this); |
| 140 } |
| 141 return parametric_color_space_; |
| 142 } |
| 143 |
| 132 // static | 144 // static |
| 133 bool ICCProfile::FromId(uint64_t id, | 145 bool ICCProfile::FromId(uint64_t id, |
| 134 bool only_if_needed, | |
| 135 ICCProfile* icc_profile) { | 146 ICCProfile* icc_profile) { |
| 136 if (!id) | 147 if (!id) |
| 137 return false; | 148 return false; |
| 138 | 149 |
| 139 Cache& cache = g_cache.Get(); | 150 Cache& cache = g_cache.Get(); |
| 140 base::AutoLock lock(cache.lock); | 151 base::AutoLock lock(cache.lock); |
| 141 | 152 |
| 142 auto found = cache.id_to_icc_profile_mru.Get(id); | 153 auto found = cache.id_to_icc_profile_mru.Get(id); |
| 143 if (found == cache.id_to_icc_profile_mru.end()) | 154 if (found == cache.id_to_icc_profile_mru.end()) |
| 144 return false; | 155 return false; |
| 145 | 156 |
| 146 const ICCProfile& found_icc_profile = found->second; | 157 *icc_profile = found->second; |
| 147 if (found_icc_profile.color_space_is_accurate_ && only_if_needed) | |
| 148 return false; | |
| 149 | |
| 150 *icc_profile = found_icc_profile; | |
| 151 return true; | 158 return true; |
| 152 } | 159 } |
| 153 | 160 |
| 154 void ICCProfile::ComputeColorSpaceAndCache() { | 161 void ICCProfile::ComputeColorSpaceAndCache() { |
| 155 if (!id_) | 162 if (!id_) |
| 156 return; | 163 return; |
| 157 | 164 |
| 158 // If this already exists in the cache, just update its |color_space_|. | 165 // If this already exists in the cache, just update its |color_space_|. |
| 159 { | 166 { |
| 160 Cache& cache = g_cache.Get(); | 167 Cache& cache = g_cache.Get(); |
| 161 base::AutoLock lock(cache.lock); | 168 base::AutoLock lock(cache.lock); |
| 162 auto found = cache.id_to_icc_profile_mru.Get(id_); | 169 auto found = cache.id_to_icc_profile_mru.Get(id_); |
| 163 if (found != cache.id_to_icc_profile_mru.end()) { | 170 if (found != cache.id_to_icc_profile_mru.end()) { |
| 164 color_space_ = found->second.color_space_; | 171 color_space_ = found->second.color_space_; |
| 172 parametric_color_space_ = found->second.parametric_color_space_; |
| 165 successfully_parsed_by_sk_icc_ = | 173 successfully_parsed_by_sk_icc_ = |
| 166 found->second.successfully_parsed_by_sk_icc_; | 174 found->second.successfully_parsed_by_sk_icc_; |
| 167 return; | 175 return; |
| 168 } | 176 } |
| 169 } | 177 } |
| 170 | 178 |
| 171 color_space_is_accurate_ = true; | |
| 172 SkMatrix44 to_XYZD50_matrix; | |
| 173 SkColorSpaceTransferFn fn; | |
| 174 sk_sp<SkICC> sk_icc = SkICC::Make(data_.data(), data_.size()); | 179 sk_sp<SkICC> sk_icc = SkICC::Make(data_.data(), data_.size()); |
| 175 if (sk_icc) { | 180 if (sk_icc) { |
| 181 bool parametric_color_space_is_accurate = false; |
| 176 successfully_parsed_by_sk_icc_ = true; | 182 successfully_parsed_by_sk_icc_ = true; |
| 177 if (!sk_icc->toXYZD50(&to_XYZD50_matrix)) { | 183 |
| 178 // Just say that the primaries were the sRGB primaries if we can't | 184 // Populate |parametric_color_space_| as a primary matrix and analytic |
| 179 // extract them. | 185 // transfer function, if possible. |
| 180 gfx::ColorSpace::CreateSRGB().GetPrimaryMatrix(&to_XYZD50_matrix); | 186 SkMatrix44 to_XYZD50_matrix; |
| 181 color_space_is_accurate_ = false; | 187 if (sk_icc->toXYZD50(&to_XYZD50_matrix)) { |
| 182 DLOG(ERROR) << "Unable to handle ICCProfile primaries."; | 188 SkColorSpaceTransferFn fn; |
| 189 // First try to get a numerical transfer function from the profile. |
| 190 if (sk_icc->isNumericalTransferFn(&fn)) { |
| 191 parametric_color_space_is_accurate = true; |
| 192 } else { |
| 193 // If that fails, try to approximate the transfer function. |
| 194 float fn_max_error = 0; |
| 195 bool got_approximate_fn = |
| 196 SkApproximateTransferFn(sk_icc, &fn_max_error, &fn); |
| 197 if (got_approximate_fn) { |
| 198 float kMaxError = 3.f / 256.f; |
| 199 if (fn_max_error < kMaxError) { |
| 200 parametric_color_space_is_accurate = true; |
| 201 } else { |
| 202 DLOG(ERROR) << "ICCProfile transfer function approximation " |
| 203 << "inexact, error: " << 256.f * fn_max_error << "/256"; |
| 204 } |
| 205 } else { |
| 206 // And if that fails, just say that the transfer function was sRGB. |
| 207 DLOG(ERROR) << "Failed to approximate ICCProfile transfer function."; |
| 208 gfx::ColorSpace::CreateSRGB().GetTransferFunction(&fn); |
| 209 } |
| 210 } |
| 211 parametric_color_space_ = |
| 212 gfx::ColorSpace::CreateCustom(to_XYZD50_matrix, fn); |
| 213 } else { |
| 214 DLOG(ERROR) << "Failed to extract ICCProfile primary matrix."; |
| 215 // TODO(ccameron): Get an approximate gamut for rasterization. |
| 216 parametric_color_space_ = gfx::ColorSpace::CreateSRGB(); |
| 183 } | 217 } |
| 184 if (!sk_icc->isNumericalTransferFn(&fn)) { | 218 |
| 185 // Just say that the transfer function was sRGB if we cannot read it. | 219 // If the approximation is accurate, then set |parametric_color_space_| and |
| 186 // TODO(ccameron): Use a least squares approximation of the transfer | 220 // |color_space_| to the same value, and link them to |this|. Otherwise, set |
| 187 // function when it is not numerical. | 221 // them separately, and do not link |parametric_color_space_| to |this|. |
| 188 gfx::ColorSpace::CreateSRGB().GetTransferFunction(&fn); | 222 if (parametric_color_space_is_accurate) { |
| 189 color_space_is_accurate_ = false; | 223 parametric_color_space_.icc_profile_id_ = id_; |
| 190 DLOG(ERROR) << "Unable to handle ICCProfile transfer function."; | 224 color_space_ = parametric_color_space_; |
| 225 } else { |
| 226 color_space_ = ColorSpace(ColorSpace::PrimaryID::ICC_BASED, |
| 227 ColorSpace::TransferID::ICC_BASED); |
| 228 color_space_.icc_profile_id_ = id_; |
| 229 color_space_.icc_profile_sk_color_space_ = |
| 230 SkColorSpace::MakeICC(data_.data(), data_.size()); |
| 191 } | 231 } |
| 192 } else { | 232 } else { |
| 233 DLOG(ERROR) << "Unable parse ICCProfile."; |
| 193 successfully_parsed_by_sk_icc_ = false; | 234 successfully_parsed_by_sk_icc_ = false; |
| 194 gfx::ColorSpace::CreateSRGB().GetPrimaryMatrix(&to_XYZD50_matrix); | |
| 195 gfx::ColorSpace::CreateSRGB().GetTransferFunction(&fn); | |
| 196 color_space_is_accurate_ = false; | |
| 197 DLOG(ERROR) << "Unable parse ICCProfile."; | |
| 198 } | 235 } |
| 199 | 236 |
| 200 // Compute the color space. | |
| 201 color_space_ = gfx::ColorSpace::CreateCustom(to_XYZD50_matrix, fn); | |
| 202 color_space_.icc_profile_id_ = id_; | |
| 203 color_space_.icc_profile_sk_color_space_ = | |
| 204 SkColorSpace::MakeICC(data_.data(), data_.size()); | |
| 205 | |
| 206 // Add to the cache. | 237 // Add to the cache. |
| 207 { | 238 { |
| 208 Cache& cache = g_cache.Get(); | 239 Cache& cache = g_cache.Get(); |
| 209 base::AutoLock lock(cache.lock); | 240 base::AutoLock lock(cache.lock); |
| 210 cache.id_to_icc_profile_mru.Put(id_, *this); | 241 cache.id_to_icc_profile_mru.Put(id_, *this); |
| 211 } | 242 } |
| 212 } | 243 } |
| 213 | 244 |
| 214 // static | |
| 215 bool ICCProfile::IsValidProfileLength(size_t length) { | |
| 216 return length >= kMinProfileLength && length <= kMaxProfileLength; | |
| 217 } | |
| 218 | |
| 219 } // namespace gfx | 245 } // namespace gfx |
| OLD | NEW |