SkColorSpace.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. /*
  2. * Copyright 2016 Google Inc.
  3. *
  4. * Use of this source code is governed by a BSD-style license that can be
  5. * found in the LICENSE file.
  6. */
  7. #include "include/core/SkColorSpace.h"
  8. #include "include/core/SkData.h"
  9. #include "include/third_party/skcms/skcms.h"
  10. #include "src/core/SkColorSpacePriv.h"
  11. #include "src/core/SkOpts.h"
  12. bool SkColorSpacePrimaries::toXYZD50(skcms_Matrix3x3* toXYZ_D50) const {
  13. return skcms_PrimariesToXYZD50(fRX, fRY, fGX, fGY, fBX, fBY, fWX, fWY, toXYZ_D50);
  14. }
  15. SkColorSpace::SkColorSpace(const float transferFn[7],
  16. const skcms_Matrix3x3& toXYZD50) {
  17. memcpy(fToXYZD50_3x3, &toXYZD50.vals[0][0], 9*sizeof(float));
  18. fToXYZD50Hash = SkOpts::hash_fn(fToXYZD50_3x3, 9*sizeof(float), 0);
  19. memcpy(fTransferFn, transferFn, 7*sizeof(float));
  20. fTransferFnHash = SkOpts::hash_fn(fTransferFn, 7*sizeof(float), 0);
  21. }
  22. static bool xyz_almost_equal(const skcms_Matrix3x3& mA, const skcms_Matrix3x3& mB) {
  23. for (int r = 0; r < 3; ++r) {
  24. for (int c = 0; c < 3; ++c) {
  25. if (!color_space_almost_equal(mA.vals[r][c], mB.vals[r][c])) {
  26. return false;
  27. }
  28. }
  29. }
  30. return true;
  31. }
  32. sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const skcms_TransferFunction& transferFn,
  33. const skcms_Matrix3x3& toXYZ) {
  34. if (!is_valid_transfer_fn(transferFn)) {
  35. return nullptr;
  36. }
  37. const float* tf = &transferFn.g;
  38. if (is_almost_srgb(transferFn)) {
  39. if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
  40. return SkColorSpace::MakeSRGB();
  41. }
  42. tf = &SkNamedTransferFn::kSRGB.g;
  43. } else if (is_almost_2dot2(transferFn)) {
  44. tf = &SkNamedTransferFn::k2Dot2.g;
  45. } else if (is_almost_linear(transferFn)) {
  46. if (xyz_almost_equal(toXYZ, SkNamedGamut::kSRGB)) {
  47. return SkColorSpace::MakeSRGBLinear();
  48. }
  49. tf = &SkNamedTransferFn::kLinear.g;
  50. }
  51. return sk_sp<SkColorSpace>(new SkColorSpace(tf, toXYZ));
  52. }
  53. class SkColorSpaceSingletonFactory {
  54. public:
  55. static SkColorSpace* Make(const skcms_TransferFunction& transferFn,
  56. const skcms_Matrix3x3& to_xyz) {
  57. return new SkColorSpace(&transferFn.g, to_xyz);
  58. }
  59. };
  60. SkColorSpace* sk_srgb_singleton() {
  61. static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kSRGB,
  62. SkNamedGamut::kSRGB);
  63. return cs;
  64. }
  65. SkColorSpace* sk_srgb_linear_singleton() {
  66. static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(SkNamedTransferFn::kLinear,
  67. SkNamedGamut::kSRGB);
  68. return cs;
  69. }
  70. sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
  71. return sk_ref_sp(sk_srgb_singleton());
  72. }
  73. sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
  74. return sk_ref_sp(sk_srgb_linear_singleton());
  75. }
  76. void SkColorSpace::computeLazyDstFields() const {
  77. fLazyDstFieldsOnce([this] {
  78. // Invert 3x3 gamut, defaulting to sRGB if we can't.
  79. {
  80. skcms_Matrix3x3 fwd, inv;
  81. memcpy(&fwd, fToXYZD50_3x3, 9*sizeof(float));
  82. if (!skcms_Matrix3x3_invert(&fwd, &inv)) {
  83. SkAssertResult(skcms_Matrix3x3_invert(&skcms_sRGB_profile()->toXYZD50, &inv));
  84. }
  85. memcpy(fFromXYZD50_3x3, &inv, 9*sizeof(float));
  86. }
  87. // Invert transfer function, defaulting to sRGB if we can't.
  88. {
  89. skcms_TransferFunction fwd, inv;
  90. this->transferFn(&fwd.g);
  91. if (!skcms_TransferFunction_invert(&fwd, &inv)) {
  92. inv = *skcms_sRGB_Inverse_TransferFunction();
  93. }
  94. memcpy(fInvTransferFn, &inv, 7*sizeof(float));
  95. }
  96. });
  97. }
  98. bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const {
  99. this->transferFn(&coeffs->g);
  100. return true;
  101. }
  102. void SkColorSpace::transferFn(float gabcdef[7]) const {
  103. memcpy(gabcdef, &fTransferFn, 7*sizeof(float));
  104. }
  105. void SkColorSpace::invTransferFn(float gabcdef[7]) const {
  106. this->computeLazyDstFields();
  107. memcpy(gabcdef, &fInvTransferFn, 7*sizeof(float));
  108. }
  109. bool SkColorSpace::toXYZD50(SkMatrix44* toXYZD50) const {
  110. toXYZD50->set3x3RowMajorf(fToXYZD50_3x3);
  111. return true;
  112. }
  113. bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const {
  114. memcpy(toXYZD50, fToXYZD50_3x3, 9*sizeof(float));
  115. return true;
  116. }
  117. void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, float src_to_dst[9]) const {
  118. dst->computeLazyDstFields();
  119. skcms_Matrix3x3 toXYZD50,
  120. fromXYZD50;
  121. memcpy(& toXYZD50, this-> fToXYZD50_3x3, 9*sizeof(float));
  122. memcpy(&fromXYZD50, dst ->fFromXYZD50_3x3, 9*sizeof(float));
  123. skcms_Matrix3x3 srcToDst = skcms_Matrix3x3_concat(&fromXYZD50, &toXYZD50);
  124. memcpy(src_to_dst, &srcToDst, 9*sizeof(float));
  125. }
  126. bool SkColorSpace::isSRGB() const {
  127. return sk_srgb_singleton() == this;
  128. }
  129. bool SkColorSpace::gammaCloseToSRGB() const {
  130. // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
  131. return memcmp(fTransferFn, &SkNamedTransferFn::kSRGB.g, 7*sizeof(float)) == 0;
  132. }
  133. bool SkColorSpace::gammaIsLinear() const {
  134. // Nearly-equal transfer functions were snapped at construction time, so just do an exact test
  135. return memcmp(fTransferFn, &SkNamedTransferFn::kLinear.g, 7*sizeof(float)) == 0;
  136. }
  137. sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() const {
  138. if (this->gammaIsLinear()) {
  139. return sk_ref_sp(const_cast<SkColorSpace*>(this));
  140. }
  141. skcms_Matrix3x3 gamut;
  142. this->toXYZD50(&gamut);
  143. return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut);
  144. }
  145. sk_sp<SkColorSpace> SkColorSpace::makeSRGBGamma() const {
  146. if (this->gammaCloseToSRGB()) {
  147. return sk_ref_sp(const_cast<SkColorSpace*>(this));
  148. }
  149. skcms_Matrix3x3 gamut;
  150. this->toXYZD50(&gamut);
  151. return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
  152. }
  153. sk_sp<SkColorSpace> SkColorSpace::makeColorSpin() const {
  154. skcms_Matrix3x3 spin = {{
  155. { 0, 0, 1 },
  156. { 1, 0, 0 },
  157. { 0, 1, 0 },
  158. }};
  159. skcms_Matrix3x3 toXYZ;
  160. this->toXYZD50(&toXYZ);
  161. skcms_Matrix3x3 spun = skcms_Matrix3x3_concat(&toXYZ, &spin);
  162. return sk_sp<SkColorSpace>(new SkColorSpace(fTransferFn, spun));
  163. }
  164. void SkColorSpace::toProfile(skcms_ICCProfile* profile) const {
  165. skcms_TransferFunction tf;
  166. skcms_Matrix3x3 toXYZD50;
  167. memcpy(&tf, fTransferFn, 7*sizeof(float));
  168. memcpy(&toXYZD50, fToXYZD50_3x3, 9*sizeof(float));
  169. skcms_Init (profile);
  170. skcms_SetTransferFunction(profile, &tf);
  171. skcms_SetXYZD50 (profile, &toXYZD50);
  172. }
  173. sk_sp<SkColorSpace> SkColorSpace::Make(const skcms_ICCProfile& profile) {
  174. // TODO: move below ≈sRGB test?
  175. if (!profile.has_toXYZD50 || !profile.has_trc) {
  176. return nullptr;
  177. }
  178. if (skcms_ApproximatelyEqualProfiles(&profile, skcms_sRGB_profile())) {
  179. return SkColorSpace::MakeSRGB();
  180. }
  181. // TODO: can we save this work and skip lazily inverting the matrix later?
  182. skcms_Matrix3x3 inv;
  183. if (!skcms_Matrix3x3_invert(&profile.toXYZD50, &inv)) {
  184. return nullptr;
  185. }
  186. // We can't work with tables or mismatched parametric curves,
  187. // but if they all look close enough to sRGB, that's fine.
  188. // TODO: should we maybe do this unconditionally to snap near-sRGB parametrics to sRGB?
  189. const skcms_Curve* trc = profile.trc;
  190. if (trc[0].table_entries != 0 ||
  191. trc[1].table_entries != 0 ||
  192. trc[2].table_entries != 0 ||
  193. 0 != memcmp(&trc[0].parametric, &trc[1].parametric, sizeof(trc[0].parametric)) ||
  194. 0 != memcmp(&trc[0].parametric, &trc[2].parametric, sizeof(trc[0].parametric)))
  195. {
  196. if (skcms_TRCs_AreApproximateInverse(&profile, skcms_sRGB_Inverse_TransferFunction())) {
  197. return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, profile.toXYZD50);
  198. }
  199. return nullptr;
  200. }
  201. return SkColorSpace::MakeRGB(profile.trc[0].parametric, profile.toXYZD50);
  202. }
  203. ///////////////////////////////////////////////////////////////////////////////////////////////////
  204. enum Version {
  205. k0_Version, // Initial version, header + flags for matrix and profile
  206. k1_Version, // Simple header (version tag) + 16 floats
  207. kCurrent_Version = k1_Version,
  208. };
  209. enum NamedColorSpace {
  210. kSRGB_NamedColorSpace,
  211. kAdobeRGB_NamedColorSpace,
  212. kSRGBLinear_NamedColorSpace,
  213. };
  214. enum NamedGamma {
  215. kLinear_NamedGamma,
  216. kSRGB_NamedGamma,
  217. k2Dot2_NamedGamma,
  218. };
  219. struct ColorSpaceHeader {
  220. // Flag values, only used by old (k0_Version) serialization
  221. static constexpr uint8_t kMatrix_Flag = 1 << 0;
  222. static constexpr uint8_t kICC_Flag = 1 << 1;
  223. static constexpr uint8_t kTransferFn_Flag = 1 << 3;
  224. uint8_t fVersion = kCurrent_Version;
  225. // Other fields are only used by k0_Version. Could be re-purposed in future versions.
  226. uint8_t fNamed = 0;
  227. uint8_t fGammaNamed = 0;
  228. uint8_t fFlags = 0;
  229. };
  230. size_t SkColorSpace::writeToMemory(void* memory) const {
  231. if (memory) {
  232. *((ColorSpaceHeader*) memory) = ColorSpaceHeader();
  233. memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
  234. memcpy(memory, fTransferFn, 7 * sizeof(float));
  235. memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
  236. memcpy(memory, fToXYZD50_3x3, 9 * sizeof(float));
  237. }
  238. return sizeof(ColorSpaceHeader) + 16 * sizeof(float);
  239. }
  240. sk_sp<SkData> SkColorSpace::serialize() const {
  241. sk_sp<SkData> data = SkData::MakeUninitialized(this->writeToMemory(nullptr));
  242. this->writeToMemory(data->writable_data());
  243. return data;
  244. }
  245. sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
  246. if (length < sizeof(ColorSpaceHeader)) {
  247. return nullptr;
  248. }
  249. ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
  250. data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
  251. length -= sizeof(ColorSpaceHeader);
  252. if (k1_Version == header.fVersion) {
  253. if (length < 16 * sizeof(float)) {
  254. return nullptr;
  255. }
  256. skcms_TransferFunction transferFn;
  257. memcpy(&transferFn, data, 7 * sizeof(float));
  258. data = SkTAddOffset<const void>(data, 7 * sizeof(float));
  259. skcms_Matrix3x3 toXYZ;
  260. memcpy(&toXYZ, data, 9 * sizeof(float));
  261. return SkColorSpace::MakeRGB(transferFn, toXYZ);
  262. } else if (k0_Version == header.fVersion) {
  263. if (0 == header.fFlags) {
  264. switch ((NamedColorSpace)header.fNamed) {
  265. case kSRGB_NamedColorSpace:
  266. return SkColorSpace::MakeSRGB();
  267. case kSRGBLinear_NamedColorSpace:
  268. return SkColorSpace::MakeSRGBLinear();
  269. case kAdobeRGB_NamedColorSpace:
  270. return SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2,
  271. SkNamedGamut::kAdobeRGB);
  272. }
  273. }
  274. auto make_named_tf = [=](const skcms_TransferFunction& tf) {
  275. if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
  276. return sk_sp<SkColorSpace>(nullptr);
  277. }
  278. // Version 0 matrix is row-major 3x4
  279. skcms_Matrix3x3 toXYZ;
  280. memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float));
  281. memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float));
  282. memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float));
  283. return SkColorSpace::MakeRGB(tf, toXYZ);
  284. };
  285. switch ((NamedGamma) header.fGammaNamed) {
  286. case kSRGB_NamedGamma:
  287. return make_named_tf(SkNamedTransferFn::kSRGB);
  288. case k2Dot2_NamedGamma:
  289. return make_named_tf(SkNamedTransferFn::k2Dot2);
  290. case kLinear_NamedGamma:
  291. return make_named_tf(SkNamedTransferFn::kLinear);
  292. default:
  293. break;
  294. }
  295. switch (header.fFlags) {
  296. case ColorSpaceHeader::kICC_Flag: {
  297. // Deprecated and unsupported code path
  298. return nullptr;
  299. }
  300. case ColorSpaceHeader::kTransferFn_Flag: {
  301. if (length < 19 * sizeof(float)) {
  302. return nullptr;
  303. }
  304. // Version 0 TF is in abcdefg order
  305. skcms_TransferFunction transferFn;
  306. transferFn.a = *(((const float*) data) + 0);
  307. transferFn.b = *(((const float*) data) + 1);
  308. transferFn.c = *(((const float*) data) + 2);
  309. transferFn.d = *(((const float*) data) + 3);
  310. transferFn.e = *(((const float*) data) + 4);
  311. transferFn.f = *(((const float*) data) + 5);
  312. transferFn.g = *(((const float*) data) + 6);
  313. data = SkTAddOffset<const void>(data, 7 * sizeof(float));
  314. // Version 0 matrix is row-major 3x4
  315. skcms_Matrix3x3 toXYZ;
  316. memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float));
  317. memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float));
  318. memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float));
  319. return SkColorSpace::MakeRGB(transferFn, toXYZ);
  320. }
  321. default:
  322. return nullptr;
  323. }
  324. } else {
  325. return nullptr;
  326. }
  327. }
  328. bool SkColorSpace::Equals(const SkColorSpace* x, const SkColorSpace* y) {
  329. if (x == y) {
  330. return true;
  331. }
  332. if (!x || !y) {
  333. return false;
  334. }
  335. if (x->hash() == y->hash()) {
  336. for (int i = 0; i < 7; i++) {
  337. SkASSERT(x-> fTransferFn[i] == y-> fTransferFn[i] && "Hash collsion");
  338. }
  339. for (int i = 0; i < 9; i++) {
  340. SkASSERT(x->fToXYZD50_3x3[i] == y->fToXYZD50_3x3[i] && "Hash collsion");
  341. }
  342. return true;
  343. }
  344. return false;
  345. }