/*
 * src/image.c
 * https://gitlab.com/bztsrc/assetpack
 *
 * Copyright (C) 2024 bzt, MIT license
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL ANY
 * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * @brief Handle image formats and generate sprite atlas
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <apck.h>
#include "image.h"

#define BMP_IMPLEMENTATION
#include "bmp.h"
#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_STDIO
#define STBI_NO_LINEAR
#define STBI_NO_HDR
#define STBI_NO_BMP
#define STBI_NO_JPEG
#include "../lib/stb_image.h"
uint8_t* WebPDecodeRGBA(const uint8_t* data, size_t data_size, int* width, int* height);
#include "libjpeg/jpeglib.h"

static jmp_buf jpeg_jmpbuf;
static void jpeg_silent(j_common_ptr cinfo) {
    jpeg_destroy(cinfo);
    longjmp(jpeg_jmpbuf, 1);
}

/**
 * Decode an image asset into a 32-bit RGBA pixel buffer
 */
uint32_t *image_decode(uint8_t *buf, uint64_t size, int *w, int *h)
{
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    volatile JSAMPROW row = 0, currow;
    JSAMPROW rowptr[1];
    JDIMENSION i, j;
    stbi__context s = { 0 };
    stbi__result_info ri = { 0 };
    uint32_t *volatile ret = NULL;
    uint32_t *pix;
    int p;

    if(!buf || size < 16 || !w || !h) return NULL;
    *w = *h = 0;

    if(!memcmp(buf, "BM", 2)) {
        /* stb_image can't handle compressed BMP */
        ret = bmp_load(buf, size, w, h);
    } else
    if(!memcmp(buf, "RIFF", 4) && !memcmp(buf + 8, "WEBP", 4)) {
        /* stb_image does not support WEBP */
        ret = (uint32_t*)WebPDecodeRGBA((const uint8_t*)buf, (size_t)size, w, h);
    } else
    if(buf[0] == 0xff && buf[1] == 0xd8 && !memcmp(buf + 6, "JFIF", 4)) {
        /* JPEG in stb_image is buggy as hell, unable to read my test files */
        memset(&cinfo, 0, sizeof(cinfo));
        memset(&jerr, 0, sizeof(jerr));
        cinfo.err=jpeg_std_error(&jerr);
        cinfo.err->output_message = jpeg_silent;
        cinfo.err->error_exit = jpeg_silent;
        if(setjmp(jpeg_jmpbuf) != 0) {
jpegerr:    jpeg_destroy_decompress(&cinfo);
            if(row) free(row);
            if(ret) free((uint32_t *)ret);
            *w = *h = 0;
            return NULL;
        }
        jpeg_create_decompress(&cinfo);
        jpeg_mem_src(&cinfo, buf, size);
        if(jpeg_read_header(&cinfo, 1) != JPEG_HEADER_OK) goto jpegerr;
        cinfo.out_color_space = JCS_RGB;
        jpeg_start_decompress(&cinfo);
        *w = cinfo.output_width;
        *h = cinfo.output_height;
        if(!(ret = (uint32_t*)malloc((*w) * (*h) * 4)) ||
           !(row = malloc(cinfo.output_width * cinfo.output_components * sizeof(JSAMPLE)))) goto jpegerr;
        rowptr[0] = row;
        pix = (uint32_t *)ret;
        for(i = 0; i < cinfo.output_height; i++) {
            currow = row;
            if(jpeg_read_scanlines(&cinfo, rowptr, 1) != 1) goto jpegerr;
            for(j = 0; j < cinfo.output_width; j++, currow += cinfo.output_components, pix++)
                if(cinfo.jpeg_color_space == JCS_GRAYSCALE)
                    *pix = ((*currow & 0xff) << 16) | ((*currow & 0xff) << 8) | (*currow & 0xff) | 0xff000000;
                else
                    *pix = (*currow) | 0xff000000;
        }
        free(row);
        jpeg_destroy_decompress(&cinfo);
    } else {
        /* for the rest (PNG, GIF, TGA etc.), try stb_image */
        s.read_from_callbacks = 0;
        s.img_buffer = s.img_buffer_original = buf;
        s.img_buffer_end = s.img_buffer_original_end = buf + size;
        ri.bits_per_channel = 8;
        ret = (uint32_t*)stbi__load_main(&s, w, h, &p, 4, &ri, 8);
        /* silence two stupid warnings in stb_image */
        (void)stbi__addints_valid(0, 0);
        (void)stbi__mul2shorts_valid(0, 0);
    }
    if(!ret) *w = *h = 0;
    return (uint32_t *)ret;
}

