Files
GR-raytracing/tests/test_mesh_overlay.c
wyj 80f9dcb3a3 Feat: Add post-tone-map mesh diagnostics with RGBA overlays
Color antialiased half-edges by ray outcome with configurable Catppuccin colors and default opacity 0.5.

Rasterize premultiplied RGBA8 overlays on the producer and composite in place after writing the clean image. Keep single-frame, movie, and replay output consistent.

Add overlay, CLI, and queue ownership regressions and document the final output architecture.
2026-10-10 01:46:32 -04:00

741 lines
31 KiB
C

/* Standalone regression for the diagnostic mesh overlay (src/mesh_overlay.c).
* It links only the overlay module, so it needs neither a catalog, ray tracing,
* FFTW, nor an output writer. Every assertion targets observable behavior:
* terminal-category colors, half-edge switching, AA coverage, deduplication,
* deterministic ordering, clipping safety and clean failure on bad input. */
#include "mesh_overlay.h"
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int fail(const char *message) {
fprintf(stderr, "%s\n", message);
return -1;
}
static LensVertex make_vertex(double x, double y, int traced,
RayOutcome outcome) {
LensVertex vertex;
memset(&vertex, 0, sizeof vertex);
vertex.image_x = x;
vertex.image_y = y;
vertex.traced = traced;
vertex.outcome = outcome;
return vertex;
}
static int channel_at(const unsigned char *rgb, int width, int x, int y,
int channel) {
return rgb[3 * ((size_t)y * (size_t)width + (size_t)x) + (size_t)channel];
}
static int test_default_settings(void) {
const MeshOverlaySettings settings = mesh_overlay_default_settings();
static const unsigned char expected[MESH_OVERLAY_CATEGORY_COUNT][3] = {
{0x7F, 0x84, 0x9C}, {0xCB, 0xA6, 0xF7}, {0xF9, 0xE2, 0xAF},
{0xF3, 0x8B, 0xA8}, {0x89, 0xB4, 0xFA}};
if (settings.opacity != 0.5)
return fail("default opacity is not 0.5");
for (int category = 0; category < MESH_OVERLAY_CATEGORY_COUNT; ++category)
for (int channel = 0; channel < 3; ++channel)
if (settings.colors[category][channel] != expected[category][channel])
return fail("default palette mismatch");
return 0;
}
static int test_parse_color(void) {
unsigned char rgb[3] = {1, 2, 3};
if (mesh_overlay_parse_color("#7F849C", rgb) != 0 || rgb[0] != 0x7F ||
rgb[1] != 0x84 || rgb[2] != 0x9C)
return fail("parse uppercase failed");
if (mesh_overlay_parse_color("#7f849c", rgb) != 0 || rgb[0] != 0x7F ||
rgb[1] != 0x84 || rgb[2] != 0x9C)
return fail("parse lowercase failed");
if (mesh_overlay_parse_color("#000000", rgb) != 0 || rgb[0] || rgb[1] ||
rgb[2])
return fail("parse black failed");
static const char *const bad[] = {"", "#", "7F849C",
"#7F849", "#7F849C0", "#GG849C",
"#7F84 9C", "#7F849c ", " #7F849C",
"#12345g", "#12345G0"};
for (size_t i = 0; i < sizeof bad / sizeof *bad; ++i)
if (mesh_overlay_parse_color(bad[i], rgb) != -1)
return fail("accepted an invalid color string");
if (mesh_overlay_parse_color(NULL, rgb) != -1)
return fail("accepted NULL text");
if (mesh_overlay_parse_color("#7F849C", NULL) != -1)
return fail("accepted NULL output");
return 0;
}
/* Deduplication, canonical ordering and per-vertex category assignment. */
static int test_prepare_dedup_categories(void) {
LensVertex vertices[4] = {
make_vertex(10, 10, 1, RAY_OUTCOME_ESCAPED),
make_vertex(30, 10, 1, RAY_OUTCOME_DARK),
make_vertex(10, 30, 1, RAY_OUTCOME_UNRESOLVED),
make_vertex(30, 30, 1, RAY_OUTCOME_INCOMPLETE)};
LensTriangle triangles[2] = {{{0, 1, 2}, 0, 0, 0}, {{0, 2, 3}, 0, 0, 0}};
FrameLensMesh mesh = {.vertices = vertices,
.vertex_count = 4,
.triangles = triangles,
.triangle_count = 2};
MeshOverlayLines lines = {0};
if (mesh_overlay_prepare(&mesh, &lines) != 0)
return fail("prepare failed on a valid quad");
/* The shared diagonal (0,2) must appear exactly once. */
if (lines.count != 5) {
mesh_overlay_lines_destroy(&lines);
return fail("unique edge count is not 5");
}
static const struct {
unsigned char c0, c1;
} expected[5] = {
{MESH_OVERLAY_CATEGORY_ESCAPE, MESH_OVERLAY_CATEGORY_DARK},
{MESH_OVERLAY_CATEGORY_ESCAPE, MESH_OVERLAY_CATEGORY_UNRESOLVED},
{MESH_OVERLAY_CATEGORY_ESCAPE, MESH_OVERLAY_CATEGORY_INCOMPLETE},
{MESH_OVERLAY_CATEGORY_DARK, MESH_OVERLAY_CATEGORY_UNRESOLVED},
{MESH_OVERLAY_CATEGORY_UNRESOLVED, MESH_OVERLAY_CATEGORY_INCOMPLETE}};
for (size_t i = 0; i < lines.count; ++i)
if (lines.lines[i].category0 != expected[i].c0 ||
lines.lines[i].category1 != expected[i].c1) {
mesh_overlay_lines_destroy(&lines);
return fail("edge category or deterministic order mismatch");
}
if (lines.lines[0].x0 != 10 || lines.lines[0].y0 != 10 ||
lines.lines[0].x1 != 30 || lines.lines[0].y1 != 10) {
mesh_overlay_lines_destroy(&lines);
return fail("edge coordinates mismatch");
}
/* Lines must copy coordinates, never alias the mutable mesh. */
vertices[0].image_x = 999;
if (lines.lines[0].x0 != 10) {
mesh_overlay_lines_destroy(&lines);
return fail("overlay lines alias the live mesh");
}
mesh_overlay_lines_destroy(&lines);
if (lines.lines != NULL || lines.count != 0)
return fail("destroy did not reset the handle");
mesh_overlay_lines_destroy(NULL);
return 0;
}
static int test_untraced_category(void) {
LensVertex vertices[3] = {
make_vertex(10, 10, 1, RAY_OUTCOME_ESCAPED),
/* traced == 0 must win over the stale outcome value. */
make_vertex(30, 10, 0, RAY_OUTCOME_ESCAPED),
make_vertex(10, 30, 1, RAY_OUTCOME_INCOMPLETE)};
LensTriangle triangle = {{0, 1, 2}, 0, 0, 0};
FrameLensMesh mesh = {.vertices = vertices,
.vertex_count = 3,
.triangles = &triangle,
.triangle_count = 1};
MeshOverlayLines lines = {0};
if (mesh_overlay_prepare(&mesh, &lines) != 0)
return fail("prepare failed for untraced mesh");
int saw_untraced = 0;
for (size_t i = 0; i < lines.count; ++i)
if (lines.lines[i].category0 == MESH_OVERLAY_CATEGORY_UNTRACED ||
lines.lines[i].category1 == MESH_OVERLAY_CATEGORY_UNTRACED)
saw_untraced = 1;
mesh_overlay_lines_destroy(&lines);
return saw_untraced ? 0 : fail("untraced vertex category missing");
}
/* Boundary edges are emitted regardless of winding, and reversing the winding
* cannot change the deterministic output. */
static int test_boundary_and_winding(void) {
LensVertex vertices[3] = {
make_vertex(10, 10, 1, RAY_OUTCOME_ESCAPED),
make_vertex(30, 10, 1, RAY_OUTCOME_DARK),
make_vertex(10, 30, 1, RAY_OUTCOME_UNRESOLVED)};
LensTriangle forward[1] = {{{0, 1, 2}, 0, 0, 0}};
LensTriangle reversed[1] = {{{2, 1, 0}, 0, 0, 0}};
FrameLensMesh mesh_a = {.vertices = vertices,
.vertex_count = 3,
.triangles = forward,
.triangle_count = 1};
FrameLensMesh mesh_b = {.vertices = vertices,
.vertex_count = 3,
.triangles = reversed,
.triangle_count = 1};
MeshOverlayLines a = {0}, b = {0};
if (mesh_overlay_prepare(&mesh_a, &a) != 0 ||
mesh_overlay_prepare(&mesh_b, &b) != 0) {
mesh_overlay_lines_destroy(&a);
mesh_overlay_lines_destroy(&b);
return fail("prepare failed for single triangle");
}
int ok = a.count == 3 && b.count == 3 &&
memcmp(a.lines, b.lines, a.count * sizeof *a.lines) == 0;
mesh_overlay_lines_destroy(&a);
mesh_overlay_lines_destroy(&b);
return ok ? 0 : fail("boundary/winding determinism failed");
}
/* Off-mesh probe witnesses have no triangle edge and must never be emitted or
* drawn as a vertex dot. */
static int test_isolated_witness_not_drawn(void) {
LensVertex vertices[4] = {
make_vertex(10, 50, 1, RAY_OUTCOME_ESCAPED),
make_vertex(30, 50, 1, RAY_OUTCOME_ESCAPED),
make_vertex(20, 30, 1, RAY_OUTCOME_ESCAPED),
make_vertex(90, 90, 1, RAY_OUTCOME_ESCAPED)};
vertices[3].diagnostic_probe = 1;
vertices[3].probe_edge[0] = 0;
vertices[3].probe_edge[1] = 1;
LensTriangle triangle = {{0, 1, 2}, 0, 0, 0};
FrameLensMesh mesh = {.vertices = vertices,
.vertex_count = 4,
.triangles = &triangle,
.triangle_count = 1};
const int width = 100, height = 100;
MeshOverlayLines lines = {0};
unsigned char *rgb = calloc((size_t)width * height * 3, 1);
const MeshOverlaySettings settings = mesh_overlay_default_settings();
if (rgb == NULL || mesh_overlay_prepare(&mesh, &lines) != 0) {
free(rgb);
mesh_overlay_lines_destroy(&lines);
return fail("prepare failed for witness mesh");
}
if (lines.count != 3) {
free(rgb);
mesh_overlay_lines_destroy(&lines);
return fail("isolated witness added an edge");
}
if (mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) != 0) {
free(rgb);
mesh_overlay_lines_destroy(&lines);
return fail("draw failed for witness mesh");
}
const int witness_painted =
channel_at(rgb, width, 90, 90, 0) != 0 ||
channel_at(rgb, width, 90, 90, 1) != 0 ||
channel_at(rgb, width, 90, 90, 2) != 0;
free(rgb);
mesh_overlay_lines_destroy(&lines);
return witness_painted ? fail("isolated witness vertex was drawn as a dot")
: 0;
}
static int test_draw_category_colors(void) {
MeshOverlaySettings settings = mesh_overlay_default_settings();
settings.opacity = 1.0;
const int width = 80, height = 40;
for (int category = 0; category < MESH_OVERLAY_CATEGORY_COUNT; ++category) {
unsigned char *rgb = calloc((size_t)width * height * 3, 1);
if (rgb == NULL)
return fail("allocation failed");
MeshOverlayLine line = {.x0 = 10,
.y0 = 20,
.x1 = 70,
.y1 = 20,
.category0 = (unsigned char)category,
.category1 = (unsigned char)category};
MeshOverlayLines lines = {.lines = &line, .count = 1};
int ok = mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) == 0;
for (int channel = 0; ok && channel < 3; ++channel)
if (channel_at(rgb, width, 40, 20, channel) !=
settings.colors[category][channel])
ok = 0;
free(rgb);
if (!ok)
return fail("category color mismatch");
}
return 0;
}
static int test_halves_and_switch(void) {
MeshOverlaySettings settings = mesh_overlay_default_settings();
settings.opacity = 1.0;
const int width = 60, height = 40;
unsigned char *rgb = calloc((size_t)width * height * 3, 1);
if (rgb == NULL)
return fail("allocation failed");
MeshOverlayLine line = {.x0 = 10,
.y0 = 20,
.x1 = 50,
.y1 = 20,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_DARK};
MeshOverlayLines lines = {.lines = &line, .count = 1};
int ok = mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) == 0;
/* The switch sits at the major-axis midpoint x = 30. Interior pixels are
* fully covered, so each must equal exactly one endpoint color: the whole
* edge is rasterized once, never as two blends that would smear the switch. */
for (int channel = 0; ok && channel < 3; ++channel) {
if (channel_at(rgb, width, 11, 20, channel) !=
settings.colors[MESH_OVERLAY_CATEGORY_ESCAPE][channel] ||
channel_at(rgb, width, 29, 20, channel) !=
settings.colors[MESH_OVERLAY_CATEGORY_ESCAPE][channel] ||
channel_at(rgb, width, 30, 20, channel) !=
settings.colors[MESH_OVERLAY_CATEGORY_DARK][channel] ||
channel_at(rgb, width, 49, 20, channel) !=
settings.colors[MESH_OVERLAY_CATEGORY_DARK][channel])
ok = 0;
}
free(rgb);
return ok ? 0 : fail("half-edge color switch failed");
}
static int test_antialiasing(void) {
MeshOverlaySettings settings = mesh_overlay_default_settings();
settings.opacity = 1.0;
const int width = 60, height = 60;
unsigned char *rgb = calloc((size_t)width * height * 3, 1);
if (rgb == NULL)
return fail("allocation failed");
MeshOverlayLine line = {.x0 = 10,
.y0 = 10,
.x1 = 50,
.y1 = 20,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_ESCAPE};
MeshOverlayLines lines = {.lines = &line, .count = 1};
int ok = mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) == 0;
const int full = settings.colors[MESH_OVERLAY_CATEGORY_ESCAPE][0];
int partial = 0;
for (int y = 0; y < height; ++y)
for (int x = 0; x < width; ++x) {
const int value = channel_at(rgb, width, x, y, 0);
if (value > 0 && value < full)
++partial;
}
free(rgb);
return ok && partial > 0 ? 0 : fail("no antialiased partial coverage");
}
static int test_subpixel_and_zero_length(void) {
MeshOverlaySettings settings = mesh_overlay_default_settings();
settings.opacity = 1.0;
unsigned char pixels[8 * 8 * 3] = {0};
MeshOverlayLine line = {.x0 = 2.1, .y0 = 3.0, .x1 = 2.4, .y1 = 3.0,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_ESCAPE};
MeshOverlayLines lines = {.lines = &line, .count = 1};
if (mesh_overlay_draw_rgb8(&lines, pixels, 8, 8, &settings))
return fail("subpixel draw failed");
for (int channel = 0; channel < 3; ++channel)
if (channel_at(pixels, 8, 2, 3, channel) !=
lround((line.x1 - line.x0) * settings.colors[0][channel]))
return fail("subpixel edge applied overlapping endpoint blends");
memset(pixels, 0, sizeof pixels);
line.x1 = line.x0;
if (mesh_overlay_draw_rgb8(&lines, pixels, 8, 8, &settings))
return fail("zero-length draw failed");
for (size_t i = 0; i < sizeof pixels; ++i)
if (pixels[i])
return fail("zero-length edge became a vertex dot");
return 0;
}
static int test_high_white_background(void) {
const MeshOverlaySettings settings = mesh_overlay_default_settings();
const int width = 80, height = 40;
unsigned char *rgb = malloc((size_t)width * height * 3);
if (rgb == NULL)
return fail("allocation failed");
memset(rgb, 255, (size_t)width * height * 3);
MeshOverlayLine line = {.x0 = 10,
.y0 = 20,
.x1 = 60,
.y1 = 20,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_ESCAPE};
MeshOverlayLines lines = {.lines = &line, .count = 1};
int ok = mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) == 0;
for (int channel = 0; ok && channel < 3; ++channel) {
const long expected =
lround(255.0 * (1.0 - settings.opacity) +
settings.colors[MESH_OVERLAY_CATEGORY_ESCAPE][channel] *
settings.opacity);
if (channel_at(rgb, width, 40, 20, channel) != expected)
ok = 0;
}
/* Still clearly visible against white. */
if (ok && channel_at(rgb, width, 40, 20, 0) == 255)
ok = 0;
free(rgb);
return ok ? 0 : fail("overlay not visible on a high-white background");
}
static int test_opacity_extremes(void) {
const int width = 60, height = 40;
MeshOverlayLine line = {.x0 = 10,
.y0 = 20,
.x1 = 50,
.y1 = 20,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_ESCAPE};
MeshOverlayLines lines = {.lines = &line, .count = 1};
unsigned char *rgb = malloc((size_t)width * height * 3);
if (rgb == NULL)
return fail("allocation failed");
MeshOverlaySettings settings = mesh_overlay_default_settings();
memset(rgb, 0x33, (size_t)width * height * 3);
settings.opacity = 0.0;
if (mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) != 0) {
free(rgb);
return fail("draw failed at opacity 0");
}
for (int i = 0; i < width * height * 3; ++i)
if (rgb[i] != 0x33) {
free(rgb);
return fail("opacity 0 changed the image");
}
memset(rgb, 0x00, (size_t)width * height * 3);
settings.opacity = 1.0;
int ok = mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) == 0;
for (int channel = 0; ok && channel < 3; ++channel)
if (channel_at(rgb, width, 30, 20, channel) !=
settings.colors[MESH_OVERLAY_CATEGORY_ESCAPE][channel])
ok = 0;
free(rgb);
return ok ? 0 : fail("opacity 1 did not apply the full color");
}
static int test_clipping_and_huge_coordinates(void) {
const MeshOverlaySettings settings = mesh_overlay_default_settings();
const int width = 64, height = 64;
unsigned char *rgb = calloc((size_t)width * height * 3, 1);
if (rgb == NULL)
return fail("allocation failed");
/* A horizontal line far beyond both image edges must still paint row 30 and
* terminate in bounded time. */
MeshOverlayLine huge = {.x0 = -1e15,
.y0 = 30,
.x1 = 1e15,
.y1 = 30,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_DARK};
MeshOverlayLines lines = {.lines = &huge, .count = 1};
int ok = mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) == 0;
int painted = 0;
for (int x = 0; x < width; ++x)
if (channel_at(rgb, width, x, 30, 0) != 0)
painted = 1;
if (!ok || !painted) {
free(rgb);
return fail("huge coordinate line was not clipped into view");
}
/* A fully offscreen line leaves the buffer untouched. */
memset(rgb, 0, (size_t)width * height * 3);
MeshOverlayLine offscreen = {.x0 = 1000,
.y0 = 1000,
.x1 = 2000,
.y1 = 1000,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_ESCAPE};
lines.lines = &offscreen;
if (mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) != 0) {
free(rgb);
return fail("offscreen line returned an error");
}
for (int i = 0; i < width * height * 3; ++i)
if (rgb[i] != 0) {
free(rgb);
return fail("offscreen line painted the image");
}
/* A huge diagonal must not overflow the integer conversions. */
MeshOverlayLine diagonal = {.x0 = -1e12,
.y0 = -1e12,
.x1 = 1e12,
.y1 = 1e12,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_DARK};
lines.lines = &diagonal;
if (mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) != 0) {
free(rgb);
return fail("huge diagonal returned an error");
}
free(rgb);
return 0;
}
static int test_extreme_magnitudes(void) {
const MeshOverlaySettings settings = mesh_overlay_default_settings();
const int width = 64, height = 64;
const size_t bytes = (size_t)width * height * 3;
unsigned char *rgb = malloc(bytes);
if (rgb == NULL)
return fail("allocation failed");
/* -DBL_MAX..+DBL_MAX overflows the endpoint difference to infinity: the whole
* batch must be rejected before painting and the buffer left untouched. */
MeshOverlayLine bad = {.x0 = -DBL_MAX,
.y0 = 10,
.x1 = DBL_MAX,
.y1 = 10,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_ESCAPE};
MeshOverlayLines lines = {.lines = &bad, .count = 1};
memset(rgb, 0x5A, bytes);
int rejected =
mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) == -1;
int unchanged = 1;
for (size_t i = 0; i < bytes; ++i)
if (rgb[i] != 0x5A)
unchanged = 0;
if (!rejected || !unchanged) {
free(rgb);
return fail("+-DBL_MAX x-delta not rejected unchanged");
}
bad.x0 = 10;
bad.x1 = 10;
bad.y0 = -DBL_MAX;
bad.y1 = DBL_MAX;
memset(rgb, 0x5A, bytes);
rejected = mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) == -1;
unchanged = 1;
for (size_t i = 0; i < bytes; ++i)
if (rgb[i] != 0x5A)
unchanged = 0;
if (!rejected || !unchanged) {
free(rgb);
return fail("+-DBL_MAX y-delta not rejected unchanged");
}
/* Same-sign DBL_MAX endpoints have a finite difference and a finite (non
* overflowing) midpoint; the segment is entirely offscreen, so it is skipped
* safely without painting. */
MeshOverlayLine same = {.x0 = DBL_MAX,
.y0 = 10,
.x1 = DBL_MAX,
.y1 = 30,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_DARK};
lines.lines = &same;
memset(rgb, 0x5A, bytes);
if (mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) != 0) {
free(rgb);
return fail("same-sign DBL_MAX segment was not handled safely");
}
for (size_t i = 0; i < bytes; ++i)
if (rgb[i] != 0x5A) {
free(rgb);
return fail("offscreen same-sign DBL_MAX segment painted the image");
}
free(rgb);
return 0;
}
static int test_clipped_midpoint_uses_original(void) {
MeshOverlaySettings settings = mesh_overlay_default_settings();
settings.opacity = 1.0;
const int width = 64, height = 40;
unsigned char *rgb = calloc((size_t)width * height * 3, 1);
if (rgb == NULL)
return fail("allocation failed");
/* The original midpoint is -40, so the entire visible span [0, 19] lies in
* the second half and every visible pixel must use category1. A midpoint
* recomputed from the clipped endpoints would wrongly color the left half. */
MeshOverlayLine line = {.x0 = -100,
.y0 = 20,
.x1 = 20,
.y1 = 20,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_DARK};
MeshOverlayLines lines = {.lines = &line, .count = 1};
int ok = mesh_overlay_draw_rgb8(&lines, rgb, width, height, &settings) == 0;
static const int probes[] = {0, 1, 5, 10, 19};
for (size_t p = 0; ok && p < sizeof probes / sizeof *probes; ++p)
for (int channel = 0; channel < 3; ++channel)
if (channel_at(rgb, width, probes[p], 20, channel) !=
settings.colors[MESH_OVERLAY_CATEGORY_DARK][channel])
ok = 0;
free(rgb);
return ok ? 0
: fail("clipped edge did not use the original midpoint category");
}
static int test_invalid_arguments(void) {
MeshOverlaySettings settings = mesh_overlay_default_settings();
unsigned char pixels[4 * 4 * 3] = {0};
MeshOverlayLines lines = {0};
if (mesh_overlay_prepare(NULL, &lines) != -1)
return fail("prepare accepted NULL mesh");
if (mesh_overlay_prepare(NULL, NULL) != -1)
return fail("prepare accepted NULL lines");
FrameLensMesh empty = {0};
if (mesh_overlay_prepare(&empty, &lines) != 0 || lines.count != 0 ||
lines.lines != NULL)
return fail("empty mesh did not produce an empty edge set");
LensVertex vertices[3] = {
make_vertex(10, 10, 1, RAY_OUTCOME_ESCAPED),
make_vertex(30, 10, 1, RAY_OUTCOME_ESCAPED),
make_vertex(10, 30, 1, RAY_OUTCOME_ESCAPED)};
LensTriangle triangle = {{0, 1, 2}, 0, 0, 0};
FrameLensMesh mesh = {.vertices = vertices,
.vertex_count = 2, /* vertex 2 is out of range */
.triangles = &triangle,
.triangle_count = 1};
if (mesh_overlay_prepare(&mesh, &lines) != -1)
return fail("prepare accepted an out-of-range vertex");
mesh.vertex_count = 3;
vertices[2].image_x = NAN;
if (mesh_overlay_prepare(&mesh, &lines) != -1)
return fail("prepare accepted a nonfinite coordinate");
vertices[2].image_x = 10;
mesh.triangles = NULL;
if (mesh_overlay_prepare(&mesh, &lines) != -1)
return fail("prepare accepted NULL triangles");
FrameLensMesh overflow = {0};
overflow.triangle_count = SIZE_MAX; /* > SIZE_MAX / 3, rejected before use */
if (mesh_overlay_prepare(&overflow, &lines) != -1)
return fail("prepare accepted an overflowing triangle count");
MeshOverlayLine line = {.x0 = 1,
.y0 = 1,
.x1 = 3,
.y1 = 1,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_ESCAPE};
MeshOverlayLines one = {.lines = &line, .count = 1};
if (mesh_overlay_draw_rgb8(NULL, pixels, 4, 4, &settings) != -1)
return fail("draw accepted NULL lines");
if (mesh_overlay_draw_rgb8(&one, NULL, 4, 4, &settings) != -1)
return fail("draw accepted NULL pixels");
if (mesh_overlay_draw_rgb8(&one, pixels, 0, 4, &settings) != -1)
return fail("draw accepted zero width");
if (mesh_overlay_draw_rgb8(&one, pixels, 4, -1, &settings) != -1)
return fail("draw accepted negative height");
if (mesh_overlay_draw_rgb8(&one, pixels, 4, 4, NULL) != -1)
return fail("draw accepted NULL settings");
MeshOverlayLines null_lines = {.lines = NULL, .count = 1};
if (mesh_overlay_draw_rgb8(&null_lines, pixels, 4, 4, &settings) != -1)
return fail("draw accepted a NULL line array with a nonzero count");
/* Dimensions too near INT_MAX would overflow the raster's y+1/x+1 casts. */
if (mesh_overlay_draw_rgb8(&one, pixels, INT_MAX, 1, &settings) != -1)
return fail("draw accepted a width near INT_MAX");
if (mesh_overlay_draw_rgb8(&one, pixels, 1, INT_MAX, &settings) != -1)
return fail("draw accepted a height near INT_MAX");
/* An unallocatable line count must be rejected before the array dereference. */
MeshOverlayLines overflow_lines = {.lines = &line, .count = SIZE_MAX};
if (mesh_overlay_draw_rgb8(&overflow_lines, pixels, 4, 4, &settings) != -1)
return fail("draw accepted an overflowing line count");
MeshOverlaySettings bad = settings;
bad.opacity = NAN;
if (mesh_overlay_draw_rgb8(&one, pixels, 4, 4, &bad) != -1)
return fail("draw accepted NaN opacity");
bad.opacity = 1.5;
if (mesh_overlay_draw_rgb8(&one, pixels, 4, 4, &bad) != -1)
return fail("draw accepted opacity above 1");
bad.opacity = -0.1;
if (mesh_overlay_draw_rgb8(&one, pixels, 4, 4, &bad) != -1)
return fail("draw accepted negative opacity");
MeshOverlayLine bad_category = {.x0 = 1,
.y0 = 1,
.x1 = 3,
.y1 = 1,
.category0 = 99,
.category1 = MESH_OVERLAY_CATEGORY_ESCAPE};
MeshOverlayLines bad_lines = {.lines = &bad_category, .count = 1};
if (mesh_overlay_draw_rgb8(&bad_lines, pixels, 4, 4, &settings) != -1)
return fail("draw accepted an out-of-range category");
MeshOverlayLine bad_coord = {.x0 = NAN,
.y0 = 1,
.x1 = 3,
.y1 = 1,
.category0 = MESH_OVERLAY_CATEGORY_ESCAPE,
.category1 = MESH_OVERLAY_CATEGORY_ESCAPE};
bad_lines.lines = &bad_coord;
if (mesh_overlay_draw_rgb8(&bad_lines, pixels, 4, 4, &settings) != -1)
return fail("draw accepted a nonfinite coordinate");
MeshOverlayLines none = {.lines = NULL, .count = 0};
if (mesh_overlay_draw_rgb8(&none, pixels, 4, 4, &settings) != 0)
return fail("draw failed on an empty edge set");
return 0;
}
static int test_rgba_layer(void) {
LensVertex vertices[3] = {
make_vertex(2, 4, 1, RAY_OUTCOME_ESCAPED),
make_vertex(12, 4, 1, RAY_OUTCOME_ESCAPED),
make_vertex(2, 12, 1, RAY_OUTCOME_ESCAPED)};
LensTriangle triangles[2] = {{{0, 1, 2}, 0, 0, 0},
{{2, 1, 0}, 0, 0, 0}};
FrameLensMesh mesh = {.vertices = vertices, .vertex_count = 3,
.triangles = triangles, .triangle_count = 1};
MeshOverlaySettings settings = mesh_overlay_default_settings();
settings.opacity = 0.5;
MeshOverlayLayer layer = {0}, duplicate = {0};
if (mesh_overlay_build_layer(&mesh, 16, 16, &settings, &layer))
return fail("RGBA layer build failed");
int ok = layer.width == 16 && layer.height == 16;
const unsigned char *on_edge = &layer.rgba[4 * (4 * 16 + 5)];
for (int c = 0; c < 3; ++c)
ok &= on_edge[c] == lround(settings.colors[0][c] * settings.opacity);
ok &= on_edge[3] == 128;
for (size_t p = 0; p < 16 * 16; ++p)
for (int c = 0; c < 3; ++c)
ok &= layer.rgba[4 * p + c] <= layer.rgba[4 * p + 3];
mesh.triangle_count = 2;
ok &= mesh_overlay_build_layer(&mesh, 16, 16, &settings, &duplicate) == 0;
if (duplicate.rgba != NULL)
ok &= memcmp(layer.rgba, duplicate.rgba, 16 * 16 * 4) == 0;
unsigned char image[16 * 16 * 3];
memset(image, 255, sizeof image);
ok &= mesh_overlay_composite_rgb8(&layer, image, 16, 16) == 0;
for (int c = 0; c < 3; ++c)
ok &= image[3 * (4 * 16 + 5) + c] == on_edge[c] + 127;
ok &= image[0] == 255; /* no overlay coverage, not a full-image gray tint */
mesh_overlay_layer_destroy(&layer);
mesh_overlay_layer_destroy(&duplicate);
ok &= layer.rgba == NULL && layer.width == 0 && layer.height == 0;
settings.opacity = 0.0;
ok &= mesh_overlay_build_layer(&mesh, 16, 16, &settings, &layer) == 0;
if (layer.rgba != NULL)
for (size_t i = 0; i < 16 * 16 * 4; ++i)
ok &= layer.rgba[i] == 0;
unsigned char before[sizeof image];
memcpy(before, image, sizeof image);
ok &= mesh_overlay_composite_rgb8(&layer, image, 16, 16) == 0;
ok &= memcmp(before, image, sizeof image) == 0;
ok &= mesh_overlay_composite_rgb8(&layer, image, 15, 16) == -1;
mesh_overlay_layer_destroy(&layer);
ok &= mesh_overlay_build_layer(&mesh, 0, 16, &settings, &layer) == -1;
ok &= mesh_overlay_build_layer(&mesh, INT_MAX, INT_MAX, &settings, &layer) == -1;
vertices[0].image_x = NAN;
ok &= mesh_overlay_build_layer(&mesh, 16, 16, &settings, &layer) == -1;
ok &= layer.rgba == NULL;
return ok ? 0 : fail("RGBA premultiplication/composition/ownership regression");
}
static int test_rgba_composition_extremes(void) {
unsigned char rgba[] = {0, 0, 0, 0, 20, 40, 60, 255, 10, 20, 30, 128};
const MeshOverlayLayer layer = {.rgba = rgba, .width = 3, .height = 1};
unsigned char rgb[] = {100, 110, 120, 100, 100, 100, 100, 100, 100};
const unsigned char expected[] = {100, 110, 120, 20, 40, 60, 60, 70, 80};
return mesh_overlay_composite_rgb8(&layer, rgb, 3, 1) == 0 &&
memcmp(rgb, expected, sizeof rgb) == 0
? 0 : fail("RGBA transparent/opaque/partial-alpha composition");
}
int main(void) {
if (test_default_settings() || test_parse_color() ||
test_prepare_dedup_categories() || test_untraced_category() ||
test_boundary_and_winding() || test_isolated_witness_not_drawn() ||
test_draw_category_colors() || test_halves_and_switch() ||
test_antialiasing() || test_subpixel_and_zero_length() ||
test_high_white_background() ||
test_opacity_extremes() || test_clipping_and_huge_coordinates() ||
test_extreme_magnitudes() || test_clipped_midpoint_uses_original() ||
test_invalid_arguments() || test_rgba_layer() ||
test_rgba_composition_extremes())
return 1;
return 0;
}