Files
dawn/tests/test-imageio.cpp
2026-09-16 05:17:25 +02:00

149 lines
4.3 KiB
C++

//
// test-imageio.cpp: check the ImageIO fallback against first-class loaders
//
// Copyright The Dawn Authors
// SPDX-License-Identifier: MPL-2.0
//
// ImageIO's format coverage is Apple's problem. What is ours is that it must
// not colour-manage behind our back: every fixture here has a known-good
// first-class decoding to compare against, and display-p3-red_vs_srgb-red.png
// is the one that catches a double conversion.
#include <dawn-config.h>
#include "libdn/libdn.hpp"
#include "test.hpp"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <memory>
#include <string>
#include <vector>
using namespace std;
namespace fs = filesystem;
#ifndef DAWN_TEST_FIXTURES_DIR
#error DAWN_TEST_FIXTURES_DIR must be defined
#endif
static dawn::ImagePtr
load(const char *name, const vector<string> &loaders, shared_ptr<dawn::Cmm> cmm,
shared_ptr<dawn::Profile> screen)
{
dawn::OpenContext ctx;
ctx.uri = dawn::path_to_uri(
(fs::path(DAWN_TEST_FIXTURES_DIR) / name).string());
ctx.cmm = cmm;
ctx.screen_profile = screen;
ctx.first_frame_only = true;
ctx.loaders = loaders;
dawn::Error error;
dawn::ImagePtr image = dawn::open(ctx, &error);
if (!image)
test::fail("open(%s): %s", name, error.message.c_str());
return image;
}
// Compares the ImageIO decoding of a fixture against its default one,
// which is whatever loader the table would normally pick for it.
static dawn::ImagePtr
compare(const char *name, int tolerance, shared_ptr<dawn::Profile> screen)
{
auto cmm = dawn::Cmm::get_default();
dawn::ImagePtr expected = load(name, {}, cmm, screen);
dawn::ImagePtr actual = load(name, {"ImageIO"}, cmm, screen);
if (!expected || !actual)
return nullptr;
CHECK(actual->loader && !strcmp(actual->loader, "ImageIO"));
// ImageIO assigns sRGB to untagged files, and must not then pass that
// off as embedded: the two loaders have to agree on whether the profile
// was the file's or our own invention.
if (expected->profile_assumed != actual->profile_assumed) {
test::fail("%s: profile_assumed %d, ImageIO says %d", name,
int(expected->profile_assumed), int(actual->profile_assumed));
}
if (expected->width != actual->width ||
expected->height != actual->height) {
test::fail("%s: %ux%u decoded as %ux%u", name, expected->width,
expected->height, actual->width, actual->height);
return nullptr;
}
int worst = 0;
uint32_t worst_x = 0, worst_y = 0;
for (uint32_t y = 0; y < expected->height; y++) {
const uint16_t *a = dawn::row_u16(*expected, y);
const uint16_t *b = dawn::row_u16(*actual, y);
for (uint32_t x = 0; x < expected->width * 4; x++) {
int difference = abs(int(a[x]) - int(b[x]));
if (difference > worst) {
worst = difference;
worst_x = x / 4;
worst_y = y;
}
}
}
if (worst > tolerance) {
test::fail("%s: differs by %d at (%u, %u), tolerance %d", name, worst,
worst_x, worst_y, tolerance);
}
return actual;
}
// PNG decodes losslessly either way, so the identity path must be exact
// but for premultiplication rounding.
static void
test_solids()
{
constexpr int tolerance = 2 * 257;
compare("blue.png", tolerance, nullptr);
compare("blue16.png", tolerance, nullptr);
compare("red_a128.png", tolerance, nullptr);
compare("rgbw_2x2.png", tolerance, nullptr);
#if DAWN_WITH_LIBTIFF
compare("blue.tif", tolerance, nullptr);
#endif
}
// Both loaders hand lcms2 the same embedded Display P3 profile, so both must
// perform exactly one conversion into an AdobeRGB-like screen profile.
static void
test_no_double_conversion()
{
auto cmm = dawn::Cmm::get_default();
double whitepoint[2] = {0.3127, 0.3290};
double adobe_primaries[6] = {
0.6400, 0.3300, 0.2100, 0.7100, 0.1500, 0.0600};
auto target = cmm->get_profile_parametric(2.2, whitepoint, adobe_primaries);
CHECK(target != nullptr);
dawn::ImagePtr image =
compare("display-p3-red_vs_srgb-red.png", 4 * 257, target);
if (!image)
return;
// The two halves must stay as far apart as test-channels demands of Wuffs.
const uint16_t *p3_red = dawn::row_u16(*image, 50) + 50 * 4;
const uint16_t *srgb_red = dawn::row_u16(*image, 50) + 150 * 4;
CHECK(p3_red[2] > srgb_red[2] + 2000);
CHECK(p3_red[1] + 1000 < srgb_red[1]);
CHECK(p3_red[0] + 1000 < srgb_red[0]);
CHECK(!image->profile_assumed);
}
int
main()
{
return test::run({
{"solid fixtures", test_solids},
{"no double conversion", test_no_double_conversion},
});
}