Jelajahi Sumber

LibGfx: Add start of an ICC profile writer

For now, this can write the profile header.

`icc` refuses to dump its contents since the required copyrightTag,
profileDescriptionTag, and required per-device-class tags are missing,
but it looks ok in a hex editor.
Nico Weber 2 tahun lalu
induk
melakukan
8b8b7b232e

+ 1 - 0
Userland/Libraries/LibGfx/CMakeLists.txt

@@ -28,6 +28,7 @@ set(SOURCES
     Font/WOFF/Font.cpp
     GradientPainting.cpp
     GIFLoader.cpp
+    ICC/BinaryWriter.cpp
     ICC/Profile.cpp
     ICC/Tags.cpp
     ICC/TagTypes.cpp

+ 68 - 0
Userland/Libraries/LibGfx/ICC/BinaryWriter.cpp

@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2023, Nico Weber <thakis@chromium.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibGfx/ICC/BinaryFormat.h>
+#include <LibGfx/ICC/BinaryWriter.h>
+#include <LibGfx/ICC/Profile.h>
+#include <time.h>
+
+namespace Gfx::ICC {
+
+ErrorOr<ByteBuffer> encode(Profile const& profile)
+{
+
+    // Leaves enough room for the profile header and the tag table count.
+    // FIXME: Serialize tag data and write tag table and tag data too.
+    auto bytes = TRY(ByteBuffer::create_zeroed(sizeof(ICCHeader) + sizeof(u32)));
+
+    VERIFY(bytes.size() >= sizeof(ICCHeader));
+    auto& raw_header = *bit_cast<ICCHeader*>(bytes.data());
+
+    raw_header.profile_size = bytes.size();
+    raw_header.preferred_cmm_type = profile.preferred_cmm_type().value_or(PreferredCMMType { 0 });
+
+    raw_header.profile_version_major = profile.version().major_version();
+    raw_header.profile_version_minor_bugfix = profile.version().minor_and_bugfix_version();
+    raw_header.profile_version_zero = 0;
+
+    raw_header.profile_device_class = profile.device_class();
+    raw_header.data_color_space = profile.data_color_space();
+    raw_header.profile_connection_space = profile.connection_space();
+
+    time_t profile_timestamp = profile.creation_timestamp();
+    struct tm tm;
+    if (!gmtime_r(&profile_timestamp, &tm))
+        return Error::from_errno(errno);
+    raw_header.profile_creation_time.year = tm.tm_year + 1900;
+    raw_header.profile_creation_time.month = tm.tm_mon + 1;
+    raw_header.profile_creation_time.day = tm.tm_mday;
+    raw_header.profile_creation_time.hours = tm.tm_hour;
+    raw_header.profile_creation_time.minutes = tm.tm_min;
+    raw_header.profile_creation_time.seconds = tm.tm_sec;
+
+    raw_header.profile_file_signature = ProfileFileSignature;
+    raw_header.primary_platform = profile.primary_platform().value_or(PrimaryPlatform { 0 });
+
+    raw_header.profile_flags = profile.flags().bits();
+    raw_header.device_manufacturer = profile.device_manufacturer().value_or(DeviceManufacturer { 0 });
+    raw_header.device_model = profile.device_model().value_or(DeviceModel { 0 });
+    raw_header.device_attributes = profile.device_attributes().bits();
+    raw_header.rendering_intent = profile.rendering_intent();
+
+    raw_header.pcs_illuminant = profile.pcs_illuminant();
+
+    raw_header.profile_creator = profile.creator().value_or(Creator { 0 });
+
+    memset(raw_header.reserved, 0, sizeof(raw_header.reserved));
+
+    auto id = Profile::compute_id(bytes);
+    static_assert(sizeof(id.data) == sizeof(raw_header.profile_id));
+    memcpy(raw_header.profile_id, id.data, sizeof(id.data));
+
+    return bytes;
+}
+
+}

+ 23 - 0
Userland/Libraries/LibGfx/ICC/BinaryWriter.h

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023, Nico Weber <thakis@chromium.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+
+namespace Gfx::ICC {
+
+class Profile;
+
+// Serializes a Profile object.
+// Ignores the Profile's on_disk_size() and id() and recomputes them instead.
+// Also ignores and the offsets and sizes in tag data.
+// But if the profile has its tag data in tag order and has a computed id,
+// it's a goal that  encode(Profile::try_load_from_externally_owned_memory(bytes) returns `bytes`.
+// Unconditionally computes a Profile ID (which is an MD5 hash of most of the contents, see Profile::compute_id()) and writes it to the output.
+ErrorOr<ByteBuffer> encode(Profile const&);
+
+}