From 19e91e5211ea2394af85e694b4925d5976f671b2 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Sat, 18 Feb 2023 20:23:04 -0500 Subject: [PATCH] LibGfx: Dedupe identical tag data objects when writing ICC data With this, common v4 profiles, such as embedded into jpgs by iPhones (when configured to write jpegs) or Pixel phones, are identical to the input when reexported :^) --- .../Libraries/LibGfx/ICC/BinaryWriter.cpp | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Userland/Libraries/LibGfx/ICC/BinaryWriter.cpp b/Userland/Libraries/LibGfx/ICC/BinaryWriter.cpp index 6af21719d5..df861cbdb5 100644 --- a/Userland/Libraries/LibGfx/ICC/BinaryWriter.cpp +++ b/Userland/Libraries/LibGfx/ICC/BinaryWriter.cpp @@ -238,21 +238,23 @@ static ErrorOr encode_tag_data(TagData const& tag_data) return ByteBuffer {}; } -static ErrorOr> encode_tag_datas(Profile const& profile) +static ErrorOr> encode_tag_datas(Profile const& profile, HashMap& tag_data_map) { Vector tag_data_bytes; + TRY(tag_data_bytes.try_ensure_capacity(profile.tag_count())); - // FIXME: If two tags refer to the same TagData object, write it just once to the output. - TRY(tag_data_bytes.try_resize(profile.tag_count())); - size_t i = 0; profile.for_each_tag([&](auto, auto tag_data) { + if (tag_data_map.contains(tag_data.ptr())) + return; + // FIXME: Come up with a way to allow TRY instead of MUST here. - tag_data_bytes[i++] = MUST(encode_tag_data(tag_data)); + tag_data_bytes.append(MUST(encode_tag_data(tag_data))); + MUST(tag_data_map.try_set(tag_data.ptr(), tag_data_bytes.size() - 1)); }); return tag_data_bytes; } -static ErrorOr encode_tag_table(ByteBuffer& bytes, Profile const& profile, Vector const& offsets, Vector const& tag_data_bytes) +static ErrorOr encode_tag_table(ByteBuffer& bytes, Profile const& profile, Vector const& offsets, Vector const& tag_data_bytes, HashMap const& tag_data_map) { VERIFY(bytes.size() >= sizeof(ICCHeader) + sizeof(u32) + profile.tag_count() * sizeof(TagTableEntry)); @@ -260,10 +262,12 @@ static ErrorOr encode_tag_table(ByteBuffer& bytes, Profile const& profile, TagTableEntry* tag_table_entries = bit_cast(bytes.data() + sizeof(ICCHeader) + sizeof(u32)); int i = 0; - profile.for_each_tag([&](auto tag_signature, auto) { + profile.for_each_tag([&](auto tag_signature, auto tag_data) { tag_table_entries[i].tag_signature = tag_signature; - tag_table_entries[i].offset_to_beginning_of_tag_data_element = offsets[i]; - tag_table_entries[i].size_of_tag_data_element = tag_data_bytes[i].size(); + + auto index = tag_data_map.get(tag_data.ptr()).value(); + tag_table_entries[i].offset_to_beginning_of_tag_data_element = offsets[index]; + tag_table_entries[i].size_of_tag_data_element = tag_data_bytes[index].size(); ++i; }); @@ -324,7 +328,8 @@ ErrorOr encode(Profile const& profile) // Valid profiles always have tags. Profile only represents valid profiles. VERIFY(profile.tag_count() > 0); - Vector tag_data_bytes = TRY(encode_tag_datas(profile)); + HashMap tag_data_map; + Vector tag_data_bytes = TRY(encode_tag_datas(profile, tag_data_map)); size_t tag_table_size = sizeof(u32) + profile.tag_count() * sizeof(TagTableEntry); size_t offset = sizeof(ICCHeader) + tag_table_size; @@ -344,7 +349,7 @@ ErrorOr encode(Profile const& profile) for (size_t i = 0; i < tag_data_bytes.size(); ++i) memcpy(bytes.data() + offsets[i], tag_data_bytes[i].data(), tag_data_bytes[i].size()); - TRY(encode_tag_table(bytes, profile, offsets, tag_data_bytes)); + TRY(encode_tag_table(bytes, profile, offsets, tag_data_bytes, tag_data_map)); TRY(encode_header(bytes, profile)); return bytes;