ICCProfile.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. /*
  2. * Copyright (c) 2022, Nico Weber <thakis@chromium.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Endian.h>
  7. #include <LibGfx/ICCProfile.h>
  8. #include <math.h>
  9. #include <time.h>
  10. // V2 spec: https://color.org/specification/ICC.1-2001-04.pdf
  11. // V4 spec: https://color.org/specification/ICC.1-2022-05.pdf
  12. namespace Gfx::ICC {
  13. namespace {
  14. // ICC V4, 4.2 dateTimeNumber
  15. // "All the dateTimeNumber values in a profile shall be in Coordinated Universal Time [...]."
  16. struct DateTimeNumber {
  17. BigEndian<u16> year;
  18. BigEndian<u16> month;
  19. BigEndian<u16> day;
  20. BigEndian<u16> hour;
  21. BigEndian<u16> minutes;
  22. BigEndian<u16> seconds;
  23. };
  24. // ICC V4, 4.6 s15Fixed16Number
  25. using s15Fixed16Number = i32;
  26. // ICC V4, 4.14 XYZNumber
  27. struct XYZNumber {
  28. BigEndian<s15Fixed16Number> x;
  29. BigEndian<s15Fixed16Number> y;
  30. BigEndian<s15Fixed16Number> z;
  31. operator XYZ() const
  32. {
  33. return XYZ { x / (double)0x1'0000, y / (double)0x1'0000, z / (double)0x1'0000 };
  34. }
  35. };
  36. ErrorOr<time_t> parse_date_time_number(DateTimeNumber const& date_time)
  37. {
  38. // ICC V4, 4.2 dateTimeNumber
  39. // "Number of the month (1 to 12)"
  40. if (date_time.month < 1 || date_time.month > 12)
  41. return Error::from_string_literal("ICC::Profile: dateTimeNumber month out of bounds");
  42. // "Number of the day of the month (1 to 31)"
  43. if (date_time.day < 1 || date_time.day > 31)
  44. return Error::from_string_literal("ICC::Profile: dateTimeNumber day out of bounds");
  45. // "Number of hours (0 to 23)"
  46. if (date_time.hour > 23)
  47. return Error::from_string_literal("ICC::Profile: dateTimeNumber hour out of bounds");
  48. // "Number of minutes (0 to 59)"
  49. if (date_time.minutes > 59)
  50. return Error::from_string_literal("ICC::Profile: dateTimeNumber minutes out of bounds");
  51. // "Number of seconds (0 to 59)"
  52. // ICC profiles apparently can't be created during leap seconds (seconds would be 60 there, but the spec doesn't allow that).
  53. if (date_time.seconds > 59)
  54. return Error::from_string_literal("ICC::Profile: dateTimeNumber seconds out of bounds");
  55. struct tm tm = {};
  56. tm.tm_year = date_time.year - 1900;
  57. tm.tm_mon = date_time.month - 1;
  58. tm.tm_mday = date_time.day;
  59. tm.tm_hour = date_time.hour;
  60. tm.tm_min = date_time.minutes;
  61. tm.tm_sec = date_time.seconds;
  62. // timegm() doesn't read tm.tm_isdst, tm.tm_wday, and tm.tm_yday, no need to fill them in.
  63. time_t timestamp = timegm(&tm);
  64. if (timestamp == -1)
  65. return Error::from_string_literal("ICC::Profile: dateTimeNumber not representable as timestamp");
  66. return timestamp;
  67. }
  68. // ICC V4, 7.2 Profile header
  69. struct ICCHeader {
  70. BigEndian<u32> profile_size;
  71. BigEndian<u32> preferred_cmm_type;
  72. u8 profile_version_major;
  73. u8 profile_version_minor_bugfix;
  74. BigEndian<u16> profile_version_zero;
  75. BigEndian<DeviceClass> profile_device_class;
  76. BigEndian<ColorSpace> data_color_space;
  77. BigEndian<ColorSpace> profile_connection_space; // "PCS" in the spec.
  78. DateTimeNumber profile_creation_time;
  79. BigEndian<u32> profile_file_signature;
  80. BigEndian<u32> primary_platform;
  81. BigEndian<u32> profile_flags;
  82. BigEndian<u32> device_manufacturer;
  83. BigEndian<u32> device_model;
  84. BigEndian<u64> device_attributes;
  85. BigEndian<u32> rendering_intent;
  86. XYZNumber pcs_illuminant;
  87. BigEndian<u32> profile_creator;
  88. u8 profile_id[16];
  89. u8 reserved[28];
  90. };
  91. static_assert(sizeof(ICCHeader) == 128);
  92. Optional<PreferredCMMType> parse_preferred_cmm_type(ICCHeader const& header)
  93. {
  94. // ICC v4, 7.2.3 Preferred CMM type field
  95. // "This field may be used to identify the preferred CMM to be used.
  96. // If used, it shall match a CMM type signature registered in the ICC Tag Registry"
  97. // https://www.color.org/signatures2.xalter currently links to
  98. // https://www.color.org/registry/signature/TagRegistry-2021-03.pdf, which contains
  99. // some CMM signatures.
  100. // This requirement is often honored in practice, but not always. For example,
  101. // JPEGs exported in Adobe Lightroom contain profiles that set this to 'Lino',
  102. // which is not present in the "CMM Signatures" table in that PDF.
  103. // "If no preferred CMM is identified, this field shall be set to zero (00000000h)."
  104. if (header.preferred_cmm_type == 0)
  105. return {};
  106. return PreferredCMMType { header.preferred_cmm_type };
  107. }
  108. ErrorOr<Version> parse_version(ICCHeader const& header)
  109. {
  110. // ICC v4, 7.2.4 Profile version field
  111. if (header.profile_version_zero != 0)
  112. return Error::from_string_literal("ICC::Profile: Reserved version bytes not zero");
  113. return Version(header.profile_version_major, header.profile_version_minor_bugfix);
  114. }
  115. ErrorOr<DeviceClass> parse_device_class(ICCHeader const& header)
  116. {
  117. // ICC v4, 7.2.5 Profile/device class field
  118. switch (header.profile_device_class) {
  119. case DeviceClass::InputDevce:
  120. case DeviceClass::DisplayDevice:
  121. case DeviceClass::OutputDevice:
  122. case DeviceClass::DeviceLink:
  123. case DeviceClass::ColorSpace:
  124. case DeviceClass::Abstract:
  125. case DeviceClass::NamedColor:
  126. return header.profile_device_class;
  127. }
  128. return Error::from_string_literal("ICC::Profile: Invalid device class");
  129. }
  130. ErrorOr<ColorSpace> parse_color_space(ColorSpace color_space)
  131. {
  132. // ICC v4, Table 19 — Data colour space signatures
  133. switch (color_space) {
  134. case ColorSpace::nCIEXYZ:
  135. case ColorSpace::CIELAB:
  136. case ColorSpace::CIELUV:
  137. case ColorSpace::YCbCr:
  138. case ColorSpace::CIEYxy:
  139. case ColorSpace::RGB:
  140. case ColorSpace::Gray:
  141. case ColorSpace::HSV:
  142. case ColorSpace::HLS:
  143. case ColorSpace::CMYK:
  144. case ColorSpace::CMY:
  145. case ColorSpace::TwoColor:
  146. case ColorSpace::ThreeColor:
  147. case ColorSpace::FourColor:
  148. case ColorSpace::FiveColor:
  149. case ColorSpace::SixColor:
  150. case ColorSpace::SevenColor:
  151. case ColorSpace::EightColor:
  152. case ColorSpace::NineColor:
  153. case ColorSpace::TenColor:
  154. case ColorSpace::ElevenColor:
  155. case ColorSpace::TwelveColor:
  156. case ColorSpace::ThirteenColor:
  157. case ColorSpace::FourteenColor:
  158. case ColorSpace::FifteenColor:
  159. return color_space;
  160. }
  161. return Error::from_string_literal("ICC::Profile: Invalid color space");
  162. }
  163. ErrorOr<ColorSpace> parse_data_color_space(ICCHeader const& header)
  164. {
  165. // ICC v4, 7.2.6 Data colour space field
  166. return parse_color_space(header.data_color_space);
  167. }
  168. ErrorOr<ColorSpace> parse_connection_space(ICCHeader const& header)
  169. {
  170. // ICC v4, 7.2.7 PCS field
  171. // and Annex D
  172. auto space = TRY(parse_color_space(header.profile_connection_space));
  173. if (header.profile_device_class != DeviceClass::DeviceLink && (space != ColorSpace::PCSXYZ && space != ColorSpace::PCSLAB))
  174. return Error::from_string_literal("ICC::Profile: Invalid profile connection space: Non-PCS space on non-DeviceLink profile");
  175. return space;
  176. }
  177. ErrorOr<time_t> parse_creation_date_time(ICCHeader const& header)
  178. {
  179. // ICC v4, 7.2.8 Date and time field
  180. return parse_date_time_number(header.profile_creation_time);
  181. }
  182. ErrorOr<DeviceAttributes> parse_device_attributes(ICCHeader const& header)
  183. {
  184. // ICC v4, 7.2.14 Device attributes field
  185. // "4 to 31": "Reserved (set to binary zero)"
  186. if (header.device_attributes & 0xffff'fff0)
  187. return Error::from_string_literal("ICC::Profile: Device attributes reserved bits not set to 0");
  188. return DeviceAttributes { header.device_attributes };
  189. }
  190. ErrorOr<void> parse_file_signature(ICCHeader const& header)
  191. {
  192. // ICC v4, 7.2.9 Profile file signature field
  193. // "The profile file signature field shall contain the value “acsp” (61637370h) as a profile file signature."
  194. if (header.profile_file_signature != 0x61637370)
  195. return Error::from_string_literal("ICC::Profile: profile file signature not 'acsp'");
  196. return {};
  197. }
  198. Optional<DeviceManufacturer> parse_device_manufacturer(ICCHeader const& header)
  199. {
  200. // ICC v4, 7.2.12 Device manufacturer field
  201. // "This field may be used to identify a device manufacturer.
  202. // If used the signature shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org"
  203. // Device manufacturers can be looked up at https://www.color.org/signatureRegistry/index.xalter
  204. // For example: https://www.color.org/signatureRegistry/?entityEntry=APPL-4150504C
  205. // Some icc files use codes not in that registry. For example. D50_XYZ.icc from https://www.color.org/XYZprofiles.xalter
  206. // has its device manufacturer set to 'none', but https://www.color.org/signatureRegistry/?entityEntry=none-6E6F6E65 does not exist.
  207. // "If not used this field shall be set to zero (00000000h)."
  208. if (header.device_manufacturer == 0)
  209. return {};
  210. return DeviceManufacturer { header.device_manufacturer };
  211. }
  212. Optional<DeviceModel> parse_device_model(ICCHeader const& header)
  213. {
  214. // ICC v4, 7.2.13 Device model field
  215. // "This field may be used to identify a device model.
  216. // If used the signature shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org"
  217. // Device models can be looked up at https://www.color.org/signatureRegistry/deviceRegistry/index.xalter
  218. // For example: https://www.color.org/signatureRegistry/deviceRegistry/?entityEntry=7FD8-37464438
  219. // Some icc files use codes not in that registry. For example. D50_XYZ.icc from https://www.color.org/XYZprofiles.xalter
  220. // has its device model set to 'none', but https://www.color.org/signatureRegistry/deviceRegistry?entityEntry=none-6E6F6E65 does not exist.
  221. // "If not used this field shall be set to zero (00000000h)."
  222. if (header.device_model == 0)
  223. return {};
  224. return DeviceModel { header.device_model };
  225. }
  226. ErrorOr<RenderingIntent> parse_rendering_intent(ICCHeader const& header)
  227. {
  228. // ICC v4, 7.2.15 Rendering intent field
  229. switch (header.rendering_intent) {
  230. case 0:
  231. return RenderingIntent::Perceptual;
  232. case 1:
  233. return RenderingIntent::MediaRelativeColorimetric;
  234. case 2:
  235. return RenderingIntent::Saturation;
  236. case 3:
  237. return RenderingIntent::ICCAbsoluteColorimetric;
  238. }
  239. return Error::from_string_literal("ICC::Profile: Invalid rendering intent");
  240. }
  241. ErrorOr<XYZ> parse_pcs_illuminant(ICCHeader const& header)
  242. {
  243. // ICC v4, 7.2.16 PCS illuminant field
  244. XYZ xyz = (XYZ)header.pcs_illuminant;
  245. /// "The value, when rounded to four decimals, shall be X = 0,9642, Y = 1,0 and Z = 0,8249."
  246. if (round(xyz.x * 10'000) != 9'642 || round(xyz.y * 10'000) != 10'000 || round(xyz.z * 10'000) != 8'249)
  247. return Error::from_string_literal("ICC::Profile: Invalid pcs illuminant");
  248. return xyz;
  249. }
  250. Optional<Creator> parse_profile_creator(ICCHeader const& header)
  251. {
  252. // ICC v4, 7.2.17 Profile creator field
  253. // "This field may be used to identify the creator of the profile.
  254. // If used the signature should match the signature contained in the device manufacturer section of the ICC signature registry found at www.color.org."
  255. // This is not always true in practice.
  256. // For example, .icc files in /System/ColorSync/Profiles on macOS 12.6 set this to 'appl', which is a CMM signature, not a device signature (that one would be 'APPL').
  257. // "If not used this field shall be set to zero (00000000h)."
  258. if (header.profile_creator == 0)
  259. return {};
  260. return Creator { header.profile_creator };
  261. }
  262. template<size_t N>
  263. bool all_bytes_are_zero(const u8 (&bytes)[N])
  264. {
  265. for (u8 byte : bytes) {
  266. if (byte != 0)
  267. return false;
  268. }
  269. return true;
  270. }
  271. ErrorOr<Optional<Crypto::Hash::MD5::DigestType>> parse_profile_id(ICCHeader const& header, ReadonlyBytes icc_bytes)
  272. {
  273. // ICC v4, 7.2.18 Profile ID field
  274. // "A profile ID field value of zero (00h) shall indicate that a profile ID has not been calculated."
  275. if (all_bytes_are_zero(header.profile_id))
  276. return Optional<Crypto::Hash::MD5::DigestType> {};
  277. Crypto::Hash::MD5::DigestType id;
  278. static_assert(sizeof(id.data) == sizeof(header.profile_id));
  279. memcpy(id.data, header.profile_id, sizeof(id.data));
  280. auto computed_id = Profile::compute_id(icc_bytes);
  281. if (id != computed_id)
  282. return Error::from_string_literal("ICC::Profile: Invalid profile id");
  283. return id;
  284. }
  285. ErrorOr<void> parse_reserved(ICCHeader const& header)
  286. {
  287. // ICC v4, 7.2.19 Reserved field
  288. // "This field of the profile header is reserved for future ICC definition and shall be set to zero."
  289. if (!all_bytes_are_zero(header.reserved))
  290. return Error::from_string_literal("ICC::Profile: Reserved header bytes are not zero");
  291. return {};
  292. }
  293. }
  294. StringView device_class_name(DeviceClass device_class)
  295. {
  296. switch (device_class) {
  297. case DeviceClass::InputDevce:
  298. return "InputDevce"sv;
  299. case DeviceClass::DisplayDevice:
  300. return "DisplayDevice"sv;
  301. case DeviceClass::OutputDevice:
  302. return "OutputDevice"sv;
  303. case DeviceClass::DeviceLink:
  304. return "DeviceLink"sv;
  305. case DeviceClass::ColorSpace:
  306. return "ColorSpace"sv;
  307. case DeviceClass::Abstract:
  308. return "Abstract"sv;
  309. case DeviceClass::NamedColor:
  310. return "NamedColor"sv;
  311. }
  312. VERIFY_NOT_REACHED();
  313. }
  314. StringView data_color_space_name(ColorSpace color_space)
  315. {
  316. switch (color_space) {
  317. case ColorSpace::nCIEXYZ:
  318. return "nCIEXYZ"sv;
  319. case ColorSpace::CIELAB:
  320. return "CIELAB"sv;
  321. case ColorSpace::CIELUV:
  322. return "CIELUV"sv;
  323. case ColorSpace::YCbCr:
  324. return "YCbCr"sv;
  325. case ColorSpace::CIEYxy:
  326. return "CIEYxy"sv;
  327. case ColorSpace::RGB:
  328. return "RGB"sv;
  329. case ColorSpace::Gray:
  330. return "Gray"sv;
  331. case ColorSpace::HSV:
  332. return "HSV"sv;
  333. case ColorSpace::HLS:
  334. return "HLS"sv;
  335. case ColorSpace::CMYK:
  336. return "CMYK"sv;
  337. case ColorSpace::CMY:
  338. return "CMY"sv;
  339. case ColorSpace::TwoColor:
  340. return "2 color"sv;
  341. case ColorSpace::ThreeColor:
  342. return "3 color (other than XYZ, Lab, Luv, YCbCr, CIEYxy, RGB, HSV, HLS, CMY)"sv;
  343. case ColorSpace::FourColor:
  344. return "4 color (other than CMYK)"sv;
  345. case ColorSpace::FiveColor:
  346. return "5 color"sv;
  347. case ColorSpace::SixColor:
  348. return "6 color"sv;
  349. case ColorSpace::SevenColor:
  350. return "7 color"sv;
  351. case ColorSpace::EightColor:
  352. return "8 color"sv;
  353. case ColorSpace::NineColor:
  354. return "9 color"sv;
  355. case ColorSpace::TenColor:
  356. return "10 color"sv;
  357. case ColorSpace::ElevenColor:
  358. return "11 color"sv;
  359. case ColorSpace::TwelveColor:
  360. return "12 color"sv;
  361. case ColorSpace::ThirteenColor:
  362. return "13 color"sv;
  363. case ColorSpace::FourteenColor:
  364. return "14 color"sv;
  365. case ColorSpace::FifteenColor:
  366. return "15 color"sv;
  367. }
  368. VERIFY_NOT_REACHED();
  369. }
  370. StringView profile_connection_space_name(ColorSpace color_space)
  371. {
  372. switch (color_space) {
  373. case ColorSpace::PCSXYZ:
  374. return "PCSXYZ"sv;
  375. case ColorSpace::PCSLAB:
  376. return "PCSLAB"sv;
  377. default:
  378. return data_color_space_name(color_space);
  379. }
  380. }
  381. StringView rendering_intent_name(RenderingIntent rendering_intent)
  382. {
  383. switch (rendering_intent) {
  384. case RenderingIntent::Perceptual:
  385. return "Perceptual"sv;
  386. case RenderingIntent::MediaRelativeColorimetric:
  387. return "Media-relative colorimetric"sv;
  388. case RenderingIntent::Saturation:
  389. return "Saturation"sv;
  390. case RenderingIntent::ICCAbsoluteColorimetric:
  391. return "ICC-absolute colorimetric"sv;
  392. }
  393. VERIFY_NOT_REACHED();
  394. }
  395. Flags::Flags() = default;
  396. Flags::Flags(u32 bits)
  397. : m_bits(bits)
  398. {
  399. }
  400. DeviceAttributes::DeviceAttributes() = default;
  401. DeviceAttributes::DeviceAttributes(u64 bits)
  402. : m_bits(bits)
  403. {
  404. }
  405. ErrorOr<NonnullRefPtr<Profile>> Profile::try_load_from_externally_owned_memory(ReadonlyBytes bytes)
  406. {
  407. auto profile = adopt_ref(*new Profile());
  408. if (bytes.size() < sizeof(ICCHeader))
  409. return Error::from_string_literal("ICC::Profile: Not enough data for header");
  410. auto header = *bit_cast<ICCHeader const*>(bytes.data());
  411. TRY(parse_file_signature(header));
  412. profile->m_preferred_cmm_type = parse_preferred_cmm_type(header);
  413. profile->m_version = TRY(parse_version(header));
  414. profile->m_device_class = TRY(parse_device_class(header));
  415. profile->m_data_color_space = TRY(parse_data_color_space(header));
  416. profile->m_connection_space = TRY(parse_connection_space(header));
  417. profile->m_creation_timestamp = TRY(parse_creation_date_time(header));
  418. profile->m_flags = Flags { header.profile_flags };
  419. profile->m_device_manufacturer = parse_device_manufacturer(header);
  420. profile->m_device_model = parse_device_model(header);
  421. profile->m_device_attributes = TRY(parse_device_attributes(header));
  422. profile->m_rendering_intent = TRY(parse_rendering_intent(header));
  423. profile->m_pcs_illuminant = TRY(parse_pcs_illuminant(header));
  424. profile->m_creator = parse_profile_creator(header);
  425. profile->m_id = TRY(parse_profile_id(header, bytes));
  426. TRY(parse_reserved(header));
  427. return profile;
  428. }
  429. Crypto::Hash::MD5::DigestType Profile::compute_id(ReadonlyBytes bytes)
  430. {
  431. // ICC v4, 7.2.18 Profile ID field
  432. // "The Profile ID shall be calculated using the MD5 fingerprinting method as defined in Internet RFC 1321.
  433. // The entire profile, whose length is given by the size field in the header, with the
  434. // profile flags field (bytes 44 to 47, see 7.2.11),
  435. // rendering intent field (bytes 64 to 67, see 7.2.15),
  436. // and profile ID field (bytes 84 to 99)
  437. // in the profile header temporarily set to zeros (00h),
  438. // shall be used to calculate the ID."
  439. const u8 zero[16] = {};
  440. Crypto::Hash::MD5 md5;
  441. md5.update(bytes.slice(0, 44));
  442. md5.update(ReadonlyBytes { zero, 4 }); // profile flags field
  443. md5.update(bytes.slice(48, 64 - 48));
  444. md5.update(ReadonlyBytes { zero, 4 }); // rendering intent field
  445. md5.update(bytes.slice(68, 84 - 68));
  446. md5.update(ReadonlyBytes { zero, 16 }); // profile ID field
  447. md5.update(bytes.slice(100));
  448. return md5.digest();
  449. }
  450. }