diff --git a/Userland/Libraries/LibGfx/ICCProfile.cpp b/Userland/Libraries/LibGfx/ICCProfile.cpp index 9d807fecd0..fc703f72cc 100644 --- a/Userland/Libraries/LibGfx/ICCProfile.cpp +++ b/Userland/Libraries/LibGfx/ICCProfile.cpp @@ -116,6 +116,25 @@ struct ICCHeader { }; static_assert(sizeof(ICCHeader) == 128); +Optional parse_preferred_cmm_type(ICCHeader const& header) +{ + // ICC v4, 7.2.3 Preferred CMM type field + + // "This field may be used to identify the preferred CMM to be used. + // If used, it shall match a CMM type signature registered in the ICC Tag Registry" + // https://www.color.org/signatures2.xalter currently links to + // https://www.color.org/registry/signature/TagRegistry-2021-03.pdf, which contains + // some CMM signatures. + // This requirement is often honored in practice, but not always. For example, + // JPEGs exported in Adobe Lightroom contain profiles that set this to 'Lino', + // which is not present in the "CMM Signatures" table in that PDF. + + // "If no preferred CMM is identified, this field shall be set to zero (00000000h)." + if (header.preferred_cmm_type == 0) + return {}; + return PreferredCMMType { header.preferred_cmm_type }; +} + ErrorOr parse_version(ICCHeader const& header) { // ICC v4, 7.2.4 Profile version field @@ -217,6 +236,38 @@ ErrorOr parse_file_signature(ICCHeader const& header) return {}; } +Optional parse_device_manufacturer(ICCHeader const& header) +{ + // ICC v4, 7.2.12 Device manufacturer field + // "This field may be used to identify a device manufacturer. + // If used the signature shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org" + // Device manufacturers can be looked up at https://www.color.org/signatureRegistry/index.xalter + // For example: https://www.color.org/signatureRegistry/?entityEntry=APPL-4150504C + // Some icc files use codes not in that registry. For example. D50_XYZ.icc from https://www.color.org/XYZprofiles.xalter + // has its device manufacturer set to 'none', but https://www.color.org/signatureRegistry/?entityEntry=none-6E6F6E65 does not exist. + + // "If not used this field shall be set to zero (00000000h)." + if (header.device_manufacturer == 0) + return {}; + return DeviceManufacturer { header.device_manufacturer }; +} + +Optional parse_device_model(ICCHeader const& header) +{ + // ICC v4, 7.2.13 Device model field + // "This field may be used to identify a device model. + // If used the signature shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org" + // Device models can be looked up at https://www.color.org/signatureRegistry/deviceRegistry/index.xalter + // For example: https://www.color.org/signatureRegistry/deviceRegistry/?entityEntry=7FD8-37464438 + // Some icc files use codes not in that registry. For example. D50_XYZ.icc from https://www.color.org/XYZprofiles.xalter + // has its device model set to 'none', but https://www.color.org/signatureRegistry/deviceRegistry?entityEntry=none-6E6F6E65 does not exist. + + // "If not used this field shall be set to zero (00000000h)." + if (header.device_model == 0) + return {}; + return DeviceModel { header.device_model }; +} + ErrorOr parse_rendering_intent(ICCHeader const& header) { // ICC v4, 7.2.15 Rendering intent field @@ -245,6 +296,20 @@ ErrorOr parse_pcs_illuminant(ICCHeader const& header) return xyz; } +Optional parse_profile_creator(ICCHeader const& header) +{ + // ICC v4, 7.2.17 Profile creator field + // "This field may be used to identify the creator of the profile. + // If used the signature should match the signature contained in the device manufacturer section of the ICC signature registry found at www.color.org." + // This is not always true in practice. + // 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'). + + // "If not used this field shall be set to zero (00000000h)." + if (header.profile_creator == 0) + return {}; + return Creator { header.profile_creator }; +} + template bool all_bytes_are_zero(const u8 (&bytes)[N]) { @@ -410,15 +475,19 @@ ErrorOr> Profile::try_load_from_externally_owned_memory(R auto header = *bit_cast(bytes.data()); TRY(parse_file_signature(header)); + profile->m_preferred_cmm_type = parse_preferred_cmm_type(header); profile->m_version = TRY(parse_version(header)); profile->m_device_class = TRY(parse_device_class(header)); profile->m_data_color_space = TRY(parse_data_color_space(header)); profile->m_connection_space = TRY(parse_connection_space(header)); profile->m_creation_timestamp = TRY(parse_creation_date_time(header)); profile->m_flags = Flags { header.profile_flags }; + profile->m_device_manufacturer = parse_device_manufacturer(header); + profile->m_device_model = parse_device_model(header); profile->m_device_attributes = TRY(parse_device_attributes(header)); profile->m_rendering_intent = TRY(parse_rendering_intent(header)); profile->m_pcs_illuminant = TRY(parse_pcs_illuminant(header)); + profile->m_creator = parse_profile_creator(header); profile->m_id = TRY(parse_profile_id(header, bytes)); TRY(parse_reserved(header)); diff --git a/Userland/Libraries/LibGfx/ICCProfile.h b/Userland/Libraries/LibGfx/ICCProfile.h index ea09084968..162ad260d0 100644 --- a/Userland/Libraries/LibGfx/ICCProfile.h +++ b/Userland/Libraries/LibGfx/ICCProfile.h @@ -15,6 +15,31 @@ namespace Gfx::ICC { +// The ICC spec uses FourCCs for many different things. +// This is used to give FourCCs for different roles distinct types, so that they can only be compared to the correct constants. +// (FourCCs that have only a small and fixed set of values should use an enum class instead, see e.g. DeviceClass and ColorSpace below.) +enum class FourCCType { + PreferredCMMType, + DeviceManufacturer, + DeviceModel, + Creator, +}; + +template +struct DistinctFourCC { + u32 value; + + char c0() const { return value >> 24; } + char c1() const { return (value >> 16) & 0xff; } + char c2() const { return (value >> 8) & 0xff; } + char c3() const { return value & 0xff; } +}; + +using PreferredCMMType = DistinctFourCC; // ICC v4, "7.2.3 Preferred CMM type field" +using DeviceManufacturer = DistinctFourCC; // ICC v4, "7.2.12 Device manufacturer field" +using DeviceModel = DistinctFourCC; // ICC v4, "7.2.13 Device model field" +using Creator = DistinctFourCC; // ICC v4, "7.2.17 Profile creator field" + // ICC v4, 7.2.4 Profile version field class Version { public: @@ -181,6 +206,7 @@ class Profile : public RefCounted { public: static ErrorOr> try_load_from_externally_owned_memory(ReadonlyBytes); + Optional preferred_cmm_type() const { return m_preferred_cmm_type; } Version version() const { return m_version; } DeviceClass device_class() const { return m_device_class; } ColorSpace data_color_space() const { return m_data_color_space; } @@ -190,29 +216,50 @@ public: time_t creation_timestamp() const { return m_creation_timestamp; } Flags flags() const { return m_flags; } + Optional device_manufacturer() const { return m_device_manufacturer; } + Optional device_model() const { return m_device_model; } DeviceAttributes device_attributes() const { return m_device_attributes; } RenderingIntent rendering_intent() const { return m_rendering_intent; } XYZ const& pcs_illuminant() const { return m_pcs_illuminant; } + Optional creator() const { return m_creator; } Optional const& id() const { return m_id; } static Crypto::Hash::MD5::DigestType compute_id(ReadonlyBytes); private: + Optional m_preferred_cmm_type; Version m_version; DeviceClass m_device_class; ColorSpace m_data_color_space; ColorSpace m_connection_space; time_t m_creation_timestamp; Flags m_flags; + Optional m_device_manufacturer; + Optional m_device_model; DeviceAttributes m_device_attributes; RenderingIntent m_rendering_intent; XYZ m_pcs_illuminant; + Optional m_creator; Optional m_id; }; } namespace AK { +template +struct Formatter> : StandardFormatter { + ErrorOr format(FormatBuilder& builder, Gfx::ICC::DistinctFourCC const& four_cc) + { + TRY(builder.put_padding('\'', 1)); + TRY(builder.put_padding(four_cc.c0(), 1)); + TRY(builder.put_padding(four_cc.c1(), 1)); + TRY(builder.put_padding(four_cc.c2(), 1)); + TRY(builder.put_padding(four_cc.c3(), 1)); + TRY(builder.put_padding('\'', 1)); + return {}; + } +}; + template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Gfx::ICC::Version const& version) diff --git a/Userland/Utilities/icc.cpp b/Userland/Utilities/icc.cpp index 6c75f4b6a9..708068c28d 100644 --- a/Userland/Utilities/icc.cpp +++ b/Userland/Utilities/icc.cpp @@ -31,6 +31,7 @@ ErrorOr serenity_main(Main::Arguments arguments) auto icc_file = TRY(Core::MappedFile::map(icc_path)); auto profile = TRY(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_file->bytes())); + out_optional("preferred CMM type", profile->preferred_cmm_type()); outln("version: {}", profile->version()); outln("device class: {}", Gfx::ICC::device_class_name(profile->device_class())); outln("data color space: {}", Gfx::ICC::data_color_space_name(profile->data_color_space())); @@ -46,6 +47,9 @@ ErrorOr serenity_main(Main::Arguments arguments) if (auto color_management_module_bits = flags.color_management_module_bits()) outln(" CMM bits: 0x{:04x}", color_management_module_bits); + out_optional("device manufacturer", profile->device_manufacturer()); + out_optional("device model", profile->device_model()); + auto device_attributes = profile->device_attributes(); outln("device attributes: 0x{:016x}", device_attributes.bits()); outln(" media is {}, {}, {}, {}", @@ -59,6 +63,7 @@ ErrorOr serenity_main(Main::Arguments arguments) outln("rendering intent: {}", Gfx::ICC::rendering_intent_name(profile->rendering_intent())); outln("pcs illuminant: {}", profile->pcs_illuminant()); + out_optional("creator", profile->creator()); out_optional("id", profile->id()); return 0;