diff options
author | Nico Weber <thakis@chromium.org> | 2022-12-26 08:21:26 -0500 |
---|---|---|
committer | Andrew Kaster <andrewdkaster@gmail.com> | 2022-12-27 07:44:37 -0700 |
commit | d8867f8077c9a71c51ca0493f83b2d88145169d3 (patch) | |
tree | 9adff427fde567cc6b80d9d03447b9430c35a487 /Userland | |
parent | 19d3821354a376396ad5501b86203617e35c18dd (diff) | |
download | serenity-d8867f8077c9a71c51ca0493f83b2d88145169d3.zip |
LibGfx: Start adding a class for handling ICC color profiles
For now, this checks the magic number and reads file version and
device class.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibGfx/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/ICCProfile.cpp | 128 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/ICCProfile.h | 70 |
3 files changed, 199 insertions, 0 deletions
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 <thakis@chromium.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Endian.h> +#include <LibGfx/ICCProfile.h> + +// 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<u32> profile_size; + BigEndian<u32> preferred_cmm_type; + + u8 profile_version_major; + u8 profile_version_minor_bugfix; + BigEndian<u16> profile_version_zero; + + BigEndian<u32> profile_device_class; + BigEndian<u32> data_color_space; + BigEndian<u32> pcs; // "Profile Connection Space" + + BigEndian<u16> year; + BigEndian<u16> month; + BigEndian<u16> day; + BigEndian<u16> hour; + BigEndian<u16> minutes; + BigEndian<u16> seconds; + + BigEndian<u32> profile_file_signature; + BigEndian<u32> primary_platform; + + BigEndian<u32> profile_flags; + BigEndian<u32> device_manufacturer; + BigEndian<u32> device_model; + BigEndian<u64> device_attributes; + BigEndian<u32> rendering_intent; + + BigEndian<i32> pcs_illuminant_x; + BigEndian<i32> pcs_illuminant_y; + BigEndian<i32> pcs_illuminant_z; + + BigEndian<u32> profile_creator; + + u8 profile_md5[16]; + u8 reserved[28]; +}; +static_assert(sizeof(ICCHeader) == 128); + +ErrorOr<Version> 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<DeviceClass> 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<void> 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<NonnullRefPtr<Profile>> 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<ICCHeader const*>(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 <thakis@chromium.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Error.h> +#include <AK/Format.h> +#include <AK/NonnullRefPtr.h> +#include <AK/RefCounted.h> +#include <AK/Span.h> + +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<Profile> { +public: + static ErrorOr<NonnullRefPtr<Profile>> 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<Gfx::ICC::Version> : Formatter<FormatString> { + ErrorOr<void> format(FormatBuilder& builder, Gfx::ICC::Version const& version) + { + return Formatter<FormatString>::format(builder, "{}.{}.{}"sv, version.major_version(), version.minor_version(), version.bugfix_version()); + } +}; +} |