mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 21:27:34 +00:00
LibGfx: Read profile creation time from ICCProfile header
This commit is contained in:
parent
bb1f6f71f1
commit
782ab0a11f
2 changed files with 63 additions and 6 deletions
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <AK/Endian.h>
|
#include <AK/Endian.h>
|
||||||
#include <LibGfx/ICCProfile.h>
|
#include <LibGfx/ICCProfile.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
// V2 spec: https://color.org/specification/ICC.1-2001-04.pdf
|
// V2 spec: https://color.org/specification/ICC.1-2001-04.pdf
|
||||||
// V4 spec: https://color.org/specification/ICC.1-2022-05.pdf
|
// V4 spec: https://color.org/specification/ICC.1-2022-05.pdf
|
||||||
|
@ -14,6 +15,58 @@ namespace Gfx::ICC {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
// ICC V4, 4.2 dateTimeNumber
|
||||||
|
// "All the dateTimeNumber values in a profile shall be in Coordinated Universal Time [...]."
|
||||||
|
struct DateTimeNumber {
|
||||||
|
BigEndian<u16> year;
|
||||||
|
BigEndian<u16> month;
|
||||||
|
BigEndian<u16> day;
|
||||||
|
BigEndian<u16> hour;
|
||||||
|
BigEndian<u16> minutes;
|
||||||
|
BigEndian<u16> seconds;
|
||||||
|
};
|
||||||
|
|
||||||
|
ErrorOr<time_t> 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
|
// ICC V4, 7.2 Profile header
|
||||||
struct ICCHeader {
|
struct ICCHeader {
|
||||||
BigEndian<u32> profile_size;
|
BigEndian<u32> profile_size;
|
||||||
|
@ -27,12 +80,7 @@ struct ICCHeader {
|
||||||
BigEndian<u32> data_color_space;
|
BigEndian<u32> data_color_space;
|
||||||
BigEndian<u32> pcs; // "Profile Connection Space"
|
BigEndian<u32> pcs; // "Profile Connection Space"
|
||||||
|
|
||||||
BigEndian<u16> year;
|
DateTimeNumber profile_creation_time;
|
||||||
BigEndian<u16> month;
|
|
||||||
BigEndian<u16> day;
|
|
||||||
BigEndian<u16> hour;
|
|
||||||
BigEndian<u16> minutes;
|
|
||||||
BigEndian<u16> seconds;
|
|
||||||
|
|
||||||
BigEndian<u32> profile_file_signature;
|
BigEndian<u32> profile_file_signature;
|
||||||
BigEndian<u32> primary_platform;
|
BigEndian<u32> primary_platform;
|
||||||
|
@ -78,6 +126,12 @@ ErrorOr<DeviceClass> parse_device_class(ICCHeader const& header)
|
||||||
return Error::from_string_literal("ICC::Profile: Invalid device class");
|
return Error::from_string_literal("ICC::Profile: Invalid device class");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorOr<time_t> 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<void> parse_file_signature(ICCHeader const& header)
|
ErrorOr<void> parse_file_signature(ICCHeader const& header)
|
||||||
{
|
{
|
||||||
// iCC v4, 7.2.9 Profile file signature field
|
// iCC v4, 7.2.9 Profile file signature field
|
||||||
|
@ -121,6 +175,7 @@ ErrorOr<NonnullRefPtr<Profile>> Profile::try_load_from_externally_owned_memory(R
|
||||||
TRY(parse_file_signature(header));
|
TRY(parse_file_signature(header));
|
||||||
profile->m_version = TRY(parse_version(header));
|
profile->m_version = TRY(parse_version(header));
|
||||||
profile->m_device_class = TRY(parse_device_class(header));
|
profile->m_device_class = TRY(parse_device_class(header));
|
||||||
|
profile->m_creation_timestamp = TRY(parse_creation_date_time(header));
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,12 @@ public:
|
||||||
|
|
||||||
Version version() const { return m_version; }
|
Version version() const { return m_version; }
|
||||||
DeviceClass device_class() const { return m_device_class; }
|
DeviceClass device_class() const { return m_device_class; }
|
||||||
|
time_t creation_timestamp() const { return m_creation_timestamp; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Version m_version;
|
Version m_version;
|
||||||
DeviceClass m_device_class;
|
DeviceClass m_device_class;
|
||||||
|
time_t m_creation_timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue