#include #include #include #include #include #include #include #include #include "candidates.h" #include "colour.h" #include "image.h" static uint32_t failures = 0; #define CHECK(expression) do { \ if (!(expression)) { \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #expression); \ ++failures; \ } \ } while (0) static RGB rgb(uint8_t red, uint8_t green, uint8_t blue) { RGB colour = {red, green, blue}; return colour; } static SDL_Surface *new_surface(int width, int height) { SDL_Surface *surface = SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32); CHECK(surface != NULL); return surface; } static void set_pixel(SDL_Surface *surface, int x, int y, RGB colour, uint8_t alpha) { uint8_t *pixel = (uint8_t *)surface->pixels + (size_t)y * (size_t)surface->pitch + (size_t)x * 4U; pixel[0] = colour.r; pixel[1] = colour.g; pixel[2] = colour.b; pixel[3] = alpha; } static void test_hex_parsing(void) { RGB colour = {0, 0, 0}; CHECK(colour_parse_hex("#1eA2fF", &colour)); CHECK(colour.r == 0x1e && colour.g == 0xa2 && colour.b == 0xff); CHECK(!colour_parse_hex("1ea2ff", &colour)); CHECK(!colour_parse_hex("#123", &colour)); CHECK(!colour_parse_hex("#gg0000", &colour)); } static void test_linear_average(void) { ColourAccumulator accumulator = {0.0, 0.0, 0.0, 0.0}; RGB average; colour_accumulator_add_u8(&accumulator, 0, 0, 0, 255); colour_accumulator_add_u8(&accumulator, 255, 255, 255, 255); CHECK(colour_accumulator_average(&accumulator, &average)); CHECK(average.r >= 187 && average.r <= 188); CHECK(average.r == average.g && average.g == average.b); } static void test_partial_blocks(void) { SDL_Surface *surface = new_surface(17, 1); CandidateSet set; int x; candidates_init(&set); for (x = 0; x < 16; ++x) set_pixel(surface, x, 0, rgb(255, 0, 0), 255); set_pixel(surface, 16, 0, rgb(0, 0, 255), 255); CHECK(image_process_surface(surface, 16, 1, &set)); CHECK(set.count == 2); CHECK(fabs(set.items[0].prevalence - 16.0) < 0.001); CHECK(fabs(set.items[1].prevalence - 1.0) < 0.001); candidates_destroy(&set); SDL_DestroySurface(surface); } static void test_transparency(void) { SDL_Surface *surface = new_surface(2, 1); CandidateSet set; candidates_init(&set); set_pixel(surface, 0, 0, rgb(255, 0, 0), 0); set_pixel(surface, 1, 0, rgb(0, 0, 255), 255); CHECK(image_process_surface(surface, 2, 1, &set)); CHECK(set.count == 1); CHECK(set.items[0].colour.r == 0 && set.items[0].colour.g == 0 && set.items[0].colour.b == 255); CHECK(fabs(set.items[0].prevalence - 1.0) < 0.001); candidates_destroy(&set); SDL_DestroySurface(surface); } static void test_partial_alpha(void) { SDL_Surface *surface = new_surface(2, 1); CandidateSet set; candidates_init(&set); set_pixel(surface, 0, 0, rgb(255, 0, 0), 255); set_pixel(surface, 1, 0, rgb(0, 0, 255), 128); CHECK(image_process_surface(surface, 2, 1, &set)); CHECK(set.count == 1); CHECK(set.items[0].colour.r > set.items[0].colour.b); CHECK(set.items[0].colour.b > 100); CHECK(fabs(set.items[0].prevalence - (1.0 + 128.0 / 255.0)) < 0.001); candidates_destroy(&set); SDL_DestroySurface(surface); } static void test_arena(void) { Arena arena; uint32_t *first; max_align_t *aligned; arena_init(&arena); first = arena_allocate(&arena, 32, sizeof(*first)); aligned = arena_allocate(&arena, 1, sizeof(*aligned)); CHECK(first != NULL && aligned != NULL); CHECK((uintptr_t)aligned % _Alignof(max_align_t) == 0); CHECK(arena_allocate(&arena, SIZE_MAX, 2) == NULL); arena_destroy(&arena); } static void test_deduplication(void) { CandidateSet set; candidates_init(&set); CHECK(candidates_add(&set, rgb(255, 0, 0), 2.0)); CHECK(candidates_add(&set, rgb(250, 5, 5), 3.0)); CHECK(set.count == 1); CHECK(fabs(set.items[0].prevalence - 5.0) < 0.001); CHECK(candidates_add(&set, rgb(0, 0, 255), 1.0)); CHECK(set.count == 2); candidates_destroy(&set); } static void test_candidate_ranking(void) { CandidateSet set; ScoredCandidate *results = NULL; size_t count = 0; size_t index; candidates_init(&set); CHECK(candidates_add(&set, rgb(255, 80, 80), 30.0)); CHECK(candidates_add(&set, rgb(80, 180, 255), 20.0)); CHECK(candidates_add(&set, rgb(100, 255, 120), 10.0)); CHECK(candidates_rank(&set, 3, rgb(30, 30, 30), 0.0f, &results, &count)); CHECK(count == 3); for (index = 1; index < count; ++index) { CHECK(results[index - 1].score >= results[index].score); } results = NULL; CHECK(candidates_rank(&set, 1, rgb(30, 30, 30), 0.0f, &results, &count)); CHECK(count == 1); CHECK(results[0].colour.r == 255 && results[0].colour.g == 80); results = NULL; CHECK(candidates_rank(&set, 3, rgb(30, 30, 30), 1.01f, &results, &count)); CHECK(count == 0 && results == NULL); candidates_destroy(&set); } static void test_chromatic_candidate_admission(void) { CandidateSet set; ScoredCandidate *results = NULL; size_t count = 0; uint32_t value; candidates_init(&set); set.merge_distance = 0.001; for (value = 24; value <= 216; value += 8) { CHECK(candidates_add(&set, rgb((uint8_t)value, (uint8_t)value, (uint8_t)value), 1000.0 - (double)value)); } CHECK(candidates_add(&set, rgb(150, 90, 90), 2.0)); CHECK(candidates_add(&set, rgb(190, 55, 65), 1.0)); CHECK(candidates_rank(&set, 3, rgb(30, 30, 30), 0.0f, &results, &count)); CHECK(count == 3); CHECK((results[0].colour.r == 190 && results[0].colour.g == 55) || (results[1].colour.r == 190 && results[1].colour.g == 55) || (results[2].colour.r == 190 && results[2].colour.g == 55)); candidates_destroy(&set); } static void test_final_tweak(void) { CandidateSet set; ScoredCandidate *results = NULL; size_t count = 0; const RGB background = {0x1e, 0x1e, 0x1e}; RGB original; float original_score; candidates_init(&set); CHECK(candidates_add(&set, rgb(0x7a, 0xa2, 0xf7), 10.0)); CHECK(candidates_rank(&set, 1, background, 0.0f, &results, &count)); CHECK(count == 1); original = results[0].colour; original_score = results[0].score; candidates_tweak(results, count, background); CHECK(results[0].score >= original_score); CHECK(fabs((double)results[0].score - (double)accent_score(results[0].colour, background)) < 0.000001); CHECK(results[0].colour.r != original.r || results[0].colour.g != original.g || results[0].colour.b != original.b); candidates_destroy(&set); } static bool write_bytes(const char *path, const uint8_t *bytes, size_t length) { FILE *file = fopen(path, "wb"); bool success; if (file == NULL) return false; success = fwrite(bytes, 1, length, file) == length; success = fclose(file) == 0 && success; return success; } static void test_static_input(void) { static const uint8_t ppm[] = { 'P', '6', '\n', '2', ' ', '1', '\n', '2', '5', '5', '\n', 255, 0, 0, 255, 0, 0 }; const char *path = "/tmp/accent-extract-static-test.data"; CandidateSet set; CHECK(write_bytes(path, ppm, sizeof(ppm))); candidates_init(&set); CHECK(image_process_file(path, 16, &set)); CHECK(set.count == 1); CHECK(set.items[0].colour.r == 255 && set.items[0].colour.g == 0); CHECK(fabs(set.items[0].prevalence - 2.0) < 0.001); candidates_destroy(&set); CHECK(remove(path) == 0); } static void test_duration_weighting(void) { SDL_Surface *red = new_surface(1, 1); SDL_Surface *blue = new_surface(1, 1); CandidateSet set; candidates_init(&set); set_pixel(red, 0, 0, rgb(255, 0, 0), 255); set_pixel(blue, 0, 0, rgb(0, 0, 255), 255); CHECK(image_process_surface(red, 16, 20, &set)); CHECK(image_process_surface(blue, 16, 200, &set)); CHECK(set.count == 2); CHECK(fabs(set.items[0].prevalence - 20.0) < 0.001); CHECK(fabs(set.items[1].prevalence - 200.0) < 0.001); candidates_destroy(&set); SDL_DestroySurface(red); SDL_DestroySurface(blue); } static void test_farbfeld(void) { static const uint8_t farbfeld[] = { 'f','a','r','b','f','e','l','d', 0,0,0,2, 0,0,0,1, 0xff,0xff, 0,0, 0,0, 0xff,0xff, 0,0, 0,0, 0xff,0xff, 0xff,0xff }; const char *path = "/tmp/accent-extract-farbfeld-test.unknown"; CandidateSet set; CHECK(write_bytes(path, farbfeld, sizeof(farbfeld))); candidates_init(&set); CHECK(image_process_file(path, 1, &set)); CHECK(set.count == 2); CHECK(set.items[0].colour.r == 255 && set.items[0].colour.b == 0); CHECK(set.items[1].colour.r == 0 && set.items[1].colour.b == 255); candidates_destroy(&set); CHECK(remove(path) == 0); } int main(void) { CHECK(SDL_Init(0)); test_hex_parsing(); test_linear_average(); test_partial_blocks(); test_transparency(); test_partial_alpha(); test_arena(); test_deduplication(); test_candidate_ranking(); test_chromatic_candidate_admission(); test_final_tweak(); test_static_input(); test_duration_weighting(); test_farbfeld(); SDL_Quit(); if (failures != 0) { fprintf(stderr, "%" PRIu32 " test(s) failed\n", failures); return EXIT_FAILURE; } puts("all tests passed"); return EXIT_SUCCESS; }