icc.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. /*
  2. * Copyright (c) 2022-2023, Nico Weber <thakis@chromium.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/String.h>
  7. #include <AK/StringView.h>
  8. #include <LibCore/ArgsParser.h>
  9. #include <LibCore/DateTime.h>
  10. #include <LibCore/MappedFile.h>
  11. #include <LibGfx/ICC/Profile.h>
  12. #include <LibGfx/ICC/Tags.h>
  13. #include <LibGfx/ImageDecoder.h>
  14. #include <LibVideo/Color/CodingIndependentCodePoints.h>
  15. template<class T>
  16. static ErrorOr<String> hyperlink(URL const& target, T const& label)
  17. {
  18. return String::formatted("\033]8;;{}\033\\{}\033]8;;\033\\", target, label);
  19. }
  20. template<class T>
  21. static void out_optional(char const* label, Optional<T> const& optional)
  22. {
  23. out("{}: ", label);
  24. if (optional.has_value())
  25. outln("{}", *optional);
  26. else
  27. outln("(not set)");
  28. }
  29. ErrorOr<int> serenity_main(Main::Arguments arguments)
  30. {
  31. Core::ArgsParser args_parser;
  32. static StringView path;
  33. args_parser.add_positional_argument(path, "Path to ICC profile or to image containing ICC profile", "FILE");
  34. args_parser.parse(arguments);
  35. auto file = TRY(Core::MappedFile::map(path));
  36. ReadonlyBytes icc_bytes;
  37. auto decoder = Gfx::ImageDecoder::try_create_for_raw_bytes(file->bytes());
  38. if (decoder) {
  39. if (auto embedded_icc_bytes = TRY(decoder->icc_data()); embedded_icc_bytes.has_value()) {
  40. icc_bytes = *embedded_icc_bytes;
  41. } else {
  42. outln("image contains no embedded ICC profile");
  43. return 1;
  44. }
  45. } else {
  46. icc_bytes = file->bytes();
  47. }
  48. auto profile = TRY(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_bytes));
  49. outln(" size: {} bytes", profile->on_disk_size());
  50. out_optional(" preferred CMM type", profile->preferred_cmm_type());
  51. outln(" version: {}", profile->version());
  52. outln(" device class: {}", Gfx::ICC::device_class_name(profile->device_class()));
  53. outln(" data color space: {}", Gfx::ICC::data_color_space_name(profile->data_color_space()));
  54. outln(" connection space: {}", Gfx::ICC::profile_connection_space_name(profile->connection_space()));
  55. outln("creation date and time: {}", Core::DateTime::from_timestamp(profile->creation_timestamp()));
  56. out_optional(" primary platform", profile->primary_platform().map([](auto platform) { return primary_platform_name(platform); }));
  57. auto flags = profile->flags();
  58. outln(" flags: 0x{:08x}", flags.bits());
  59. outln(" - {}embedded in file", flags.is_embedded_in_file() ? "" : "not ");
  60. outln(" - can{} be used independently of embedded color data", flags.can_be_used_independently_of_embedded_color_data() ? "" : "not");
  61. if (auto unknown_icc_bits = flags.icc_bits() & ~Gfx::ICC::Flags::KnownBitsMask)
  62. outln(" other unknown ICC bits: 0x{:04x}", unknown_icc_bits);
  63. if (auto color_management_module_bits = flags.color_management_module_bits())
  64. outln(" CMM bits: 0x{:04x}", color_management_module_bits);
  65. out_optional(" device manufacturer", TRY(profile->device_manufacturer().map([](auto device_manufacturer) {
  66. return hyperlink(device_manufacturer_url(device_manufacturer), device_manufacturer);
  67. })));
  68. out_optional(" device model", TRY(profile->device_model().map([](auto device_model) {
  69. return hyperlink(device_model_url(device_model), device_model);
  70. })));
  71. auto device_attributes = profile->device_attributes();
  72. outln(" device attributes: 0x{:016x}", device_attributes.bits());
  73. outln(" media is:");
  74. outln(" - {}",
  75. device_attributes.media_reflectivity() == Gfx::ICC::DeviceAttributes::MediaReflectivity::Reflective ? "reflective" : "transparent");
  76. outln(" - {}",
  77. device_attributes.media_glossiness() == Gfx::ICC::DeviceAttributes::MediaGlossiness::Glossy ? "glossy" : "matte");
  78. outln(" - {}",
  79. device_attributes.media_polarity() == Gfx::ICC::DeviceAttributes::MediaPolarity::Positive ? "of positive polarity" : "of negative polarity");
  80. outln(" - {}",
  81. device_attributes.media_color() == Gfx::ICC::DeviceAttributes::MediaColor::Colored ? "colored" : "black and white");
  82. VERIFY((flags.icc_bits() & ~Gfx::ICC::DeviceAttributes::KnownBitsMask) == 0);
  83. if (auto vendor_bits = device_attributes.vendor_bits())
  84. outln(" vendor bits: 0x{:08x}", vendor_bits);
  85. outln(" rendering intent: {}", Gfx::ICC::rendering_intent_name(profile->rendering_intent()));
  86. outln(" pcs illuminant: {}", profile->pcs_illuminant());
  87. out_optional(" creator", profile->creator());
  88. out_optional(" id", profile->id());
  89. size_t profile_disk_size = icc_bytes.size();
  90. if (profile_disk_size != profile->on_disk_size()) {
  91. VERIFY(profile_disk_size > profile->on_disk_size());
  92. outln("{} trailing bytes after profile data", profile_disk_size - profile->on_disk_size());
  93. }
  94. outln("");
  95. outln("tags:");
  96. HashMap<Gfx::ICC::TagData*, Gfx::ICC::TagSignature> tag_data_to_first_signature;
  97. profile->for_each_tag([&tag_data_to_first_signature](auto tag_signature, auto tag_data) {
  98. if (auto name = tag_signature_spec_name(tag_signature); name.has_value())
  99. out("{} ({}): ", *name, tag_signature);
  100. else
  101. out("Unknown tag ({}): ", tag_signature);
  102. outln("type {}, offset {}, size {}", tag_data->type(), tag_data->offset(), tag_data->size());
  103. // Print tag data only the first time it's seen.
  104. // (Different sigatures can refer to the same data.)
  105. auto it = tag_data_to_first_signature.find(tag_data);
  106. if (it != tag_data_to_first_signature.end()) {
  107. outln(" (see {} above)", it->value);
  108. return;
  109. }
  110. tag_data_to_first_signature.set(tag_data, tag_signature);
  111. if (tag_data->type() == Gfx::ICC::CicpTagData::Type) {
  112. auto& cicp = static_cast<Gfx::ICC::CicpTagData&>(*tag_data);
  113. outln(" color primaries: {} - {}", cicp.color_primaries(),
  114. Video::color_primaries_to_string((Video::ColorPrimaries)cicp.color_primaries()));
  115. outln(" transfer characteristics: {} - {}", cicp.transfer_characteristics(),
  116. Video::transfer_characteristics_to_string((Video::TransferCharacteristics)cicp.transfer_characteristics()));
  117. outln(" matrix coefficients: {} - {}", cicp.matrix_coefficients(),
  118. Video::matrix_coefficients_to_string((Video::MatrixCoefficients)cicp.matrix_coefficients()));
  119. outln(" video full range flag: {}", cicp.video_full_range_flag());
  120. } else if (tag_data->type() == Gfx::ICC::CurveTagData::Type) {
  121. auto& curve = static_cast<Gfx::ICC::CurveTagData&>(*tag_data);
  122. if (curve.values().is_empty()) {
  123. outln(" identity curve");
  124. } else if (curve.values().size() == 1) {
  125. outln(" gamma: {}", FixedPoint<8, u16>::create_raw(curve.values()[0]));
  126. } else {
  127. // FIXME: Maybe print the actual points if -v is passed?
  128. outln(" curve with {} points", curve.values().size());
  129. }
  130. } else if (tag_data->type() == Gfx::ICC::Lut16TagData::Type) {
  131. auto& lut16 = static_cast<Gfx::ICC::Lut16TagData&>(*tag_data);
  132. outln(" input table: {} channels x {} entries", lut16.number_of_input_channels(), lut16.number_of_input_table_entries());
  133. outln(" output table: {} channels x {} entries", lut16.number_of_output_channels(), lut16.number_of_output_table_entries());
  134. outln(" color lookup table: {} grid points, {} total entries", lut16.number_of_clut_grid_points(), lut16.clut_values().size());
  135. auto const& e = lut16.e_matrix();
  136. outln(" e = [ {}, {}, {},", e[0], e[1], e[2]);
  137. outln(" {}, {}, {},", e[3], e[4], e[5]);
  138. outln(" {}, {}, {} ]", e[6], e[7], e[8]);
  139. } else if (tag_data->type() == Gfx::ICC::Lut8TagData::Type) {
  140. auto& lut8 = static_cast<Gfx::ICC::Lut8TagData&>(*tag_data);
  141. outln(" input table: {} channels x {} entries", lut8.number_of_input_channels(), lut8.number_of_input_table_entries());
  142. outln(" output table: {} channels x {} entries", lut8.number_of_output_channels(), lut8.number_of_output_table_entries());
  143. outln(" color lookup table: {} grid points, {} total entries", lut8.number_of_clut_grid_points(), lut8.clut_values().size());
  144. auto const& e = lut8.e_matrix();
  145. outln(" e = [ {}, {}, {},", e[0], e[1], e[2]);
  146. outln(" {}, {}, {},", e[3], e[4], e[5]);
  147. outln(" {}, {}, {} ]", e[6], e[7], e[8]);
  148. } else if (tag_data->type() == Gfx::ICC::LutAToBTagData::Type) {
  149. auto& a_to_b = static_cast<Gfx::ICC::LutAToBTagData&>(*tag_data);
  150. outln(" {} input channels, {} output channels", a_to_b.number_of_input_channels(), a_to_b.number_of_output_channels());
  151. if (auto const& optional_clut = a_to_b.clut(); optional_clut.has_value()) {
  152. auto const& clut = optional_clut.value();
  153. outln(" color lookup table: {} grid points, {}",
  154. MUST(String::join(" x "sv, clut.number_of_grid_points_in_dimension)),
  155. MUST(clut.values.visit(
  156. [](Vector<u8> const& v) { return String::formatted("{} u8 entries", v.size()); },
  157. [](Vector<u16> const& v) { return String::formatted("{} u16 entries", v.size()); })));
  158. } else {
  159. outln(" color lookup table: (not set)");
  160. }
  161. if (auto const& optional_e = a_to_b.e_matrix(); optional_e.has_value()) {
  162. auto const& e = optional_e.value();
  163. outln(" e = [ {}, {}, {}, {},", e[0], e[1], e[2], e[9]);
  164. outln(" {}, {}, {}, {},", e[3], e[4], e[5], e[10]);
  165. outln(" {}, {}, {}, {} ]", e[6], e[7], e[8], e[11]);
  166. } else {
  167. outln(" e = (not set)");
  168. }
  169. } else if (tag_data->type() == Gfx::ICC::LutBToATagData::Type) {
  170. auto& b_to_a = static_cast<Gfx::ICC::LutBToATagData&>(*tag_data);
  171. outln(" {} input channels, {} output channels", b_to_a.number_of_input_channels(), b_to_a.number_of_output_channels());
  172. if (auto const& optional_e = b_to_a.e_matrix(); optional_e.has_value()) {
  173. auto const& e = optional_e.value();
  174. outln(" e = [ {}, {}, {}, {},", e[0], e[1], e[2], e[9]);
  175. outln(" {}, {}, {}, {},", e[3], e[4], e[5], e[10]);
  176. outln(" {}, {}, {}, {} ]", e[6], e[7], e[8], e[11]);
  177. } else {
  178. outln(" e = (not set)");
  179. }
  180. if (auto const& optional_clut = b_to_a.clut(); optional_clut.has_value()) {
  181. auto const& clut = optional_clut.value();
  182. outln(" color lookup table: {} grid points, {}",
  183. MUST(String::join(" x "sv, clut.number_of_grid_points_in_dimension)),
  184. MUST(clut.values.visit(
  185. [](Vector<u8> const& v) { return String::formatted("{} u8 entries", v.size()); },
  186. [](Vector<u16> const& v) { return String::formatted("{} u16 entries", v.size()); })));
  187. } else {
  188. outln(" color lookup table: (not set)");
  189. }
  190. } else if (tag_data->type() == Gfx::ICC::MultiLocalizedUnicodeTagData::Type) {
  191. auto& multi_localized_unicode = static_cast<Gfx::ICC::MultiLocalizedUnicodeTagData&>(*tag_data);
  192. for (auto& record : multi_localized_unicode.records()) {
  193. outln(" {:c}{:c}/{:c}{:c}: \"{}\"",
  194. record.iso_639_1_language_code >> 8, record.iso_639_1_language_code & 0xff,
  195. record.iso_3166_1_country_code >> 8, record.iso_3166_1_country_code & 0xff,
  196. record.text);
  197. }
  198. } else if (tag_data->type() == Gfx::ICC::NamedColor2TagData::Type) {
  199. auto& named_colors = static_cast<Gfx::ICC::NamedColor2TagData&>(*tag_data);
  200. outln(" vendor specific flag: 0x{:08x}", named_colors.vendor_specific_flag());
  201. outln(" common name prefix: \"{}\"", named_colors.prefix());
  202. outln(" common name suffix: \"{}\"", named_colors.suffix());
  203. outln(" {} colors:", named_colors.size());
  204. for (size_t i = 0; i < min(named_colors.size(), 5u); ++i) {
  205. const auto& pcs = named_colors.pcs_coordinates(i);
  206. // FIXME: Display decoded values? (See ICC v4 6.3.4.2 and 10.8.)
  207. out(" \"{}\", PCS coordinates: 0x{:04x} 0x{:04x} 0x{:04x}", MUST(named_colors.color_name(i)), pcs.xyz.x, pcs.xyz.y, pcs.xyz.z);
  208. if (auto number_of_device_coordinates = named_colors.number_of_device_coordinates(); number_of_device_coordinates > 0) {
  209. out(", device coordinates:");
  210. for (size_t j = 0; j < number_of_device_coordinates; ++j)
  211. out(" 0x{:04x}", named_colors.device_coordinates(i)[j]);
  212. }
  213. outln();
  214. }
  215. if (named_colors.size() > 5u)
  216. outln(" ...");
  217. } else if (tag_data->type() == Gfx::ICC::ParametricCurveTagData::Type) {
  218. auto& parametric_curve = static_cast<Gfx::ICC::ParametricCurveTagData&>(*tag_data);
  219. switch (parametric_curve.function_type()) {
  220. case Gfx::ICC::ParametricCurveTagData::FunctionType::Type0:
  221. outln(" Y = X**{}", parametric_curve.g());
  222. break;
  223. case Gfx::ICC::ParametricCurveTagData::FunctionType::Type1:
  224. outln(" Y = ({}*X + {})**{} if X >= -{}/{}",
  225. parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.b(), parametric_curve.a());
  226. outln(" Y = 0 else");
  227. break;
  228. case Gfx::ICC::ParametricCurveTagData::FunctionType::Type2:
  229. outln(" Y = ({}*X + {})**{} + {} if X >= -{}/{}",
  230. parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.c(), parametric_curve.b(), parametric_curve.a());
  231. outln(" Y = {} else", parametric_curve.c());
  232. break;
  233. case Gfx::ICC::ParametricCurveTagData::FunctionType::Type3:
  234. outln(" Y = ({}*X + {})**{} if X >= {}",
  235. parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.d());
  236. outln(" Y = {}*X else", parametric_curve.c());
  237. break;
  238. case Gfx::ICC::ParametricCurveTagData::FunctionType::Type4:
  239. outln(" Y = ({}*X + {})**{} + {} if X >= {}",
  240. parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.e(), parametric_curve.d());
  241. outln(" Y = {}*X + {} else", parametric_curve.c(), parametric_curve.f());
  242. break;
  243. }
  244. } else if (tag_data->type() == Gfx::ICC::S15Fixed16ArrayTagData::Type) {
  245. // This tag can contain arbitrarily many fixed-point numbers, but in practice it's
  246. // exclusively used for the 'chad' tag, where it always contains 9 values that
  247. // represent a 3x3 matrix. So print the values in groups of 3.
  248. auto& fixed_array = static_cast<Gfx::ICC::S15Fixed16ArrayTagData&>(*tag_data);
  249. out(" [");
  250. int i = 0;
  251. for (auto value : fixed_array.values()) {
  252. if (i > 0) {
  253. out(",");
  254. if (i % 3 == 0) {
  255. outln();
  256. out(" ");
  257. }
  258. }
  259. out(" {}", value);
  260. i++;
  261. }
  262. outln(" ]");
  263. } else if (tag_data->type() == Gfx::ICC::SignatureTagData::Type) {
  264. auto& signature = static_cast<Gfx::ICC::SignatureTagData&>(*tag_data);
  265. // FIXME: For colorimetricIntentImageStateTag, interpret signature according to ICC v4 Table 26
  266. // FIXME: For perceptualRenderingIntentGamutTag, interpret signature according to ICC v4 Table 27
  267. // FIXME: For saturationRenderingIntentGamutTag, interpret signature according to ICC v4 Table 28
  268. // FIXME: For technologyTag, interpret signature according to ICC v4 Table 29
  269. outln(" signature: '{:c}{:c}{:c}{:c}' / 0x{:08x}",
  270. signature.signature() >> 24, (signature.signature() >> 16) & 0xff, (signature.signature() >> 8) & 0xff, signature.signature() & 0xff,
  271. signature.signature());
  272. } else if (tag_data->type() == Gfx::ICC::TextDescriptionTagData::Type) {
  273. auto& text_description = static_cast<Gfx::ICC::TextDescriptionTagData&>(*tag_data);
  274. outln(" ascii: \"{}\"", text_description.ascii_description());
  275. out_optional(" unicode", MUST(text_description.unicode_description().map([](auto description) { return String::formatted("\"{}\"", description); })));
  276. outln(" unicode language code: 0x{}", text_description.unicode_language_code());
  277. out_optional(" macintosh", MUST(text_description.macintosh_description().map([](auto description) { return String::formatted("\"{}\"", description); })));
  278. } else if (tag_data->type() == Gfx::ICC::TextTagData::Type) {
  279. outln(" text: \"{}\"", static_cast<Gfx::ICC::TextTagData&>(*tag_data).text());
  280. } else if (tag_data->type() == Gfx::ICC::XYZTagData::Type) {
  281. for (auto& xyz : static_cast<Gfx::ICC::XYZTagData&>(*tag_data).xyzs())
  282. outln(" {}", xyz);
  283. }
  284. });
  285. return 0;
  286. }