test: cover luminance and colourDistance
This commit is contained in:
1 parent
b1fe7bbfd0
commit
647ad7f254
1 file changed
+151
@@ -0,0 +1,151 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "testHarness.h"
|
||||||
|
#include "vf/Block.h"
|
||||||
|
#include "vf/TileTexture.h"
|
||||||
|
|
||||||
|
using namespace vf;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
int luminance(TilePixel texel) {
|
||||||
|
return (texel.r * 299 + texel.g * 587 + texel.b * 114) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
int colourDistance(TilePixel texel, TileRgb reference) {
|
||||||
|
int dr = static_cast<int>(texel.r) - reference.r;
|
||||||
|
int dg = static_cast<int>(texel.g) - reference.g;
|
||||||
|
int db = static_cast<int>(texel.b) - reference.b;
|
||||||
|
return dr * dr + dg * dg + db * db;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool leansToAccent(TilePixel texel, const TileSpec& spec) {
|
||||||
|
return colourDistance(texel, spec.accent) < colourDistance(texel, spec.base);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The palette owns how many tiles exist. A table short of a row must surface as
|
||||||
|
// a reported failure, not as a read past the end of the table proving it.
|
||||||
|
int renderedTiles(const TileSampler& sampler) {
|
||||||
|
int held = sampler.palette().size();
|
||||||
|
return held < tileCount ? held : tileCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> renderTile(const TileSampler& sampler, int tile) {
|
||||||
|
int size = sampler.params().tileSize;
|
||||||
|
std::vector<uint8_t> bytes(static_cast<std::size_t>(size) * size * 4);
|
||||||
|
sampler.writeTile(tile, bytes.data());
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> renderAtlas(const TileSampler& sampler, bool backwards) {
|
||||||
|
int size = sampler.params().tileSize;
|
||||||
|
std::size_t tileBytes = static_cast<std::size_t>(size) * size * 4;
|
||||||
|
int held = renderedTiles(sampler);
|
||||||
|
std::vector<uint8_t> bytes(tileBytes * static_cast<std::size_t>(held));
|
||||||
|
if (backwards) {
|
||||||
|
for (int tile = held - 1; tile >= 0; --tile) {
|
||||||
|
sampler.writeTile(tile, bytes.data() + tileBytes * static_cast<std::size_t>(tile));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int tile = 0; tile < held; ++tile) {
|
||||||
|
sampler.writeTile(tile, bytes.data() + tileBytes * static_cast<std::size_t>(tile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first row and column carry a deliberate edge darkening, which would hand
|
||||||
|
// every tile a luminance spread for free. Contrast is therefore only ever
|
||||||
|
// measured inside that border.
|
||||||
|
struct LuminanceRange {
|
||||||
|
int lowest = 255;
|
||||||
|
int highest = 0;
|
||||||
|
int spread() const { return highest - lowest; }
|
||||||
|
};
|
||||||
|
|
||||||
|
LuminanceRange interiorRange(const TileSampler& sampler, int tile) {
|
||||||
|
LuminanceRange range;
|
||||||
|
int size = sampler.params().tileSize;
|
||||||
|
for (int y = 1; y < size; ++y) {
|
||||||
|
for (int x = 1; x < size; ++x) {
|
||||||
|
int value = luminance(sampler.pixelAt(tile, x, y));
|
||||||
|
if (value < range.lowest) range.lowest = value;
|
||||||
|
if (value > range.highest) range.highest = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSpec withoutGrain(TileSpec spec) {
|
||||||
|
spec.noise = 0.0f;
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Columns in one row that the pattern painted with the accent colour. For a
|
||||||
|
// brick tile those are the vertical mortar joints.
|
||||||
|
std::vector<int> accentColumns(const TileSampler& sampler, const TileSpec& spec, int tileSalt,
|
||||||
|
int y) {
|
||||||
|
std::vector<int> columns;
|
||||||
|
for (int x = 0; x < sampler.params().tileSize; ++x) {
|
||||||
|
if (leansToAccent(sampler.pixelOf(spec, tileSalt, x, y), spec)) columns.push_back(x);
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A brick course is offset from the one above it, so the set of mortar columns
|
||||||
|
// has to differ between courses. Rows that are entirely mortar are the
|
||||||
|
// horizontal joints and say nothing about the offset.
|
||||||
|
int distinctJointLayouts(const TileSampler& sampler, const TileSpec& spec, int tileSalt) {
|
||||||
|
int size = sampler.params().tileSize;
|
||||||
|
std::set<std::vector<int>> layouts;
|
||||||
|
for (int y = 0; y < size; ++y) {
|
||||||
|
std::vector<int> columns = accentColumns(sampler, spec, tileSalt, y);
|
||||||
|
if (columns.empty()) continue;
|
||||||
|
if (static_cast<int>(columns.size()) > size / 2) continue;
|
||||||
|
layouts.insert(columns);
|
||||||
|
}
|
||||||
|
return static_cast<int>(layouts.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSpec syntheticBrick() {
|
||||||
|
TileSpec spec;
|
||||||
|
spec.name = "syntheticBrick";
|
||||||
|
spec.pattern = TilePattern::Brick;
|
||||||
|
spec.material = TileMaterial::Stone;
|
||||||
|
spec.base = TileRgb{30, 30, 30};
|
||||||
|
spec.accent = TileRgb{240, 240, 240};
|
||||||
|
spec.noise = 0.0f;
|
||||||
|
spec.scale = 0.40f;
|
||||||
|
spec.density = 0.50f;
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Same params in, same bytes out. Anything that reached for a clock, a global
|
||||||
|
// counter or an uninitialised buffer would show up here first.
|
||||||
|
VF_TEST(renderingATileTwiceProducesIdenticalBytes) {
|
||||||
|
TileSampler first;
|
||||||
|
TileSampler second;
|
||||||
|
CHECK_EQ(first.palette().size(), tileCount);
|
||||||
|
for (int tile = 0; tile < renderedTiles(first); ++tile) {
|
||||||
|
std::vector<uint8_t> a = renderTile(first, tile);
|
||||||
|
std::vector<uint8_t> b = renderTile(second, tile);
|
||||||
|
bool identical = a == b;
|
||||||
|
CHECK(identical);
|
||||||
|
if (!identical) std::printf(" tile %d is not reproducible\n", tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A tile must not care which tiles were generated before it. The atlas built
|
||||||
|
// back to front has to match the atlas built front to back byte for byte.
|
||||||
|
VF_TEST(theAtlasDoesNotDependOnTheOrderTilesAreGenerated) {
|
||||||
|
TileSampler sampler;
|
||||||
|
std::vector<uint8_t> forwards = renderAtlas(sampler, false);
|
||||||
|
std::vector<uint8_t> backwards = renderAtlas(sampler, true);
|
||||||
|
CHECK_EQ(forwards.size(), backwards.size());
|
||||||
|
CHECK(forwards == backwards);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user