From d8867f8077c9a71c51ca0493f83b2d88145169d3 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 26 Dec 2022 08:21:26 -0500 Subject: [PATCH] LibGfx: Start adding a class for handling ICC color profiles For now, this checks the magic number and reads file version and device class. --- Userland/Libraries/LibGfx/CMakeLists.txt | 1 + Userland/Libraries/LibGfx/ICCProfile.cpp | 128 +++++++++++++++++++++++ Userland/Libraries/LibGfx/ICCProfile.h | 70 +++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 Userland/Libraries/LibGfx/ICCProfile.cpp create mode 100644 Userland/Libraries/LibGfx/ICCProfile.h diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index b84c6b36ca..6127cf09b8 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/Userland/Libraries/LibGfx/CMakeLists.txt @@ -25,6 +25,7 @@ set(SOURCES Font/Typeface.cpp Font/WOFF/Font.cpp GIFLoader.cpp + ICCProfile.cpp ICOLoader.cpp ImageDecoder.cpp JPGLoader.cpp diff --git a/Userland/Libraries/LibGfx/ICCProfile.cpp b/Userland/Libraries/LibGfx/ICCProfile.cpp new file mode 100644 index 0000000000..15621e4124 --- /dev/null +++ b/Userland/Libraries/LibGfx/ICCProfile.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022, Nico Weber + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +// V2 spec: https://color.org/specification/ICC.1-2001-04.pdf +// V4 spec: https://color.org/specification/ICC.1-2022-05.pdf + +namespace Gfx::ICC { + +namespace { + +// ICC V4, 7.2 Profile header +struct ICCHeader { + BigEndian profile_size; + BigEndian preferred_cmm_type; + + u8 profile_version_major; + u8 profile_version_minor_bugfix; + BigEndian profile_version_zero; + + BigEndian profile_device_class; + BigEndian data_color_space; + BigEndian pcs; // "Profile Connection Space" + + BigEndian year; + BigEndian month; + BigEndian day; + BigEndian hour; + BigEndian minutes; + BigEndian seconds; + + BigEndian profile_file_signature; + BigEndian primary_platform; + + BigEndian profile_flags; + BigEndian device_manufacturer; + BigEndian device_model; + BigEndian device_attributes; + BigEndian rendering_intent; + + BigEndian pcs_illuminant_x; + BigEndian pcs_illuminant_y; + BigEndian pcs_illuminant_z; + + BigEndian profile_creator; + + u8 profile_md5[16]; + u8 reserved[28]; +}; +static_assert(sizeof(ICCHeader) == 128); + +ErrorOr parse_version(ICCHeader const& header) +{ + // ICC v4, 7.2.4 Profile version field + if (header.profile_version_zero != 0) + return Error::from_string_literal("ICC::Profile: Reserved version bytes not zero"); + return Version(header.profile_version_major, header.profile_version_minor_bugfix); +} + +ErrorOr parse_device_class(ICCHeader const& header) +{ + // ICC v4, 7.2.5 Profile/device class field + switch (header.profile_device_class) { + case (u32)DeviceClass::InputDevce: + case (u32)DeviceClass::DisplayDevice: + case (u32)DeviceClass::OutputDevice: + case (u32)DeviceClass::DeviceLink: + case (u32)DeviceClass::ColorSpace: + case (u32)DeviceClass::Abstract: + case (u32)DeviceClass::NamedColor: + return DeviceClass { u32 { header.profile_device_class } }; + } + return Error::from_string_literal("ICC::Profile: Invalid device class"); +} + +ErrorOr parse_file_signature(ICCHeader const& header) +{ + // iCC v4, 7.2.9 Profile file signature field + if (header.profile_file_signature != 0x61637370) + return Error::from_string_literal("ICC::Profile: profile file signature not 'acsp'"); + return {}; +} +} + +char const* device_class_name(DeviceClass device_class) +{ + switch (device_class) { + case DeviceClass::InputDevce: + return "InputDevce"; + case DeviceClass::DisplayDevice: + return "DisplayDevice"; + case DeviceClass::OutputDevice: + return "OutputDevice"; + case DeviceClass::DeviceLink: + return "DeviceLink"; + case DeviceClass::ColorSpace: + return "ColorSpace"; + case DeviceClass::Abstract: + return "Abstract"; + case DeviceClass::NamedColor: + return "NamedColor"; + default: + return "(unknown device class)"; + } +} + +ErrorOr> Profile::try_load_from_externally_owned_memory(ReadonlyBytes bytes) +{ + auto profile = adopt_ref(*new Profile()); + + if (bytes.size() < sizeof(ICCHeader)) + return Error::from_string_literal("ICC::Profile: Not enough data for header"); + + auto header = *bit_cast(bytes.data()); + + TRY(parse_file_signature(header)); + profile->m_version = TRY(parse_version(header)); + profile->m_device_class = TRY(parse_device_class(header)); + + return profile; +} + +} diff --git a/Userland/Libraries/LibGfx/ICCProfile.h b/Userland/Libraries/LibGfx/ICCProfile.h new file mode 100644 index 0000000000..f28cee6267 --- /dev/null +++ b/Userland/Libraries/LibGfx/ICCProfile.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022, Nico Weber + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Gfx::ICC { + +// ICC v4, 7.2.4 Profile version field +class Version { +public: + Version() = default; + Version(u8 major, u8 minor_and_bugfix) + : m_major_version(major) + , m_minor_and_bugfix_version(minor_and_bugfix) + { + } + + u8 major_version() const { return m_major_version; } + u8 minor_version() const { return m_minor_and_bugfix_version >> 4; } + u8 bugfix_version() const { return m_minor_and_bugfix_version & 0xf; } + +private: + u8 m_major_version = 0; + u8 m_minor_and_bugfix_version = 0; +}; + +// ICC v4, 7.2.5 Profile/device class field +enum class DeviceClass : u32 { + InputDevce = 0x73636E72, // 'scnr' + DisplayDevice = 0x6D6E7472, // 'mntr' + OutputDevice = 0x70727472, // 'prtr' + DeviceLink = 0x6C696E6B, // 'link' + ColorSpace = 0x73706163, // 'spac' + Abstract = 0x61627374, // 'abst' + NamedColor = 0x6E6D636C, // 'nmcl' +}; +char const* device_class_name(DeviceClass); + +class Profile : public RefCounted { +public: + static ErrorOr> try_load_from_externally_owned_memory(ReadonlyBytes bytes); + + Version version() const { return m_version; } + DeviceClass device_class() const { return m_device_class; } + +private: + Version m_version; + DeviceClass m_device_class; +}; + +} + +namespace AK { +template<> +struct Formatter : Formatter { + ErrorOr format(FormatBuilder& builder, Gfx::ICC::Version const& version) + { + return Formatter::format(builder, "{}.{}.{}"sv, version.major_version(), version.minor_version(), version.bugfix_version()); + } +}; +}