Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_atlas.c

n_atlas multi-page sprite atlas test (headless).

n_atlas multi-page sprite atlas test (headless). Builds a 64x32 image in memory, encodes it to PNG, and loads it as a 4x2 cell grid. Everything asserted here needs no display: cell geometry is derived from the bitmap and the grid, so it can be checked without drawing.

The refusals matter as much as the happy path. A cell index past the end of the grid must report failure rather than blit garbage from the next row, and a caller handed FALSE can fall back to something sensible (literal text for a missing glyph, the previous atlas for a bad page).

Author
Castagnier Mickael
Version
1.0
Date
21/08/2026
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define ALLEGRO_UNSTABLE 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nilorea/n_log.h"
#include "allegro5/allegro_image.h"
#include "allegro5/allegro_memfile.h"
static int failures = 0;
static void expect_true(const char* label, int cond) {
if (!cond) {
n_log(LOG_ERR, "%s: condition false", label);
} else {
n_log(LOG_INFO, "%s: OK", label);
}
}
static void expect_eqf(const char* label, float got, float want) {
float d = got - want;
if (d < 0.0f) {
d = -d;
}
if (d > 0.01f) {
n_log(LOG_ERR, "%s: got %f want %f", label, got, want);
} else {
n_log(LOG_INFO, "%s: OK", label);
}
}
/* encode a cols*cell x rows*cell test image to PNG in memory */
static unsigned char* make_png(int w, int h, size_t* out_len) {
ALLEGRO_BITMAP* bmp = NULL;
ALLEGRO_FILE* mf = NULL;
unsigned char* buf = NULL;
size_t cap = 256 * 1024;
int64_t n = 0;
al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
bmp = al_create_bitmap(w, h);
if (!bmp) {
return NULL;
}
al_set_target_bitmap(bmp);
al_clear_to_color(al_map_rgb(10, 20, 30));
al_set_target_bitmap(NULL);
buf = malloc(cap);
if (!buf) {
al_destroy_bitmap(bmp);
return NULL;
}
mf = al_open_memfile(buf, (int64_t)cap, "w");
if (!mf || !al_save_bitmap_f(mf, ".png", bmp)) {
if (mf) {
al_fclose(mf);
}
al_destroy_bitmap(bmp);
free(buf);
return NULL;
}
n = al_ftell(mf);
al_fclose(mf);
al_destroy_bitmap(bmp);
if (n <= 0) {
free(buf);
return NULL;
}
(*out_len) = (size_t)n;
return buf;
}
int main(void) {
N_ATLAS* atlas = NULL;
unsigned char* png = NULL;
size_t png_len = 0;
float sx = 0.0f, sy = 0.0f, sw = 0.0f, sh = 0.0f;
if (!al_init()) {
n_log(LOG_ERR, "could not init allegro");
return 1;
}
al_init_image_addon();
png = make_png(64, 32, &png_len); /* 4 cols x 2 rows of 16px cells */
expect_true("png encoded", png != NULL && png_len > 0);
if (!png) {
return 1;
}
atlas = new_n_atlas(2);
expect_true("atlas created", atlas != NULL);
expect_true("page 0 not ready before load", !n_atlas_page_ready(atlas, 0));
expect_true("set_page", n_atlas_set_page(atlas, 0, png, png_len, ".png", 4, 2) == TRUE);
expect_true("page 0 ready", n_atlas_page_ready(atlas, 0) == TRUE);
expect_true("page 1 still empty", !n_atlas_page_ready(atlas, 1));
/* cell geometry is derived from the bitmap and grid: 64/4 = 16 wide */
expect_true("cell 0 rect", n_atlas_cell_rect(atlas, 0, 0, &sx, &sy, &sw, &sh) == TRUE);
expect_eqf("cell 0 x", sx, 0.0f);
expect_eqf("cell 0 y", sy, 0.0f);
expect_eqf("cell w", sw, 16.0f);
expect_eqf("cell h", sh, 16.0f);
/* index 5 = col 1, row 1 (left to right, then top to bottom) */
expect_true("cell 5 rect", n_atlas_cell_rect(atlas, 0, 5, &sx, &sy, &sw, &sh) == TRUE);
expect_eqf("cell 5 x", sx, 16.0f);
expect_eqf("cell 5 y", sy, 16.0f);
/* last valid cell of a 4x2 grid is index 7 */
expect_true("cell 7 exists", n_atlas_cell_rect(atlas, 0, 7, NULL, NULL, NULL, NULL) == TRUE);
expect_true("cell 8 past the grid is refused", n_atlas_cell_rect(atlas, 0, 8, NULL, NULL, NULL, NULL) == FALSE);
expect_true("unloaded page is refused", n_atlas_cell_rect(atlas, 1, 0, NULL, NULL, NULL, NULL) == FALSE);
expect_true("out of range page is refused", n_atlas_cell_rect(atlas, 99, 0, NULL, NULL, NULL, NULL) == FALSE);
/* bad arguments are refused, and leave the loaded page alone */
expect_true("zero cols refused", n_atlas_set_page(atlas, 0, png, png_len, ".png", 0, 2) == FALSE);
expect_true("out of range page refused", n_atlas_set_page(atlas, 99, png, png_len, ".png", 4, 2) == FALSE);
expect_true("undecodable bytes refused", n_atlas_set_page(atlas, 1, "not a png", 9, ".png", 4, 2) == FALSE);
expect_true("page 0 survived the refusals", n_atlas_page_ready(atlas, 0) == TRUE);
/* replacing a page frees the old bitmap: ASan catches a leak here */
expect_true("replace page 0", n_atlas_set_page(atlas, 0, png, png_len, ".png", 2, 1) == TRUE);
expect_true("regridded cell w", n_atlas_cell_rect(atlas, 0, 0, NULL, NULL, &sw, NULL) == TRUE);
expect_eqf("regridded cell w = 32", sw, 32.0f);
expect_true("max bitmap size sane", n_atlas_max_bitmap_size() >= 2048);
n_atlas_destroy(&atlas);
expect_true("destroy nulls the handle", atlas == NULL);
n_atlas_destroy(&atlas); /* NULL-safe second call */
free(png);
if (failures) {
n_log(LOG_ERR, "ex_atlas: %d failure(s)", failures);
return 1;
}
printf("ex_atlas: all checks passed\n");
return 0;
}
static int failures
Definition ex_atlas.c:51
static void expect_eqf(const char *label, float got, float want)
Definition ex_atlas.c:62
int main(void)
Definition ex_atlas.c:117
static unsigned char * make_png(int w, int h, size_t *out_len)
Definition ex_atlas.c:76
static void expect_true(const char *label, int cond)
Definition ex_atlas.c:53
int n_atlas_max_bitmap_size(void)
get the largest bitmap dimension this display can hold
Definition n_atlas.c:43
int n_atlas_set_page(N_ATLAS *atlas, size_t page, const void *bytes, size_t len, const char *ext, uint16_t cols, uint16_t rows)
decode bytes into one atlas page
Definition n_atlas.c:112
void n_atlas_destroy(N_ATLAS **atlas)
destroy an atlas and every bitmap it owns
Definition n_atlas.c:75
int n_atlas_page_ready(const N_ATLAS *atlas, size_t page)
test whether a page holds a usable bitmap
Definition n_atlas.c:160
int n_atlas_cell_rect(const N_ATLAS *atlas, size_t page, size_t index, float *sx, float *sy, float *sw, float *sh)
compute the source rectangle of one cell
Definition n_atlas.c:183
N_ATLAS * new_n_atlas(size_t npages)
allocate an atlas holding npages empty pages
Definition n_atlas.c:54
a set of atlas pages addressed as (page, cell index)
Definition n_atlas.h:55
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_INFO
informational
Definition n_log.h:106
Multi-page sprite atlas: random-access cell blitting from a grid.
Common headers and low-level functions & define.
Generic log system.