6 n__atlas_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

src/n_atlas.c

Multi-page sprite atlas: random-access cell blitting from a grid. More...

Detailed Description

Multi-page sprite atlas: random-access cell blitting from a grid.

Author: Castagnier Mickael aka Gull Ra Driel

Version: 1.0

Date: 21/08/2026

Source code

/*
 * 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
 */


#include "nilorea/n_atlas.h"

#include "allegro5/allegro_image.h"
#include "allegro5/allegro_memfile.h"

int n_atlas_max_bitmap_size(void) {
    ALLEGRO_DISPLAY* d = al_get_current_display();
    int m = d ? al_get_display_option(d, ALLEGRO_MAX_BITMAP_SIZE) : 0;
    return m > 0 ? m : 2048;
}

N_ATLAS* new_n_atlas(size_t npages) {
    N_ATLAS* atlas = NULL;

    __n_assert(npages > 0, return NULL);

    Malloc(atlas, N_ATLAS, 1);
    __n_assert(atlas, return NULL);

    Malloc(atlas->pages, N_ATLAS_PAGE, npages);
    if (!atlas->pages) {
        Free(atlas);
        return NULL;
    }
    atlas->npages = npages;
    return atlas;
}

void n_atlas_destroy(N_ATLAS** atlas) {
    __n_assert(atlas && (*atlas), return);

    if ((*atlas)->pages) {
        size_t it = 0;
        for (it = 0; it < (*atlas)->npages; it++) {
            if ((*atlas)->pages[it].bmp) {
                al_destroy_bitmap((*atlas)->pages[it].bmp);
                (*atlas)->pages[it].bmp = NULL;
            }
        }
        Free((*atlas)->pages);
    }
    Free((*atlas));
    return;
}

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) {
    ALLEGRO_FILE* mf = NULL;
    ALLEGRO_BITMAP* bmp = NULL;
    int maxpx = 0;

    __n_assert(atlas, return FALSE);
    __n_assert(atlas->pages, return FALSE);
    __n_assert(bytes, return FALSE);
    __n_assert(ext, return FALSE);
    __n_assert(page < atlas->npages, return FALSE);
    __n_assert(len > 0, return FALSE);
    __n_assert(cols > 0, return FALSE);
    __n_assert(rows > 0, return FALSE);

    mf = al_open_memfile((void*)bytes, (int64_t)len, "r");
    if (!mf) {
        n_log(LOG_ERR, "could not open a memfile over %zu bytes", len);
        return FALSE;
    }
    bmp = al_load_bitmap_f(mf, ext);
    al_fclose(mf);
    if (!bmp) {
        n_log(LOG_ERR, "could not decode a '%s' atlas page from %zu bytes", ext, len);
        return FALSE;
    }

    maxpx = n_atlas_max_bitmap_size();
    if (al_get_bitmap_width(bmp) > maxpx || al_get_bitmap_height(bmp) > maxpx) {
        n_log(LOG_ERR, "atlas page %zu is %dx%d, over this display's %d limit", page, al_get_bitmap_width(bmp), al_get_bitmap_height(bmp), maxpx);
        al_destroy_bitmap(bmp);
        return FALSE;
    }

    if (atlas->pages[page].bmp) {
        al_destroy_bitmap(atlas->pages[page].bmp);
    }
    atlas->pages[page].bmp = bmp;
    atlas->pages[page].cols = cols;
    atlas->pages[page].rows = rows;
    return TRUE;
}

int n_atlas_page_ready(const N_ATLAS* atlas, size_t page) {
    __n_assert(atlas, return FALSE);
    __n_assert(atlas->pages, return FALSE);
    if (page >= atlas->npages) {
        return FALSE;
    }
    return (atlas->pages[page].bmp && atlas->pages[page].cols > 0 && atlas->pages[page].rows > 0) ? TRUE : FALSE;
}

int n_atlas_cell_rect(const N_ATLAS* atlas, size_t page, size_t index, float* sx, float* sy, float* sw, float* sh) {
    size_t col = 0, row = 0;
    float cw = 0.0, ch = 0.0;

    if (!n_atlas_page_ready(atlas, page)) {
        return FALSE;
    }
    col = index % (size_t)atlas->pages[page].cols;
    row = index / (size_t)atlas->pages[page].cols;
    if (row >= (size_t)atlas->pages[page].rows) {
        return FALSE; /* index past the end of the grid */
    }
    cw = (float)al_get_bitmap_width(atlas->pages[page].bmp) / (float)atlas->pages[page].cols;
    ch = (float)al_get_bitmap_height(atlas->pages[page].bmp) / (float)atlas->pages[page].rows;

    if (sx) {
        (*sx) = (float)col * cw;
    }
    if (sy) {
        (*sy) = (float)row * ch;
    }
    if (sw) {
        (*sw) = cw;
    }
    if (sh) {
        (*sh) = ch;
    }
    return TRUE;
}

int n_atlas_draw_cell(N_ATLAS* atlas, size_t page, size_t index, float x, float y, float size) {
    float sx = 0.0, sy = 0.0, sw = 0.0, sh = 0.0;

    __n_assert(size > 0.0, return FALSE);
    if (!n_atlas_cell_rect(atlas, page, index, &sx, &sy, &sw, &sh)) {
        return FALSE;
    }
    al_draw_scaled_bitmap(atlas->pages[page].bmp, sx, sy, sw, sh, x, y, size, size, 0);
    return TRUE;
}