Sfoglia il codice sorgente

LibGfx+icc: Print fields that are fourccs registered with the ICC

Namely:
- preferred CMM type
- device manufacturer
- device model
- profile creator

These all have in common that they can take arbitrary values, so I added
a FourCC class to deal with them, instead of using an enum class.
I made distinct types for each of them, so that they aren't accidentally
mixed up.
Nico Weber 2 anni fa
parent
commit
fdbe501d3e

+ 69 - 0
Userland/Libraries/LibGfx/ICCProfile.cpp

@@ -116,6 +116,25 @@ struct ICCHeader {
 };
 static_assert(sizeof(ICCHeader) == 128);
 
+Optional<PreferredCMMType> 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<Version> parse_version(ICCHeader const& header)
 {
     // ICC v4, 7.2.4 Profile version field
@@ -217,6 +236,38 @@ ErrorOr<void> parse_file_signature(ICCHeader const& header)
     return {};
 }
 
+Optional<DeviceManufacturer> 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<DeviceModel> 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<RenderingIntent> parse_rendering_intent(ICCHeader const& header)
 {
     // ICC v4, 7.2.15 Rendering intent field
@@ -245,6 +296,20 @@ ErrorOr<XYZ> parse_pcs_illuminant(ICCHeader const& header)
     return xyz;
 }
 
+Optional<Creator> 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<size_t N>
 bool all_bytes_are_zero(const u8 (&bytes)[N])
 {
@@ -410,15 +475,19 @@ ErrorOr<NonnullRefPtr<Profile>> Profile::try_load_from_externally_owned_memory(R
     auto header = *bit_cast<ICCHeader const*>(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));
 

+ 47 - 0
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<FourCCType type>
+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<FourCCType::PreferredCMMType>;     // ICC v4, "7.2.3 Preferred CMM type field"
+using DeviceManufacturer = DistinctFourCC<FourCCType::DeviceManufacturer>; // ICC v4, "7.2.12 Device manufacturer field"
+using DeviceModel = DistinctFourCC<FourCCType::DeviceModel>;               // ICC v4, "7.2.13 Device model field"
+using Creator = DistinctFourCC<FourCCType::Creator>;                       // 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<Profile> {
 public:
     static ErrorOr<NonnullRefPtr<Profile>> try_load_from_externally_owned_memory(ReadonlyBytes);
 
+    Optional<PreferredCMMType> 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<DeviceManufacturer> device_manufacturer() const { return m_device_manufacturer; }
+    Optional<DeviceModel> 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> creator() const { return m_creator; }
     Optional<Crypto::Hash::MD5::DigestType> const& id() const { return m_id; }
 
     static Crypto::Hash::MD5::DigestType compute_id(ReadonlyBytes);
 
 private:
+    Optional<PreferredCMMType> 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<DeviceManufacturer> m_device_manufacturer;
+    Optional<DeviceModel> m_device_model;
     DeviceAttributes m_device_attributes;
     RenderingIntent m_rendering_intent;
     XYZ m_pcs_illuminant;
+    Optional<Creator> m_creator;
     Optional<Crypto::Hash::MD5::DigestType> m_id;
 };
 
 }
 
 namespace AK {
+template<Gfx::ICC::FourCCType Type>
+struct Formatter<Gfx::ICC::DistinctFourCC<Type>> : StandardFormatter {
+    ErrorOr<void> format(FormatBuilder& builder, Gfx::ICC::DistinctFourCC<Type> 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<Gfx::ICC::Version> : Formatter<FormatString> {
     ErrorOr<void> format(FormatBuilder& builder, Gfx::ICC::Version const& version)

+ 5 - 0
Userland/Utilities/icc.cpp

@@ -31,6 +31,7 @@ ErrorOr<int> 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<int> 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<int> 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;