Files
EVEngine/test/resource_format_image.cpp
T

168 lines
7.2 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "data/ByteData.h"
#include "filesystem/FileData.h"
#include "image/Image.h"
#include "image/ImageData.h"
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <memory>
#include <string>
#include <vector>
namespace {
#include "PathBesideSource.h"
EVE_DEFINE_PATH_BESIDE_SOURCE()
std::vector<char> fixture(zeroerr::TestContext *_ZEROERR_TEST_CONTEXT, const std::string &name) {
std::ifstream input(pathBesideThisSource(("fixtures/resource_formats/" + name).c_str()), std::ios::binary);
REQUIRE(input.good());
return {std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>()};
}
void decodePattern(zeroerr::TestContext *_ZEROERR_TEST_CONTEXT, const std::string &extension, bool alpha,
float tolerance, float divisor = 255.f) {
const auto bytes = fixture(_ZEROERR_TEST_CONTEXT, "pattern." + extension);
eve::data::ByteData source(bytes.data(), bytes.size());
auto *module = eve::image::Image::create();
std::unique_ptr<eve::image::ImageData> decoded(module->newImageData(&source));
REQUIRE(decoded != nullptr);
REQUIRE_EQ(decoded->getWidth(), 5);
REQUIRE_EQ(decoded->getHeight(), 3);
for (int y = 0; y < 3; ++y) {
for (int x = 0; x < 5; ++x) {
const auto pixel = decoded->getPixel(x, y);
CHECK(std::fabs(pixel.r - (17 + x * 37) / divisor) <= tolerance);
CHECK(std::fabs(pixel.g - (23 + y * 71) / divisor) <= tolerance);
CHECK(std::fabs(pixel.b - (11 + x * 13 + y * 29) / divisor) <= tolerance);
CHECK(std::fabs(pixel.a - (alpha ? (x + y * 5) * 17 / 255.f : 1.f)) <= tolerance);
}
}
}
void exportPattern(zeroerr::TestContext *_ZEROERR_TEST_CONTEXT, medialoader::FormatHandler::EncodedFormat format,
const std::string &pixelFormat, const std::string &filename) {
auto *module = eve::image::Image::create();
std::unique_ptr<eve::image::ImageData> source(module->newImageData(5, 3, pixelFormat));
for (int y = 0; y < 3; ++y)
for (int x = 0; x < 5; ++x) {
const float lowBits = pixelFormat == "RGBA16" ? (x + 1) / 65535.f : 0.f;
source->setPixel(x, y,
{(17 + x * 37) / 255.f + lowBits, (23 + y * 71) / 255.f + lowBits,
(11 + x * 13 + y * 29) / 255.f + lowBits, (x + y * 5) * 17 / 255.f + lowBits});
}
std::unique_ptr<eve::filesystem::FileData> encoded(source->encode(format, filename.c_str(), false));
REQUIRE(encoded != nullptr);
std::unique_ptr<eve::image::ImageData> decoded(module->newImageData(encoded.get()));
REQUIRE_EQ(decoded->getWidth(), source->getWidth());
REQUIRE_EQ(decoded->getHeight(), source->getHeight());
REQUIRE_EQ(decoded->getFormat(), pixelFormat);
REQUIRE_EQ(decoded->getSize(), source->getSize());
CHECK_EQ(std::memcmp(decoded->getData(), source->getData(), source->getSize()), 0);
if (const char *directory = std::getenv("EVENGINE_FORMAT_EXPORT_DIR")) {
std::filesystem::create_directories(directory);
std::ofstream output(std::filesystem::path(directory) / filename, std::ios::binary);
REQUIRE(output.good());
output.write(static_cast<const char *>(encoded->getData()), static_cast<std::streamsize>(encoded->getSize()));
REQUIRE(output.good());
}
}
} // namespace
TEST_CASE("resourceFormats.image.png.independentPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "png", true, 1.f / 255.f);
}
TEST_CASE("resourceFormats.image.tga.independentPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "tga", true, 1.f / 255.f);
}
TEST_CASE("resourceFormats.image.bmp.independentPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "bmp", false, 1.f / 255.f);
}
TEST_CASE("resourceFormats.image.jpg.independentPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "jpg", false, 3.f / 255.f);
}
TEST_CASE("resourceFormats.image.jpeg.independentPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "jpeg", false, 3.f / 255.f);
}
TEST_CASE("resourceFormats.image.gif.independentPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "gif", false, 1.f / 255.f);
}
TEST_CASE("resourceFormats.image.webp.independentPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "webp", true, 1.f / 255.f);
}
TEST_CASE("resourceFormats.image.hdr.independentPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "hdr", false, 1.f / 256.f, 256.f);
}
TEST_CASE("resourceFormats.image.exr.independentPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "exr", false, 2.f / 255.f);
}
TEST_CASE("resourceFormats.image.png.rgba8Export") {
exportPattern(_ZEROERR_TEST_CONTEXT, medialoader::FormatHandler::ENCODED_PNG, "RGBA8", "rgba8.png");
}
TEST_CASE("resourceFormats.image.png.rgba16Export") {
exportPattern(_ZEROERR_TEST_CONTEXT, medialoader::FormatHandler::ENCODED_PNG, "RGBA16", "rgba16.png");
}
TEST_CASE("resourceFormats.image.tga.rgba8Export") {
exportPattern(_ZEROERR_TEST_CONTEXT, medialoader::FormatHandler::ENCODED_TGA, "RGBA8", "rgba8.tga");
}
TEST_CASE("resourceFormats.image.truncatedHeadersRejectWithoutPoisoningDecoder") {
auto *module = eve::image::Image::create();
for (const auto *extension : {"png", "tga", "bmp", "jpg", "exr", "hdr", "gif", "webp"}) {
auto bytes = fixture(_ZEROERR_TEST_CONTEXT, std::string("pattern.") + extension);
bytes.resize(4);
eve::data::ByteData source(bytes.data(), bytes.size());
bool rejected = false;
try {
std::unique_ptr<eve::image::ImageData> unexpected(module->newImageData(&source));
} catch (const std::exception &) {
rejected = true;
}
CHECK(rejected);
}
decodePattern(_ZEROERR_TEST_CONTEXT, "png", true, 1.f / 255.f);
}
TEST_CASE("resourceFormats.image.gif.animatedFirstFrame") {
decodePattern(_ZEROERR_TEST_CONTEXT, "animated.gif", false, 1.f / 255.f);
}
TEST_CASE("resourceFormats.image.webp.animatedFirstFrame") {
decodePattern(_ZEROERR_TEST_CONTEXT, "animated.webp", false, 1.f / 255.f);
}
TEST_CASE("resourceFormats.image.webp.lossyPixels") {
decodePattern(_ZEROERR_TEST_CONTEXT, "lossy.webp", false, 0.15f);
}
TEST_CASE("resourceFormats.image.hdr.truncatedPayloadIsRejected") {
auto bytes = fixture(_ZEROERR_TEST_CONTEXT, "pattern.hdr");
bytes.pop_back();
eve::data::ByteData source(bytes.data(), bytes.size());
bool rejected = false;
try {
std::unique_ptr<eve::image::ImageData> unexpected(eve::image::Image::create()->newImageData(&source));
} catch (const std::exception &) {
rejected = true;
}
REQUIRE(rejected);
}
TEST_CASE("resourceFormats.image.webp.truncatedPayloadIsRejected") {
auto bytes = fixture(_ZEROERR_TEST_CONTEXT, "pattern.webp");
bytes.resize(bytes.size() / 2);
eve::data::ByteData source(bytes.data(), bytes.size());
bool rejected = false;
try {
std::unique_ptr<eve::image::ImageData> unexpected(eve::image::Image::create()->newImageData(&source));
} catch (const std::exception &) {
rejected = true;
}
REQUIRE(rejected);
}