// atlasc.c // Copyright 2019 Sepehr Taghdisian (septag@github). All rights reserved. // License: https://github.com/septag/atlasc#license-bsd-2-clause // // sx_math.h // Copyright 2018 Sepehr Taghdisian (septag@github). All rights reserved. // License: https://github.com/septag/sx#license-bsd-2-clause #ifndef ATLASC_HEADER #define ATLASC_HEADER #define ATLASC_VERSION "1.2.3" #include #include #include #ifndef __cplusplus #define ATLAS_CAST #else #define ATLAS_CAST(T) T extern "C" { #endif typedef union vec2 { struct { float x, y; }; float f[2]; } vec2; typedef union vec3 { struct { float x, y, z; }; float f[3]; } vec3; typedef union vec2i { struct { int x, y; }; int n[2]; } vec2i; typedef union recti { struct { int xmin, ymin, xmax, ymax; }; struct { vec2i vmin, vmax; }; int f[4]; } recti; typedef struct atlas_flags { int alpha_threshold; float dist_threshold; int max_width; int max_height; int border; int pot; int padding; int mesh; int max_verts_per_mesh; float scale; } atlas_flags; typedef struct atlas_image { uint8_t* pixels; // only supports 32bpp RGBA format int width; int height; char *name; } atlas_image; typedef struct atlas_sprite { uint8_t* src_image; // RGBA image buffer (32bpp) vec2i src_size; // widthxheight recti sprite_rect; // cropped rectangle relative to sprite's source image (pixels) recti sheet_rect; // rectangle in final sheet (pixels) char *name; unsigned frame; // sprite-mesh data (if flag is set. see atlas_flags) uint16_t num_tris; int num_points; vec2i* pts; vec2i* uvs; uint16_t* tris; } atlas_sprite; typedef struct atlas_t { atlas_sprite* sprites; int num_sprites; int* frames; int num_frames; atlas_image output; } atlas_t; // receives input files and common arguments. returns atlas_t // you have to free the data after use with `atlas_free` atlas_t* atlas_loadfiles(array(char*) files, atlas_flags flags); // receives input image buffers and common arguments. returns atlas_t // you have to free the data after use with `atlas_free` atlas_t* atlas_loadimages(array(atlas_image) images, atlas_flags flags); // bool atlas_save(const char *outfile, const atlas_t* atlas, atlas_flags flags); // frees atlas_t memory void atlas_free(atlas_t* atlas); // returns the last error string const char* atlas_last_error(); #ifdef __cplusplus } #endif #endif // ATLASC_HEADER // #ifdef ATLASC_IMPLEMENTATION #include #include //////////////////////////////////////////////////////////////////////////////////////////////////// // Types/Primitives #define vec2(x,y) (ATLAS_CAST(vec2) { (float)(x), (float)(y) }) #define vec3(x,y,z) (ATLAS_CAST(vec3) { (float)(x), (float)(y), (float)(z) }) #define vec2i(x,y) (ATLAS_CAST(vec2i) { (int)(x), (int)(y) }) #define recti(x,y,X,Y) (ATLAS_CAST(recti) { (int)(x), (int)(y), (int)(X), (int)(Y) }) #define minf(a,b) ((a) < (b) ? (a) : (b)) #define maxf(a,b) ((a) > (b) ? (a) : (b)) #define clampf(a,b,c) ( (a) < (b) ? (b) : (a) > (c) ? (c) : (a)) static int nearest_pow2(int n) { return --n, n |= n >> 1, n |= n >> 2, n |= n >> 4, n |= n >> 8, n |= n >> 16, ++n; } // https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 static float sx_abs(float _a) { union { float f; unsigned int ui; } u = { _a }; return u.ui &= 0x7FFFFFFF, u.f; } static bool equalf(float _a, float _b, float _epsilon) { const float lhs = sx_abs(_a - _b), aa = sx_abs(_a), ab = sx_abs(_b), rhs = _epsilon * maxf(1.0f, maxf(aa, ab)); return lhs <= rhs; } // http://realtimecollisiondetection.net/blog/?t=89 static vec3 cross3(const vec3 _a, const vec3 _b) { return vec3(_a.y * _b.z - _a.z * _b.y, _a.z * _b.x - _a.x * _b.z, _a.x * _b.y - _a.y * _b.x); } static float dot2(const vec2 _a, const vec2 _b) { return _a.x * _b.x + _a.y * _b.y; } static float len2(const vec2 _a) { return sqrt(dot2(_a, _a)); } static vec2 norm2(const vec2 _a) { const float len = len2(_a); /*assert(len > 0 && "Divide by zero");*/ return vec2(_a.x / (len + !len), _a.y / (len + !len)); } static vec2 add2(const vec2 _a, const vec2 _b) { return vec2(_a.x + _b.x, _a.y + _b.y); } static vec2 sub2(const vec2 _a, const vec2 _b) { return vec2(_a.x - _b.x, _a.y - _b.y); } static vec2 scale2(const vec2 _a, float _b) { return vec2(_a.x * _b, _a.y * _b); } static vec2i add2i(const vec2i _a, const vec2i _b) { return vec2i(_a.x + _b.x, _a.y + _b.y); } static vec2i sub2i(const vec2i _a, const vec2i _b) { return vec2i(_a.x - _b.x, _a.y - _b.y); } static vec2i min2i(const vec2i _a, const vec2i _b) { return vec2i(minf(_a.x, _b.x), minf(_a.y, _b.y)); } static vec2i max2i(const vec2i _a, const vec2i _b) { return vec2i(maxf(_a.x, _b.x), maxf(_a.y, _b.y)); } static recti rectiwh(int _x, int _y, int _w, int _h) { return recti(_x, _y, _x + _w, _y + _h); } static recti recti_expand(const recti rc, const vec2i expand) { return recti(rc.xmin - expand.x, rc.ymin - expand.y, rc.xmax + expand.x, rc.ymax + expand.y); } static void recti_add_point(recti* rc, const vec2i pt) { rc->vmin = min2i(rc->vmin, pt); rc->vmax = max2i(rc->vmax, pt); } // ---------------------------------------------------------------------------- #ifndef ATLAS_REALLOC #define ATLAS_REALLOC realloc #endif #ifndef ATLAS_MSIZE #define ATLAS_MSIZE _msize #endif #ifndef ATLAS_CALLOC #define ATLAS_CALLOC(n,m) memset(ATLAS_REALLOC(0, (n)*(m)), 0, (n)*(m)) #endif #ifndef ATLAS_FREE #define ATLAS_FREE(ptr) ((ptr) = ATLAS_REALLOC((ptr), 0)) #endif #define align_mask(_value, _mask) (((_value) + (_mask)) & ((~0) & (~(_mask)))) static void panic_if(int fail) { if(fail) exit(-fprintf(stderr, "out of memory!\n")); } static void path_unixpath(char *buf, unsigned buflen, const char *inpath) { snprintf(buf, buflen, "%s", inpath); while( strchr(buf, '\\') ) *strchr(buf, '\\') = '/'; } static void path_basename(char *buf, unsigned buflen, const char *inpath) { const char *a = strrchr(inpath, '\\'); const char *b = strrchr(inpath, '/'); snprintf(buf, buflen, "%s", a > b ? a+1 : b > a ? b+1 : inpath ); } static bool path_isfile(const char* filepath) { FILE *f = fopen(filepath, "rb"); return f ? fclose(f), 1 : 0; } static char g_error_str[512]; const char* atlas_last_error() { return g_error_str; } static void atlas__free_sprites(atlas_sprite* sprites, int num_sprites) { for (int i = 0; i < num_sprites; i++) { if (sprites[i].src_image) { stbi_image_free(sprites[i].src_image); } if (sprites[i].tris) { ATLAS_FREE(sprites[i].tris); } if (sprites[i].pts) { ATLAS_FREE(sprites[i].pts); } if (sprites[i].uvs) { ATLAS_FREE(sprites[i].uvs); } if (sprites[i].name) { ATLAS_FREE(sprites[i].name); } } ATLAS_FREE(sprites); } static void atlas__blit(uint8_t* dst, int dst_x, int dst_y, int dst_pitch, const uint8_t* src, int src_x, int src_y, int src_w, int src_h, int src_pitch, int bpp) { assert(dst); assert(src); const int pixel_sz = bpp / 8; const uint8_t* src_ptr = src + src_y * src_pitch + src_x * pixel_sz; uint8_t* dst_ptr = dst + dst_y * dst_pitch + dst_x * pixel_sz; for (int y = src_y; y < (src_y + src_h); y++) { memcpy(dst_ptr, src_ptr, src_w * pixel_sz); src_ptr += src_pitch; dst_ptr += dst_pitch; } } static vec2 atlas__itof2(const s2o_point p) { return vec2((float)p.x, (float)p.y); } // modified version of: // https://github.com/anael-seghezzi/Maratis-Tiny-C-library/blob/master/include/m_raster.h static bool atlas__test_line(const uint8_t* buffer, int w, int h, s2o_point p0, s2o_point p1) { const uint8_t* data = buffer; int x0 = p0.x; int y0 = p0.y; int x1 = p1.x; int y1 = p1.y; int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; int err = dx + dy, e2; while (1) { if (x0 > -1 && y0 > -1 && x0 < w && y0 < h) { const uint8_t* pixel = data + (y0 * w + x0); if (*pixel) return true; // line intersects with image data } if (x0 == x1 && y0 == y1) break; e2 = 2 * err; if (e2 >= dy) { err += dy; x0 += sx; } if (e2 <= dx) { err += dx; y0 += sy; } } return false; } // returns true if 'pts' buffer is changed static bool atlas__offset_pt(s2o_point* pts, int num_pts, int pt_idx, float amount, int w, int h) { s2o_point ipt = pts[pt_idx]; s2o_point _ipt = ipt; vec2 pt = atlas__itof2(ipt); vec2 prev_pt = (pt_idx > 0) ? atlas__itof2(pts[pt_idx - 1]) : atlas__itof2(pts[num_pts - 1]); vec2 next_pt = (pt_idx + 1) < num_pts ? atlas__itof2(pts[pt_idx + 1]) : atlas__itof2(pts[0]); vec2 edge1 = norm2(sub2(prev_pt, pt)); vec2 edge2 = norm2(sub2(next_pt, pt)); // calculate normal vector to move the point away from the polygon vec2 n; vec3 c = cross3(vec3(edge1.x, edge1.y, 0), vec3(edge2.x, edge2.y, 0)); if (equalf(c.z, 0.0f, 0.00001f)) { n = scale2(vec2(-edge1.y, edge1.x), amount); } else { // c.z < 0 -> point intersecting convex edges // c.z > 0 -> point intersecting concave edges float k = c.z < 0.0f ? -1.0f : 1.0f; n = scale2(norm2(add2(edge1, edge2)), k * amount); } pt = add2(pt, n); ipt.x = (int)pt.x; ipt.y = (int)pt.y; ipt.x = clampf(ipt.x, 0, w); ipt.y = clampf(ipt.y, 0, h); pts[pt_idx] = ipt; return (_ipt.x != ipt.x) || (_ipt.y != ipt.y); } static void atlas__fix_outline_pts(const uint8_t* thresholded, int tw, int th, s2o_point* pts, int num_pts) { // NOTE: winding is assumed to be CW const float offset_amount = 2.0f; for (int i = 0; i < num_pts; i++) { s2o_point pt = pts[i]; int next_i = (i + 1) < num_pts ? (i + 1) : 0; // assert(!thresholded[pt.y * tw + pt.x]); // point shouldn't be inside threshold s2o_point next_pt = pts[next_i]; while (atlas__test_line(thresholded, tw, th, pt, next_pt)) { if (!atlas__offset_pt(pts, num_pts, i, offset_amount, tw, th)) break; atlas__offset_pt(pts, num_pts, next_i, offset_amount, tw, th); // refresh points for the new line intersection test pt = pts[i]; next_pt = pts[next_i]; } } } static void atlas__make_mesh(atlas_sprite* spr, const s2o_point* pts, int pt_count, int max_verts, const uint8_t* thresholded, int width, int height) { s2o_point* temp_pts = ATLAS_CALLOC(pt_count,sizeof(s2o_point)); panic_if(!temp_pts); memcpy(temp_pts, pts, sizeof(s2o_point)*pt_count); int num_verts = pt_count; if (width > 1 && height > 1) { const float delta = 0.5f; const float threshold_start = 0.5f; float threshold = threshold_start; for(;;) { s2o_distance_based_path_simplification(temp_pts, &num_verts, threshold); if(num_verts <= max_verts) break; memcpy(temp_pts, pts, sizeof(s2o_point)*pt_count); num_verts = pt_count; threshold += delta; } // fix any collisions with the actual image // @r-lyeh: method below is buggy. will return dupe points atlas__fix_outline_pts(thresholded, width, height, temp_pts, num_verts); } //< @r-lyeh: remove dupes for (int i = 0; i < num_verts - 1; i++) { for (int j = i + 1; j < num_verts; j++) { if( temp_pts[i].x == temp_pts[j].x && temp_pts[i].y == temp_pts[j].y ) { temp_pts[j].x = temp_pts[num_verts - 1].x; temp_pts[j].y = temp_pts[num_verts - 1].y; --num_verts; --j; } } } //< // triangulate del_point2d_t* dpts = ATLAS_CALLOC(num_verts, sizeof(del_point2d_t)); panic_if(!dpts); for (int i = 0; i < num_verts; i++) { dpts[i].x = (double)temp_pts[i].x; dpts[i].y = (double)temp_pts[i].y; //printf("%d) %f,%f\n", i, dpts[i].x, dpts[i].y); //< @r-lyeh: debug dupe points } delaunay2d_t* polys = delaunay2d_from(dpts, num_verts); assert(polys); tri_delaunay2d_t* tris = tri_delaunay2d_from(polys); assert(tris); ATLAS_FREE(dpts); delaunay2d_release(polys); assert(tris->num_triangles < UINT16_MAX); spr->tris = ATLAS_CALLOC(tris->num_triangles * 3,sizeof(uint16_t)); spr->pts = ATLAS_CALLOC(tris->num_points, sizeof(vec2i)); assert(spr->tris); assert(spr->pts); for (unsigned int i = 0; i < tris->num_triangles; i++) { unsigned int index = i * 3; spr->tris[index] = (uint16_t)tris->tris[index]; spr->tris[index + 1] = (uint16_t)tris->tris[index + 1]; spr->tris[index + 2] = (uint16_t)tris->tris[index + 2]; } for (unsigned int i = 0; i < tris->num_points; i++) { spr->pts[i] = vec2i((int)tris->points[i].x, (int)tris->points[i].y); } spr->num_tris = (uint16_t)tris->num_triangles; spr->num_points = (int)tris->num_points; tri_delaunay2d_release(tris); ATLAS_FREE(temp_pts); } atlas_t* atlas_loadimages(array(atlas_image) images, atlas_flags flags) { assert(images); array(int) frames = 0; array(atlas_sprite) sprites = 0; for (int i = 0; i < array_count(images); i++) { // find is_cached { int found = 0, k = 0; static array(uint64_t) cache = 0; static array(uint64_t) index = 0; uint64_t hash = hash_init; hash = hash_bin(&images[i].width, sizeof(images[i].width), hash); hash = hash_bin(&images[i].height, sizeof(images[i].height), hash); hash = hash_bin((char*)images[i].pixels, images[i].width * images[i].height * 4, hash); for (; k < array_count(cache); ++k) if (cache[k] == hash) { found = 1; break; } if (found) { array_push(frames, index[k]); continue; } else { array_push(cache, hash); array_push(index, k); array_push(frames, k); } //printf("%d) %llx\n", array_count(cache), hash); } atlas_sprite zero = {0}; atlas_sprite* spr = &zero; if(images[i].name) spr->name = STRDUP(images[i].name); spr->frame = i; spr->src_size.x = images[i].width; spr->src_size.y = images[i].height; assert(images[i].width > 0 && images[i].height > 0); assert(images[i].pixels); uint8_t* pixels = images[i].pixels; // rescale if (!equalf(flags.scale, 1.0f, 0.0001f)) { int target_w = (int)((float)spr->src_size.x * flags.scale); int target_h = (int)((float)spr->src_size.y * flags.scale); uint8_t* resized_pixels = ATLAS_CALLOC(1, 4 * target_w * target_h); panic_if(!resized_pixels); if (!stbir_resize_uint8(pixels, spr->src_size.x, spr->src_size.y, 4 * spr->src_size.x, resized_pixels, target_w, target_h, 4 * target_w, 4)) { snprintf(g_error_str, sizeof(g_error_str), "could not resize image: #%d", i + 1); atlas__free_sprites(sprites, array_count(sprites)); return NULL; } stbi_image_free(pixels); spr->src_size.x = target_w; spr->src_size.y = target_h; pixels = resized_pixels; } spr->src_image = pixels; recti sprite_rect = {0}; int pt_count = 0; s2o_point* pts = 0; uint8_t* alpha = s2o_rgba_to_alpha(spr->src_image, spr->src_size.x, spr->src_size.y); uint8_t* thresholded = s2o_alpha_to_thresholded(alpha, spr->src_size.x, spr->src_size.y, flags.alpha_threshold); free(alpha); if (flags.mesh && spr->src_size.x > 1 && spr->src_size.y > 1) { uint8_t* dilate_thres = s2o_dilate_thresholded(thresholded, spr->src_size.x, spr->src_size.y); uint8_t* outlined = s2o_thresholded_to_outlined(dilate_thres, spr->src_size.x, spr->src_size.y); free(dilate_thres); pts = s2o_extract_outline_path(outlined, spr->src_size.x, spr->src_size.y, &pt_count, NULL); free(outlined); //< @r-lyeh @fixme: many sprites will return extremely low num of points (like 8) even if the sprite is complex enough. //< this will lead to produce here a nearly zero sprite_rect, then sheet_rect, then eventually an empty frame at end of pipeline. // calculate cropped rectangle sprite_rect = recti(INT_MAX, INT_MAX, INT_MIN, INT_MIN); for (int k = 0; k < pt_count; k++) { recti_add_point(&sprite_rect, vec2i(pts[k].x, pts[k].y)); } sprite_rect.xmax++; sprite_rect.ymax++; } else { sprite_rect = recti(0, 0, spr->src_size.x, spr->src_size.y); pt_count = 4; pts = ATLAS_CALLOC(pt_count, sizeof(s2o_point)); pts[0] = (s2o_point) {0, 0}; pts[1] = (s2o_point) {spr->src_size.x, 0}; pts[2] = (s2o_point) {spr->src_size.x, spr->src_size.y}; pts[3] = (s2o_point) {0, spr->src_size.y}; } // generate mesh if set in arguments if (flags.mesh) { atlas__make_mesh(spr, pts, pt_count, flags.max_verts_per_mesh, thresholded, spr->src_size.x, spr->src_size.y); } ATLAS_FREE(pts); free(thresholded); spr->sprite_rect = sprite_rect; array_push(sprites, *spr); } int num_sprites = array_count(sprites); // pack sprites into a sheet stbrp_context rp_ctx = {0}; int max_width = flags.max_width; int max_height = flags.max_height; int num_rp_nodes = max_width + max_height; stbrp_rect* rp_rects = ATLAS_CALLOC(num_sprites, sizeof(stbrp_rect)); stbrp_node* rp_nodes = ATLAS_CALLOC(num_rp_nodes, sizeof(stbrp_node)); panic_if(!rp_rects || !rp_nodes); for (int i = 0; i < num_sprites; i++) { recti rc = sprites[i].sprite_rect; int rc_resize = (flags.border + flags.padding) * 2; rp_rects[i].w = (rc.xmax - rc.xmin) + rc_resize; rp_rects[i].h = (rc.ymax - rc.ymin) + rc_resize; } stbrp_init_target(&rp_ctx, max_width, max_height, rp_nodes, num_rp_nodes); recti final_rect = recti(INT_MAX, INT_MAX, INT_MIN, INT_MIN); if (stbrp_pack_rects(&rp_ctx, rp_rects, num_sprites)) { for (int i = 0; i < num_sprites; i++) { atlas_sprite* spr = &sprites[i]; recti sheet_rect = rectiwh(rp_rects[i].x, rp_rects[i].y, rp_rects[i].w, rp_rects[i].h); // calculate the total size of output image recti_add_point(&final_rect, sheet_rect.vmin); recti_add_point(&final_rect, sheet_rect.vmax); // shrink back rect and set the real sheet_rect for the sprite spr->sheet_rect = recti_expand(sheet_rect, vec2i(-flags.border, -flags.border)); } } int dst_w = final_rect.xmax - final_rect.xmin; int dst_h = final_rect.ymax - final_rect.ymin; // make output size divide by 4 by default dst_w = align_mask(dst_w, 3); dst_h = align_mask(dst_h, 3); if (flags.pot) { dst_w = nearest_pow2(dst_w); dst_h = nearest_pow2(dst_h); } uint8_t* dst = ATLAS_CALLOC(1, dst_w * dst_h * 4); panic_if(!dst); // calculate UVs for sprite meshes if (flags.mesh) { for (int i = 0; i < num_sprites; i++) { atlas_sprite* spr = &sprites[i]; // if sprite has mesh, calculate UVs for it if (spr->pts && spr->num_points) { const int padding = flags.padding; vec2i offset = spr->sprite_rect.vmin; vec2i sheet_pos = vec2i(spr->sheet_rect.xmin + padding, spr->sheet_rect.ymin + padding); vec2i* uvs = ATLAS_CALLOC(spr->num_points, sizeof(vec2i)); assert(uvs); for (int pi = 0; pi < spr->num_points; pi++) { vec2i pt = spr->pts[pi]; uvs[pi] = add2i(sub2i(pt, offset), sheet_pos); } spr->uvs = uvs; } // generate uvs } } for (int i = 0; i < num_sprites; i++) { const atlas_sprite* spr = &sprites[i]; // calculate UVs for sprite-meshes // remove padding and blit from src_image to dst recti dstrc = recti_expand(spr->sheet_rect, vec2i(-flags.padding, -flags.padding)); recti srcrc = spr->sprite_rect; atlas__blit(dst, dstrc.xmin, dstrc.ymin, dst_w * 4, spr->src_image, srcrc.xmin, srcrc.ymin, srcrc.xmax - srcrc.xmin, srcrc.ymax - srcrc.ymin, spr->src_size.x * 4, 32); } atlas_t* atlas = ATLAS_CALLOC(1, sizeof(atlas_t)); panic_if(!atlas); atlas->output.pixels = dst; atlas->output.width = dst_w; atlas->output.height = dst_h; atlas->sprites = sprites; atlas->num_sprites = num_sprites; atlas->frames = frames; atlas->num_frames = array_count(frames); ATLAS_FREE(rp_nodes); ATLAS_FREE(rp_rects); return atlas; } static char *atlas_anims = 0; static char *atlas_current_anim = 0; atlas_t* atlas_loadfiles(array(char*) files, atlas_flags flags) { assert(files); array(atlas_image) images = 0; for (int i = 0; i < array_count(files); ++i) { if (!path_isfile(files[i])) { snprintf(g_error_str, sizeof(g_error_str), "input image not found: %s", files[i]); goto err_cleanup; } int comp; atlas_image img = {0}; img.pixels = stbi_load(files[i], &img.width, &img.height, &comp, 4); #ifdef CUTE_ASEPRITE_H if (!img.pixels) { bool loaded = 0; for( ase_t* ase = cute_aseprite_load_from_file(files[i], NULL); ase; cute_aseprite_free(ase), ase = 0, loaded = 1) { ase_tag_t *parent = ase->tags + 0; //< abc/def/ghi.aseprite -> ghi if( atlas_current_anim ) *atlas_current_anim = '\0'; strcatf(&atlas_current_anim, files[i]); path_basename(atlas_current_anim, strlen(atlas_current_anim), files[i]); if( strrchr(atlas_current_anim, '.')) *strrchr(atlas_current_anim, '.') = '\0'; trimspace(atlas_current_anim); //< for( int f = 0; f < ase->frame_count; ++f) { ase_frame_t *frame = ase->frames + f; // find rect int x = INT_MAX, y = INT_MAX, x2 = INT_MIN, y2 = INT_MIN; for( int c = 0; c < frame->cel_count; ++c ) { ase_cel_t *cel = frame->cels + c; if( cel->layer->flags & ASE_LAYER_FLAGS_VISIBLE ) { if( cel->x < x ) x = cel->x; if( cel->h < y ) y = cel->y; if( (cel->x + cel->w) > x2 ) x2 = cel->x + cel->w; if( (cel->y + cel->h) > y2 ) y2 = cel->y + cel->h; } } if (x2 <= 0 || y2 <= 0) { // submit empty frame img.width = 1; img.height = 1; img.pixels = calloc(1, 1*1*4); array_push(images, img); continue; } int cx = x; int cy = y; int cw = x2-x; int ch = y2-y; int tn = 4; int tw = ase->w; // find clip img.width = cw; img.height = ch; img.pixels = calloc(1, cw*ch*4); // @fixme: because of a stbi_image_free() within rescale section, this should be allocated with stbi allocator for( unsigned y = 0; y < ch; ++y ) memcpy((char *)img.pixels + (0+(0+y)*cw)*tn, (char*)frame->pixels + (cx+(cy+y)*tw)*tn, cw*tn); array_push(images, img); } static int count = 0; if(!atlas_anims) strcatf(&atlas_anims, "[anims]\n"); for( int t = 0; t < ase->tag_count; ++t) { ase_tag_t *tag = ase->tags + t; // find full name int range[2] = {tag->from_frame, tag->to_frame}; char name[256] = {0}; for( int tt = 0; tt < ase->tag_count; ++tt ) { ase_tag_t *ttag = ase->tags + tt; if( range[0] >= ttag->from_frame && range[1] <= ttag->to_frame ) strcat(name, "."), strcat(name, ttag->name); } trimspace(name); char *sep = ""; strcatf(&atlas_anims, "[%d].name=%s.%s\n", count, atlas_current_anim, name+1); strcatf(&atlas_anims, "[%d].frames=", count); if( tag->loop_animation_direction != ASE_ANIMATION_DIRECTION_BACKWARDS) for( int from = tag->from_frame; from <= tag->to_frame; ++from ) { strcatf(&atlas_anims, "%s%d,%d", sep, from, ase->frames[from].duration_milliseconds), sep = ","; } sep = ""; if( tag->loop_animation_direction != ASE_ANIMATION_DIRECTION_FORWARDS) for( int from = tag->from_frame; from <= tag->to_frame; ++from ) { strcatf(&atlas_anims, "%s%d,%d", sep, from, ase->frames[from].duration_milliseconds), sep = ","; } strcatf(&atlas_anims,"\n"); ++count; } } if( loaded ) continue; } #endif if (!img.pixels) { continue; //< @r-lyeh: keep going snprintf(g_error_str, sizeof(g_error_str), "invalid image format: %s", files[i]); goto err_cleanup; } if( !img.name ) img.name = STRDUP(files[i]); array_push(images, img); } atlas_t* atlas = atlas_loadimages(images, flags); return atlas; err_cleanup: for (int i = 0; i < array_count(images); i++) { if (images[i].pixels) { stbi_image_free(images[i].pixels); } if (images[i].name) { ATLAS_FREE(images[i].name); } } array_free(images); return NULL; } void atlas_free(atlas_t* atlas) { assert(atlas); if (atlas->sprites) atlas__free_sprites(atlas->sprites, atlas->num_sprites); if (atlas->frames) ATLAS_FREE(atlas->frames); if (atlas->output.pixels) ATLAS_FREE(atlas->output.pixels); ATLAS_FREE(atlas); } // custom write function typedef struct { int offset; void *buffer; } stbi_mem_context; static void stbi_write_mem(void *context, void *data, int size) { stbi_mem_context *ctx = (stbi_mem_context*)context; memcpy( ctx->buffer, data, size ); ctx->offset += size; } bool atlas_save(const char *outfile, const atlas_t *atlas, atlas_flags flags) { assert(outfile); const bool is_file = strcmp(outfile, "stdout"); const atlas_sprite* sprites = atlas->sprites; const int* frames = atlas->frames; const int num_frames = atlas->num_frames; const int num_sprites = atlas->num_sprites; const uint8_t* dst = atlas->output.pixels; const int dst_w = atlas->output.width; const int dst_h = atlas->output.height; char image_filepath[256]; char image_filename[256]; snprintf(image_filepath, sizeof(image_filepath), "%s.png", outfile); path_basename(image_filename, sizeof(image_filename), image_filepath); stbi_write_png_compression_level = 5; // 8 // write texture, if needed if( is_file ) { if (!stbi_write_png(image_filepath, dst_w, dst_h, 4, dst, dst_w * 4)) { fprintf(stderr, "could not write image file `%s`\n", image_filepath); return false; } } // write atlas description into .ini file FILE *writer = is_file ? fopen(outfile, "wt") : stdout; if (!writer) { fprintf(stderr, "could not write ini file `%s`\n", outfile); return false; } fprintf(writer, "[atlas]\n"); if (is_file) { fprintf(writer, "file=%s\n", image_filepath); } else { stbi_mem_context ctx = {0, ATLAS_CALLOC(1, dst_w*dst_h*4+256) }; int result = stbi_write_png_to_func(stbi_write_mem, &ctx, dst_w, dst_h, 4, dst, dst_w*4); char *b64 = base64_encode(ctx.buffer, ctx.offset); fprintf(writer, "bitmap=%s\n", b64); // %d:%s\n", ctx.offset, b64); ATLAS_FREE(ctx.buffer); free(b64); } fprintf(writer, "size=%d,%d\n", dst_w, dst_h); fprintf(writer, "border=%d,%d\n", flags.border, flags.border); fprintf(writer, "padding=%d,%d\n", flags.padding, flags.padding); for( int i = 0; i < num_frames; i++ ) { const atlas_sprite* spr = sprites + frames[i]; char name[256]; path_unixpath(name, sizeof(name), spr->name ? spr->name : ""); if(name[0]) fprintf(writer, "[%d].name=%s\n", i, name); fprintf(writer, "[%d].frame=%u\n", i, spr->frame); //fprintf(writer, "[%d].size=%d,%d\n", i, spr->src_size.n[0], spr->src_size.n[1]); //fprintf(writer, "[%d].rect=%u,%u,%u,%u\n", i, spr->sprite_rect.f[0], spr->sprite_rect.f[1], spr->sprite_rect.f[2], spr->sprite_rect.f[3]); fprintf(writer, "[%d].sheet=%u,%u,%u,%u\n", i, spr->sheet_rect.f[0], spr->sheet_rect.f[1], spr->sheet_rect.f[2], spr->sheet_rect.f[3]); if( spr->num_tris ) { fprintf(writer, "[%d].indices=", i); // %d:", i, (int)spr->num_tris * 3); for( int j = 0, jend = (int)spr->num_tris * 3; j < jend; ++j ) fprintf(writer, "%u%s", spr->tris[j], j < (jend-1) ? "," : "\n"); fprintf(writer, "[%d].coords=", i); // %d:", i, spr->num_points*2); for( int j = 0, jend = spr->num_points; j < jend; j++ ) fprintf(writer, "%.f,%.f%s", (double)spr->pts[j].x, (double)spr->pts[j].y, j < (jend-1) ? ",":"\n" ); fprintf(writer, "[%d].uvs=", i); // %d:", i, spr->num_points*2); for( int j = 0, jend = spr->num_points; j < jend; j++ ) fprintf(writer, "%.f,%.f%s", (double)spr->uvs[j].x, (double)spr->uvs[j].y, j < (jend-1) ? ",":"\n" ); } } if( atlas_anims ) fprintf(writer, "%s\n", atlas_anims); if(writer != stdout) fclose(writer); return true; } #endif // ATLASC_IMPLEMENTATION