summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorNico Weber <thakis@chromium.org>2022-12-26 08:21:26 -0500
committerAndrew Kaster <andrewdkaster@gmail.com>2022-12-27 07:44:37 -0700
commitd8867f8077c9a71c51ca0493f83b2d88145169d3 (patch)
tree9adff427fde567cc6b80d9d03447b9430c35a487 /Userland
parent19d3821354a376396ad5501b86203617e35c18dd (diff)
downloadserenity-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.txt1
-rw-r--r--Userland/Libraries/LibGfx/ICCProfile.cpp128
-rw-r--r--Userland/Libraries/LibGfx/ICCProfile.h70
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());
+ }
+};
+}