From 782ab0a11f650e8b44341855d9cd93ac489e1900 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 27 Dec 2022 13:37:37 -0500 Subject: [PATCH] LibGfx: Read profile creation time from ICCProfile header --- Userland/Libraries/LibGfx/ICCProfile.cpp | 67 +++++++++++++++++++++--- Userland/Libraries/LibGfx/ICCProfile.h | 2 + 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/Userland/Libraries/LibGfx/ICCProfile.cpp b/Userland/Libraries/LibGfx/ICCProfile.cpp index 15621e4124..c1e0fa3ae3 100644 --- a/Userland/Libraries/LibGfx/ICCProfile.cpp +++ b/Userland/Libraries/LibGfx/ICCProfile.cpp @@ -6,6 +6,7 @@ #include #include +#include // V2 spec: https://color.org/specification/ICC.1-2001-04.pdf // V4 spec: https://color.org/specification/ICC.1-2022-05.pdf @@ -14,6 +15,58 @@ namespace Gfx::ICC { namespace { +// ICC V4, 4.2 dateTimeNumber +// "All the dateTimeNumber values in a profile shall be in Coordinated Universal Time [...]." +struct DateTimeNumber { + BigEndian year; + BigEndian month; + BigEndian day; + BigEndian hour; + BigEndian minutes; + BigEndian seconds; +}; + +ErrorOr parse_date_time_number(DateTimeNumber const& date_time) +{ + // ICC V4, 4.2 dateTimeNumber + + // "Number of the month (1 to 12)" + if (date_time.month < 1 || date_time.month > 12) + return Error::from_string_literal("ICC::Profile: dateTimeNumber month out of bounds"); + + // "Number of the day of the month (1 to 31)" + if (date_time.day < 1 || date_time.day > 31) + return Error::from_string_literal("ICC::Profile: dateTimeNumber day out of bounds"); + + // "Number of hours (0 to 23)" + if (date_time.hour > 23) + return Error::from_string_literal("ICC::Profile: dateTimeNumber hour out of bounds"); + + // "Number of minutes (0 to 59)" + if (date_time.minutes > 59) + return Error::from_string_literal("ICC::Profile: dateTimeNumber minutes out of bounds"); + + // "Number of seconds (0 to 59)" + // ICC profiles apparently can't be created during leap seconds (seconds would be 60 there, but the spec doesn't allow that). + if (date_time.seconds > 59) + return Error::from_string_literal("ICC::Profile: dateTimeNumber seconds out of bounds"); + + struct tm tm = {}; + tm.tm_year = date_time.year - 1900; + tm.tm_mon = date_time.month - 1; + tm.tm_mday = date_time.day; + tm.tm_hour = date_time.hour; + tm.tm_min = date_time.minutes; + tm.tm_sec = date_time.seconds; + // timegm() doesn't read tm.tm_isdst, tm.tm_wday, and tm.tm_yday, no need to fill them in. + + time_t timestamp = timegm(&tm); + if (timestamp == -1) + return Error::from_string_literal("ICC::Profile: dateTimeNumber not representable as timestamp"); + + return timestamp; +} + // ICC V4, 7.2 Profile header struct ICCHeader { BigEndian profile_size; @@ -27,12 +80,7 @@ struct ICCHeader { BigEndian data_color_space; BigEndian pcs; // "Profile Connection Space" - BigEndian year; - BigEndian month; - BigEndian day; - BigEndian hour; - BigEndian minutes; - BigEndian seconds; + DateTimeNumber profile_creation_time; BigEndian profile_file_signature; BigEndian primary_platform; @@ -78,6 +126,12 @@ ErrorOr parse_device_class(ICCHeader const& header) return Error::from_string_literal("ICC::Profile: Invalid device class"); } +ErrorOr parse_creation_date_time(ICCHeader const& header) +{ + // iCC v4, 7.2.8 Date and time field + return parse_date_time_number(header.profile_creation_time); +} + ErrorOr parse_file_signature(ICCHeader const& header) { // iCC v4, 7.2.9 Profile file signature field @@ -121,6 +175,7 @@ ErrorOr> Profile::try_load_from_externally_owned_memory(R TRY(parse_file_signature(header)); profile->m_version = TRY(parse_version(header)); profile->m_device_class = TRY(parse_device_class(header)); + profile->m_creation_timestamp = TRY(parse_creation_date_time(header)); return profile; } diff --git a/Userland/Libraries/LibGfx/ICCProfile.h b/Userland/Libraries/LibGfx/ICCProfile.h index f28cee6267..2a918c205f 100644 --- a/Userland/Libraries/LibGfx/ICCProfile.h +++ b/Userland/Libraries/LibGfx/ICCProfile.h @@ -51,10 +51,12 @@ public: Version version() const { return m_version; } DeviceClass device_class() const { return m_device_class; } + time_t creation_timestamp() const { return m_creation_timestamp; } private: Version m_version; DeviceClass m_device_class; + time_t m_creation_timestamp; }; }