image_t *images = NULL;
int numimages = 0;
atlas_t atlas = { 0 };
char *dirext[] = { "_s", "_sw", "_w", "_nw", "_n", "_ne", "_e", "_se" }, *loopext[] = { "_loop", "_once", "_bf", "_wang", "_wall" };

/**
 * Add frames from a pixel buffer
 */
int image_add(char *name, uint32_t *pix, int w, int h)
{
    atlas_spr_t spr;
    uint32_t *data;
    int i, j, k, l, m, n, o, p, q, s, x, y, W, H, dir = 0, loop = 0, cols, rows;

    /* chop off suffixes */
    l = strlen(name);
    do {
        k = 0;
        for(i = 0; i < 8; i++) {
            j = strlen(dirext[i]);
            if(l > j && !strcmp(name + l - j, dirext[i])) { k = 1; l -= j; name[l] = 0; dir = i; break; }
        }
        for(i = 0; i < 5; i++) {
            j = strlen(loopext[i]);
            if(l > j && !strcmp(name + l - j, loopext[i])) { k = 1; l -= j; name[l] = 0; loop = i; break; }
        }
    } while(k);
    if(loop >= 3) dir = 0;

    /* look up sprite and add if not exists */
    for(i = 0; i < numimages && strcmp(images[i].name, name); i++);
    if(i >= numimages) {
        i = numimages++;
        if(!(images = (image_t*)realloc(images, numimages * sizeof(image_t)))) {
            numimages = 0;
            return APCK_ERR_NOMEM;
        }
        memset(&images[i], 0, sizeof(image_t));
        strncpy(images[i].name, name, FILENAME_MAX);
    }

    /* set values */
    if(images[i].buf[dir].pix) free(images[i].buf[dir].pix);
    images[i].buf[dir].pix = pix;
    images[i].buf[dir].w = w;
    images[i].buf[dir].h = h;
    images[i].sprite.type = loop;

    cols = rows = 1; W = w; H = h;
    if(loop < 3) {
        /* detect columns, rows and sprite size */
        for(o = 25, n = w / 16; n <= w / 2; n++) {
            p = w / n;
            if(p > 1 && p * n == w) {
                s = ((p - 1) * h);
                for(k = n, m = 0; k < w; k += n)
                    for(l = 0; l < h; l++)
                        if(!(pix[l * w + k] & 0xfc000000)) m++;
                m = m * 50 / s;
                if(m - 1 > o) { o = m; cols = p; W = n; }
            }
        }
        q = 2 * W / 3;
        for(o = 25, n = h / 16; n <= h / 2; n++) {
            p = h / n;
            if(p > 1 && p * n == h) {
                s = ((p - 1) * w);
                for(k = n, m = 0; k < h; k += n)
                    for(l = 0; l < w; l++)
                        if(!(pix[k * w + l] & 0xfc000000)) m++;
                m = m * 50 / s;
                if(m - 1 > o && (n >= q || m >= 49)) { o = m; rows = p; H = n; }
            }
        }
    } else {
        /* try to detect number of animation frames on wang tiles. We only support one autotile per image */
        if(loop == 3) {
            /* for RPG Maker, they are 3 tiles high, and either 2 tiles (1 frame) or 6 tiles (3 frames) wide */
            if(!(H % 3) && (W == (H / 3) * 2 || W == (H / 3) * 6)) { W = 2 * H / 3; cols = w / W; } else
            /* for TNG autotiles, they are 5 tiles high and 3 tiles wide, any number of frames */
            if(!(H % 5) && W >= (H / 5) * 3 && !(W % ((H / 5) * 3))) { W = 3 * H / 5; cols = w / W; } else
            /* for LPC autotiles, they are 6 tiles high and 3 tiles wide, any number of frames */
            if(!(H % 6) && W >= (H / 6) * 3 && !(W % ((H / 6) * 3))) { W = 3 * H / 6; cols = w / W; }
        }
        /* for walls, we just use the entire image, no animation */
    }
    if(images[i].sprite.w < W) images[i].sprite.w = W;
    if(images[i].sprite.h < H) images[i].sprite.h = H;

    /* allocate memory */
    n = cols * rows;
    images[i].sprite.nf[dir] = n;
    if(!(images[i].sprite.frames[dir] = (apck_frame_t*)realloc(images[i].sprite.frames[dir], n * sizeof(apck_frame_t)))) {
        images[i].sprite.nf[dir] = 0;
        images[i].buf[dir].pix = NULL;
        free(pix);
        return APCK_ERR_NOMEM;
    }
    memset(images[i].sprite.frames[dir], 0, n * sizeof(apck_frame_t));
    if(!(atlas.spr = (atlas_spr_t*)realloc(atlas.spr, (atlas.numspr + n) * sizeof(atlas_spr_t)))) {
        atlas.numspr = 0;
        images[i].buf[dir].pix = NULL;
        free(pix);
        return APCK_ERR_NOMEM;
    }
    memset(atlas.spr + atlas.numspr, 0, n * sizeof(atlas_spr_t));

    /* add frames */
    for(j = k = 0; j < n; j++) {
        memset(&spr, 0, sizeof(spr));
        spr.idx = i;
        spr.dir = dir;
        spr.frm = k;
        spr.w = W;
        spr.h = H;
        spr.p = w;
        data = pix + (j / cols) * H * w + (j % cols) * W;
        /* crop sprite to content */
        for(y = 0; y < H && spr.h > 0; y++) {
            for(x = 0; x < W && !(data[y * spr.p + x] & 0xfc000000); x++);
            if(x < W) break;
            spr.t++; spr.h--;
        }
        for(y = H - 1; y >= spr.t && spr.h > 0; y--) {
            for(x = 0; x < W && !(data[y * spr.p + x] & 0xfc000000); x++);
            if(x < W) break;
            spr.h--;
        }
        for(x = 0; x < W && spr.h > 0 && spr.w > 0; x++) {
            for(y = 0; y < spr.h && !(data[(y + spr.t) * spr.p + x] & 0xfc000000); y++);
            if(y < spr.h) break;
            spr.l++; spr.w--;
        }
        for(x = W - 1; x >= spr.l && spr.h > 0 && spr.w > 0; x--) {
            for(y = 0; y < spr.h && !(data[(y + spr.t) * spr.p + x] & 0xfc000000); y++);
            if(y < spr.h) break;
            spr.w--;
        }
        if(spr.w > 0 && spr.h > 0) {
            spr.data = data + spr.p * spr.t + spr.l;
            if(spr.w > atlas.mw) atlas.mw = spr.w;
            if(spr.h > atlas.mh) atlas.mw = spr.h;
            atlas.sp += spr.w * spr.h;
            memcpy(atlas.spr + atlas.numspr, &spr, sizeof(atlas_spr_t));
            atlas.numspr++;
            k++;
        }
    }
    return APCK_OK;
}

