Files
dawn/tests/test-icns.cpp
p 2390a18079
Alpine 3.24 Success
Alpine 3.24 Flatpak Success
Arch Linux AUR Success
Cleanup
2026-10-04 05:06:59 +02:00

300 lines
9.0 KiB
C++

//
// test-icns.cpp: ICNS container and legacy pixel format tests
//
// Copyright The Dawn Authors
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-config.h>
#include "test.hpp"
#include <libdn/libdn-loaders.hpp>
#include <libdn/libdn.hpp>
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <span>
#include <string>
#include <utility>
#include <vector>
using namespace std;
using test::append_be32;
// Generated once with macOS 15 ImageIO. These are the irreducible encoded
// payload tests; repetitive legacy formats are assembled below.
constexpr char kMacosGold[] =
"icns\000\000\000\220ic11\000\000\000\210\211PNG\015\012\032\012\000\000"
"\000\015IHDR\000\000\000 \000\000\000 \010\006\000\000\000szz\364\000"
"\000\000GIDATX\011\355\327\261\015\0000\010\003A\234\375wN\000e\207o\036"
"\006\260uT\244n/8g\262\247\003\325#X\362W_\001\360\002e\001\005\024P@"
"\001\005\024P@\001\005\024P@\001\\ \373\027R\257q\207?N2\0124V\256If\000"
"\000\000\000IEND\256B`\202";
constexpr char kJpeg2000Gold[] =
"icns\000\000\002\354ic05\000\000\002\344\000\000\000\014jP \015\012\207"
"\012\000\000\000\024ftypjp2 \000\000\000\000jp2 \000\000\000Ojp2h\000"
"\000\000\026ihdr\000\000\000 \000\000\000 \000\004\007\007\001\000\000"
"\000\000\017colr\001\000\000\000\000\000\020\000\000\000\"cdef\000\004"
"\000\000\000\000\000\001\000\003\000\001\000\000\000\001\000\000\000\002"
"\000\002\000\000\000\003\000\000\000\000jp2c\377O\377Q\0002\000\000\000"
"\000\000 \000\000\000 \000\000\000\000\000\000\000\000\000\000\000 \000"
"\000\000 \000\000\000\000\000\000\000\000\000\004\007\001\001\007\001"
"\001\007\001\001\007\001\001\377R\000\014\000\000\000\001\001\005\004"
"\004\000\001\377\\\000\023 PXX`XX`XX`XXXPPX\377d\000\021\000\001Kakadu-v"
"5.2.1\377\220\000\012\000\000\000\000\001\367\000\001\377\223\300\371"
"\000\200\006\307\332\004\003\030\307\332\004\006\270\307\332\002\000\300"
"|\200\240|\340`}@ \011h\011\002\300>\020`|\340\240>p@\000\0043\010\020"
"\300>\020`|\340`>p@\011\006\001X\200\301\365\001\301\365\002@|\340\300"
"\005\036|\007\361\223\001\007\251\221\300\371\302@\371\301\300|\340\300"
"\007\315)\206\005On\0054D\300\371\302@\371\302@|\340\300\005\036#\223"
"\010\012w\232\007\371\314\240\0040\003\241\000\016\013q\300\371\303\301"
"\365\003@>@\300\025!\030&[\320\327\027S\254\024\177\304\021\341S \211)"
"\300|\201\240|\341\240\037 `\022p\2157\204\237\025\021\267\245.\265\0258"
"\240\305, \300|\201`|\341`\017\204@\025!\001\350\225\027S\014?8\021\341W"
"\255\300\035\012\001\360\207\000|!\000\004\027\001U\017\004\021\301\365"
"\004\300|\201\340>@\200\024\254\342,\205\013dq'>\177\205\265A\352M=\320"
"\260h\300\371\304@|\201`>@`\021\374\331\211U!>$>\211K\343m=\321=\300\371"
"\304@|\201\240>@\240\024\254\34252w\300\253=\314\362k\375?>\206\273\301o"
"\300|\200\340\372\201 \372\200\300=\315q>\211H\255=\314\264\303\352\013"
"\237\200\204>\320\220\024\303S\210\343\242\003\337 \332\013\030 \310ft"
"\201\203\026W\303\346\203\377ep\353\025C{\226\177\202?\352\230\301\363"
"\213\217\264>\017\250 \022\023K\210\343v\317/\232\267+\027t\303^X\230"
"\235{\350`H\012\001\303\252\022\223sDc\007-\237\301\363\212\217\264:\017"
"\250$\024\303S\210\343v\317G\326&\030 \310fw\311R\321E\3222~\035;\025C{D"
"c\006@\357A\303\352\004\237\2004~\000\200\235\261,\350\235\233\217\277"
"\265,\235\261\212\217\377\331";
struct Pixel {
uint16_t b, g, r, a;
};
struct Expected {
uint32_t width, height;
uint8_t r, g, b, a = 255;
};
static Pixel
pixel_at(const dawn::Image &image, uint32_t x, uint32_t y)
{
const uint16_t *p = dawn::row_u16(image, y) + x * 4;
return {p[0], p[1], p[2], p[3]};
}
template <size_t N>
static span<const uint8_t>
bytes(const char (&data)[N])
{
return {(const uint8_t *) data, N - 1};
}
static void
append_entry(
vector<uint8_t> &out, const char (&type)[5], span<const uint8_t> data)
{
out.insert(out.end(), type, type + 4);
append_be32(out, uint32_t(data.size() + 8));
out.insert(out.end(), data.begin(), data.end());
}
static void
append_rle_channel(vector<uint8_t> &out, uint8_t value)
{
// The ICNS run lengths represented by FF and FB are 130 and 126.
out.insert(out.end(), {0xFF, value, 0xFB, value});
}
static vector<uint8_t>
legacy_gold()
{
vector<uint8_t> entries, data(64);
fill(data.begin() + 32, data.end(), 0xFF);
append_entry(entries, "ics#", data);
data.assign(128, 0x88); // Palette 4: green in both nibbles.
append_entry(entries, "ics4", data);
data.assign(256, 235); // Palette 8: blue.
append_entry(entries, "ics8", data);
data.clear();
for (uint8_t channel : {uint8_t(0x20), uint8_t(0x70), uint8_t(0xD0)})
append_rle_channel(data, channel);
append_entry(entries, "is32", data);
data.assign(256, 128);
append_entry(entries, "s8mk", data);
data.assign({'A', 'R', 'G', 'B'});
for (uint8_t channel :
{uint8_t(0x40), uint8_t(0xF0), uint8_t(0x40), uint8_t(0x20)})
append_rle_channel(data, channel);
append_entry(entries, "ic04", data);
vector<uint8_t> result{'i', 'c', 'n', 's'};
append_be32(result, uint32_t(entries.size() + 8));
result.insert(result.end(), entries.begin(), entries.end());
return result;
}
static uint16_t
premul8(uint8_t value, uint8_t alpha)
{
uint32_t v = uint32_t(value) * 257;
uint32_t a = uint32_t(alpha) * 257;
return uint16_t((v * a + 32768) / 65535);
}
static bool
near(uint16_t a, uint16_t b, uint16_t tolerance)
{
return abs(int(a) - int(b)) <= tolerance;
}
static void
expect_page(
const dawn::Image &page, const Expected &want, uint16_t tolerance = 0)
{
CHECK(page.width == want.width);
CHECK(page.height == want.height);
Pixel p = pixel_at(page, page.width / 2, page.height / 2);
if (!near(p.b, premul8(want.b, want.a), tolerance) ||
!near(p.g, premul8(want.g, want.a), tolerance) ||
!near(p.r, premul8(want.r, want.a), tolerance) ||
!near(p.a, uint16_t(want.a) * 257, tolerance)) {
test::fail("%ux%u: got BGRA (%u,%u,%u,%u)", want.width, want.height,
p.b, p.g, p.r, p.a);
}
}
static dawn::ImagePtr
load_gold(span<const uint8_t> data, bool first_only,
vector<string> *warnings = nullptr)
{
dawn::OpenContext ctx;
ctx.first_frame_only = first_only;
ctx.warnings = warnings;
dawn::Error error;
dawn::ImagePtr image = dawn::open_from_data(data, ctx, &error);
if (!image)
test::fail("open ICNS gold: %s", error.message.c_str());
return image;
}
static void
expect_pages(
dawn::ImagePtr image, span<const Expected> expected, uint16_t tolerance = 0)
{
for (size_t i = 0; i < expected.size(); i++) {
if (!image) {
test::fail("page chain ended before page %llu",
(unsigned long long) (i + 1));
return;
}
expect_page(*image, expected[i], tolerance);
if (image->page_next)
CHECK(image->page_next->page_previous.lock().get() == image.get());
image = image->page_next;
}
CHECK(image == nullptr);
}
static void
test_macos_gold()
{
static constexpr Expected expected[] = {
{32, 32, 0x00, 0xFF, 0x00},
};
vector<string> warnings;
dawn::ImagePtr image = load_gold(bytes(kMacosGold), false, &warnings);
CHECK(image != nullptr);
if (!image)
return;
CHECK(string(image->loader) == "ICNS");
CHECK(warnings.empty());
expect_pages(std::move(image), expected);
}
static void
test_first_page_only()
{
dawn::ImagePtr image = load_gold(bytes(kMacosGold), true);
CHECK(image != nullptr);
if (!image)
return;
CHECK(image->width == 32);
CHECK(image->height == 32);
CHECK(image->page_next == nullptr);
}
static void
test_legacy_gold()
{
static constexpr Expected expected[] = {
{16, 16, 0xFF, 0xFF, 0xFF},
{16, 16, 0x1F, 0xB7, 0x14},
{16, 16, 0x00, 0x00, 0xEE},
{16, 16, 0x20, 0x70, 0xD0, 128},
{16, 16, 0xF0, 0x40, 0x20, 64},
};
vector<string> warnings;
vector<uint8_t> data = legacy_gold();
dawn::ImagePtr image = load_gold(data, false, &warnings);
CHECK(image != nullptr);
CHECK(warnings.empty());
expect_pages(std::move(image), expected);
}
static void
test_jpeg2000_gold()
{
dawn::OpenContext ctx;
vector<string> warnings;
ctx.warnings = &warnings;
dawn::Error error;
dawn::ImagePtr image =
dawn::load_icns(bytes(kJpeg2000Gold), ctx, &error);
#if DAWN_WITH_OPENJPEG
CHECK(image != nullptr);
if (image)
expect_page(*image, {32, 32, 0x17, 0x6B, 0xC7}, 8 * 257);
CHECK(warnings.empty());
#else
CHECK(image == nullptr);
CHECK(error.message == "empty or unsupported ICNS image");
CHECK(warnings.size() == 1);
#endif
}
static void
test_truncated_tail()
{
vector<uint8_t> data = legacy_gold();
data.insert(data.end(), {'b', 'a', 'd', '!'});
append_be32(data, 12); // The declared entry payload is absent.
uint32_t size = uint32_t(data.size());
data[4] = uint8_t(size >> 24);
data[5] = uint8_t(size >> 16);
data[6] = uint8_t(size >> 8);
data[7] = uint8_t(size);
dawn::OpenContext ctx;
vector<string> warnings;
ctx.warnings = &warnings;
dawn::Error error;
dawn::ImagePtr image = dawn::load_icns(data, ctx, &error);
CHECK(image != nullptr);
CHECK(!warnings.empty());
}
int
main()
{
return test::run({
{"macOS gold", test_macos_gold},
{"first page", test_first_page_only},
{"legacy gold", test_legacy_gold},
{"JPEG 2000 gold", test_jpeg2000_gold},
{"truncated tail", test_truncated_tail},
});
}