/**
 * Helper to sort atlas sprites
 */
int _atlas_sizecmp(const void *a, const void *b)
{
    const atlas_spr_t *A = (const atlas_spr_t*)a;
    const atlas_spr_t *B = (const atlas_spr_t*)b;
    if(A->h != B->h) return B->h - A->h;
    return B->w - A->w;
}

/**
 * Build atlas and mappings
 */
int image_build_atlas(int maxsize, int doop)
{
    apck_frame_t *frame;
    uint32_t *dst, *src;
    int i, j, a, b, c, m;

    if(!atlas.spr || !atlas.numspr) return APCK_OK;

    qsort(atlas.spr, atlas.numspr, sizeof(atlas_spr_t), _atlas_sizecmp);

    /* determine the optimal atlas size */
    if(maxsize > 16384) maxsize = 16384;
    for(atlas.size = 256; atlas.size < atlas.mw || atlas.size < atlas.mh || atlas.size * atlas.size < atlas.sp; atlas.size += 256);
    if(atlas.size > maxsize) {
        atlas.size = maxsize;
        if(atlas.size < atlas.mw) atlas.size = atlas.mw;
        if(atlas.size < atlas.mh) atlas.size = atlas.mh;
    }

    for(i = 0; i < atlas.numspr; i++)
        if(atlas.spr[i].data && atlas.spr[i].h > 0) {
            /* deduplicate */
            for(m = 0; m < i; m++) {
                if(atlas.spr[m].o != APCK_M_COPY) continue;
                /* if the sprites match, simply use the previous sprite's atlas mapping */
                if(atlas.spr[m].w >= atlas.spr[i].w && atlas.spr[m].h >= atlas.spr[i].h) {
                    b = atlas.spr[m].w - atlas.spr[i].w;
                    c = atlas.spr[m].h - atlas.spr[i].h;
                    /* match on top left (or entire sprite match) */
                    for(a = 0; a < atlas.spr[i].h && !memcmp(atlas.spr[m].data + a * atlas.spr[m].p,
                        atlas.spr[i].data + a * atlas.spr[i].p, atlas.spr[i].w * sizeof(uint32_t)); a++);
                    if(a == atlas.spr[i].h) {
                        atlas.spr[i].o = APCK_M_COPY;
                        atlas.spr[i].a = atlas.spr[m].a;
                        atlas.spr[i].x = atlas.spr[m].x;
                        atlas.spr[i].y = atlas.spr[m].y;
                        break;
                    }
                    /* match on top right */
                    if(b > 0) {
                        for(a = 0; a < atlas.spr[i].h && !memcmp(atlas.spr[m].data + b + a * atlas.spr[m].p,
                            atlas.spr[i].data + a * atlas.spr[i].p, atlas.spr[i].w * sizeof(uint32_t)); a++);
                        if(a == atlas.spr[i].h) {
                            atlas.spr[i].o = APCK_M_COPY;
                            atlas.spr[i].a = atlas.spr[m].a;
                            atlas.spr[i].x = atlas.spr[m].x + b;
                            atlas.spr[i].y = atlas.spr[m].y;
                            break;
                        }
                    }
                    /* match on bottom left */
                    if(c > 0) {
                        for(a = 0; a < atlas.spr[i].h && !memcmp(atlas.spr[m].data + (a + c) * atlas.spr[m].p,
                            atlas.spr[i].data + a * atlas.spr[i].p, atlas.spr[i].w * sizeof(uint32_t)); a++);
                        if(a == atlas.spr[i].h) {
                            atlas.spr[i].o = APCK_M_COPY;
                            atlas.spr[i].a = atlas.spr[m].a;
                            atlas.spr[i].x = atlas.spr[m].x;
                            atlas.spr[i].y = atlas.spr[m].y + c;
                            break;
                        }
                    }
                    /* match on bottom right */
                    if(b > 0 && c > 0) {
                        for(a = 0; a < atlas.spr[i].h && !memcmp(atlas.spr[m].data + b + (a + c) * atlas.spr[m].p,
                            atlas.spr[i].data + a * atlas.spr[i].p, atlas.spr[i].w * sizeof(uint32_t)); a++);
                        if(a == atlas.spr[i].h) {
                            atlas.spr[i].o = APCK_M_COPY;
                            atlas.spr[i].a = atlas.spr[m].a;
                            atlas.spr[i].x = atlas.spr[m].x + b;
                            atlas.spr[i].y = atlas.spr[m].y + c;
                            break;
                        }
                    }
                }
                if(doop) {
                    if(atlas.spr[m].w == atlas.spr[i].w && atlas.spr[m].h == atlas.spr[i].h) {
                        /* check if it's vertically flipped version of another sprite */
                        if(atlas.spr[i].h > 1) {
                            for(a = 0; a < atlas.spr[i].w; a++) {
                                for(b = 0; b < atlas.spr[i].h && atlas.spr[m].data[b * atlas.spr[m].p + a] ==
                                  atlas.spr[i].data[(atlas.spr[i].h - 1 - b) * atlas.spr[i].p + a]; b++);
                                if(b < atlas.spr[i].h) break;
                            }
                            if(a == atlas.spr[i].w) {
                                atlas.spr[i].o = APCK_M_FLIPV;
                                atlas.spr[i].a = atlas.spr[m].a;
                                atlas.spr[i].x = atlas.spr[m].x;
                                atlas.spr[i].y = atlas.spr[m].y;
                                break;
                            }
                        }
                        /* check if it's horizontally flipped version of another sprite */
                        if(atlas.spr[i].w > 1) {
                            for(a = 0; a < atlas.spr[i].h; a++) {
                                for(b = 0; b < atlas.spr[i].w && atlas.spr[m].data[a * atlas.spr[m].p + b] ==
                                  atlas.spr[i].data[a * atlas.spr[i].p + atlas.spr[i].w - 1 - b]; b++);
                                if(b < atlas.spr[i].w) break;
                            }
                            if(a == atlas.spr[i].h) {
                                atlas.spr[i].o = APCK_M_FLIPH;
                                atlas.spr[i].a = atlas.spr[m].a;
                                atlas.spr[i].x = atlas.spr[m].x;
                                atlas.spr[i].y = atlas.spr[m].y;
                                break;
                            }
                        }
                        /* check if it's a 180 degrees rotated version of another sprite */
                        for(b = 0; b < atlas.spr[i].h; b++) {
                            for(a = 0; a < atlas.spr[i].w &&
                              atlas.spr[m].data[(atlas.spr[m].h - 1 - b) * atlas.spr[m].p + atlas.spr[m].w - 1 - a] ==
                              atlas.spr[i].data[b * atlas.spr[i].p + a]; a++);
                            if(a < atlas.spr[i].w) break;
                        }
                        if(b == atlas.spr[i].h) {
                            atlas.spr[i].o = APCK_M_ROT180;
                            atlas.spr[i].a = atlas.spr[m].a;
                            atlas.spr[i].x = atlas.spr[m].x;
                            atlas.spr[i].y = atlas.spr[m].y;
                            break;
                        }
                    }
                    if(atlas.spr[m].h == atlas.spr[i].w && atlas.spr[m].w == atlas.spr[i].h) {
                        /* check if it's clockwise rotated version of another sprite */
                        for(a = 0; a < atlas.spr[i].h; a++) {
                            for(b = 0; b < atlas.spr[i].w &&
                              atlas.spr[m].data[(atlas.spr[m].h - 1 - b) * atlas.spr[m].p + a] ==
                              atlas.spr[i].data[a * atlas.spr[i].p + b]; b++);
                            if(b < atlas.spr[i].w) break;
                        }
                        if(a == atlas.spr[i].h) {
                            atlas.spr[i].o = APCK_M_ROT90;
                            atlas.spr[i].a = atlas.spr[m].a;
                            atlas.spr[i].x = atlas.spr[m].x;
                            atlas.spr[i].y = atlas.spr[m].y;
                            atlas.spr[i].w = atlas.spr[m].w;
                            atlas.spr[i].h = atlas.spr[m].h;
                            break;
                        }
                        /* check if it's counter clockwise rotated version of another sprite */
                        for(a = 0; a < atlas.spr[i].h; a++) {
                            for(b = 0; b < atlas.spr[i].w &&
                              atlas.spr[m].data[b * atlas.spr[m].p + atlas.spr[m].w - 1 - a] ==
                              atlas.spr[i].data[a * atlas.spr[i].p + b]; b++);
                            if(b < atlas.spr[i].w) break;
                        }
                        if(a == atlas.spr[i].h) {
                            atlas.spr[i].o = APCK_M_ROT270;
                            atlas.spr[i].a = atlas.spr[m].a;
                            atlas.spr[i].x = atlas.spr[m].x;
                            atlas.spr[i].y = atlas.spr[m].y;
                            atlas.spr[i].w = atlas.spr[m].w;
                            atlas.spr[i].h = atlas.spr[m].h;
                            break;
                        }
                    }
                }
            }
            if(m >= i) {
                /* find best fit */
                for(m = -1, a = atlas.size * atlas.size + 1, j = 0; atlas.slots && j < atlas.numslot; j++)
                    if(atlas.slots[j].w >= atlas.spr[i].w && atlas.slots[j].h >= atlas.spr[i].h) {
                        b = (atlas.slots[j].w - atlas.spr[i].w) * atlas.spr[i].h +
                            (atlas.slots[j].h - atlas.spr[i].h) * atlas.slots[j].w;
                        if(b < a) { a = b; m = j; }
                    }
                if(m == -1) {
                    /* if there was no slot to fit the sprite, add a new atlas image */
                    if(!(atlas.imgs = (uint32_t**)realloc(atlas.imgs, (atlas.numimg + 1) * sizeof(uint32_t*)))) {
                        atlas.numimg = 0;
                        return APCK_ERR_NOMEM;
                    }
                    if(!(atlas.imgs[atlas.numimg] = (uint32_t*)malloc(atlas.size * atlas.size * sizeof(uint32_t))) ||
                       !(atlas.slots = (atlas_slot_t*)realloc(atlas.slots, (atlas.numslot + 2) * sizeof(atlas_slot_t)))) {
                        atlas.numslot = 0;
                        return APCK_ERR_NOMEM;
                    }
                    memset(atlas.imgs[atlas.numimg], 0, atlas.size * atlas.size * sizeof(uint32_t));
                    atlas.slots[atlas.numslot].a = atlas.slots[atlas.numslot + 1].a = atlas.spr[i].a = atlas.numimg;
                    atlas.slots[atlas.numslot].x = atlas.spr[i].w;
                    atlas.slots[atlas.numslot].y = atlas.slots[atlas.numslot + 1].x = 0;
                    atlas.slots[atlas.numslot].w = atlas.size - atlas.spr[i].w;
                    atlas.slots[atlas.numslot].h = atlas.slots[atlas.numslot + 1].y = atlas.spr[i].h;
                    atlas.slots[atlas.numslot + 1].w = atlas.size;
                    atlas.slots[atlas.numslot + 1].h = atlas.size - atlas.spr[i].h;
                    atlas.numslot += 2;
                    atlas.numimg++;
                    if(atlas.numimg > 0xff) return APCK_ERR_NOMEM;
                } else {
                    /* we have found a slot, split it
                     *
                     * mmmmm    ssmmm     (m = slot found)
                     * mmmmm -> ssmmm     (s = sprite to insert)
                     * mmmmm    nnnnn     (n = new slot)
                     */
                    if(!(atlas.slots = (atlas_slot_t*)realloc(atlas.slots, (atlas.numslot + 1) * sizeof(atlas_slot_t)))) {
                        atlas.numslot = 0;
                        return APCK_ERR_NOMEM;
                    }
                    atlas.spr[i].a = atlas.slots[atlas.numslot].a = atlas.slots[m].a;
                    atlas.spr[i].x = atlas.slots[atlas.numslot].x = atlas.slots[m].x;
                    atlas.spr[i].y = atlas.slots[atlas.numslot].y = atlas.slots[m].y;
                    atlas.slots[atlas.numslot].w = atlas.slots[m].w;
                    atlas.slots[atlas.numslot].y += atlas.spr[i].h;
                    atlas.slots[atlas.numslot].h = atlas.slots[m].h - atlas.spr[i].h;
                    atlas.slots[m].x += atlas.spr[i].w;
                    atlas.slots[m].w -= atlas.spr[i].w;
                    atlas.slots[m].h = atlas.spr[i].h;
                    atlas.numslot++;
                }
                /* copy the sprite to the image */
                src = atlas.spr[i].data;
                dst = atlas.imgs[atlas.spr[i].a] + atlas.size * atlas.spr[i].y + atlas.spr[i].x;
                for(j = 0; j < atlas.spr[i].h; j++, dst += atlas.size, src += atlas.spr[i].p)
                    memcpy(dst, src, atlas.spr[i].w * sizeof(uint32_t));
            }
        }

    /* copy new data to apck_sprites */
    for(i = 0; i < atlas.numspr; i++) {
        frame = &images[atlas.spr[i].idx].sprite.frames[atlas.spr[i].dir][atlas.spr[i].frm];
        frame->atlas = atlas.spr[i].a;
        frame->m = atlas.spr[i].o;
        frame->w = atlas.spr[i].w;
        frame->h = atlas.spr[i].h;
        frame->sx = atlas.spr[i].x;
        frame->sy = atlas.spr[i].y;
        frame->dx = atlas.spr[i].l;
        frame->dy = atlas.spr[i].t;
        frame->dur = 10;
    }

    return APCK_OK;
}
