#define DQN_ASAN_POISON 1 #define DQN_ASAN_VET_POISON 1 #define DQN_ONLY_RECT #define DQN_ONLY_WIN #define DQN_ONLY_V2 #define DQN_ONLY_SLICE #define DQN_ONLY_SARRAY #define DQN_ONLY_LIST #define DQN_ONLY_FS #define _CRT_SECURE_NO_WARNINGS #define DQN_IMPLEMENTATION #include "External/tely/External/dqn/dqn.h" DQN_MSVC_WARNING_PUSH DQN_MSVC_WARNING_DISABLE(4244) // warning C4244: 'argument': conversion from 'int' to 'short', possible loss of data #define STB_RECT_PACK_IMPLEMENTATION #include "External/tely/external/stb/stb_rect_pack.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #include "External/tely/external/stb/stb_image_write.h" #define STB_IMAGE_IMPLEMENTATION #include "External/tely/external/stb/stb_image.h" DQN_MSVC_WARNING_POP int main(int argc, char const *argv[]) { if (argc != 2) { Dqn_Log_InfoF("USAGE: feely_pona_sprite_packer \"\""); return -1; } Dqn_Library_Init(); Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); Dqn_String8 dir = Dqn_String8_InitCString8(argv[1]); // NOTE: Get the list of files ================================================================= Dqn_List file_list = Dqn_List_Init(scratch.arena, 128); for (Dqn_Win_FolderIterator it = {}; Dqn_Win_FolderIterate(dir, &it); ) { if (Dqn_String8_EndsWithInsensitive(it.file_name, DQN_STRING8(".png"))) { Dqn_String8 *item = Dqn_List_Make(&file_list, Dqn_ZeroMem_Yes); *item = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/%.*s", DQN_STRING_FMT(dir), DQN_STRING_FMT(it.file_name)); } } // NOTE: Setup the rect-pack state ============================================================= int atlas_size = 2048; stbrp_node *nodes = Dqn_Arena_NewArray(scratch.arena, stbrp_node, file_list.count, Dqn_ZeroMem_Yes); stbrp_context pack_context = {}; stbrp_init_target(&pack_context, atlas_size, atlas_size, nodes, DQN_CAST(int)file_list.count); // NOTE: Load the sprites to determine their dimensions for rect packing Dqn_SArray rects = Dqn_SArray_Init(scratch.arena, file_list.count, Dqn_ZeroMem_Yes); for (Dqn_ListIterator it = {}; Dqn_List_Iterate(&file_list, &it, 0);) { int x = 0, y = 0, channels_in_file = 0; stbi_uc *pixels = stbi_load(it.data->data, &x, &y, &channels_in_file, 4 /*desired_channels*/); DQN_ASSERT(pixels); stbi_image_free(pixels); // NOTE: Add a rect to the packing context stbrp_rect *rect = Dqn_SArray_Make(&rects, Dqn_ZeroMem_Yes); rect->w = x; rect->h = y; Dqn_Log_InfoF("Packing sprite: %.*s", DQN_STRING_FMT(*it.data)); } // NOTE: Pack the rects ======================================================================== if (stbrp_pack_rects(&pack_context, rects.data, DQN_CAST(int)rects.size) != 1) { Dqn_Log_ErrorF("STB rect pack failed to pack font rects into rectangle [width=%d, height=%d, num_rects=%d]", atlas_size, atlas_size, file_list.count); return -1; } // NOTE: Load the files once more and generate the final image ================================= int final_bpp = 4; int final_image_stride = atlas_size * final_bpp; char *final_image = Dqn_Arena_NewArray(scratch.arena, char, atlas_size * final_image_stride, Dqn_ZeroMem_Yes); // NOTE: Generate the meta file ================================================================ Dqn_String8 meta_path = Dqn_String8_InitF(scratch.allocator, "%.*s.txt", DQN_STRING_FMT(dir)); Dqn_FsFile meta_file = Dqn_Fs_OpenFile(meta_path, Dqn_FsFileOpen_CreateAlways, Dqn_FsFileAccess_Write); Dqn_Log_InfoF("Generating meta file: %.*s", DQN_STRING_FMT(meta_path)); for (Dqn_ListIterator it = {}; Dqn_List_Iterate(&file_list, &it, 0);) { int w, h, channels_in_file; stbi_uc *loaded_image = stbi_load(it.data->data, &w, &h, &channels_in_file, 4 /*desired_channels*/); stbrp_rect packed_rect = rects.data[it.index]; char *src = DQN_CAST(char *)loaded_image; // NOTE: Generate the image ================================================================ for (int y = 0; y < packed_rect.h; y++) { char *row = final_image + ((packed_rect.y + y) * final_image_stride) + (packed_rect.x * final_bpp); for (int x = 0; x < packed_rect.w; x++) { *row++ = *src++; *row++ = *src++; *row++ = *src++; *row++ = *src++; } } stbi_image_free(loaded_image); // NOTE: Write the sprite and the rects to the sheet Dqn_String8 file_name = Dqn_String8_FileNameFromPath(*it.data); Dqn_String8 file_name_without_extension = Dqn_String8_BinarySplit(file_name, DQN_STRING8(".")).lhs; Dqn_Fs_WriteFileF(&meta_file, "%.*s;%d;%d;%d;%d", DQN_STRING_FMT(file_name_without_extension), packed_rect.x, packed_rect.y, packed_rect.w, packed_rect.h); if (it.index != (file_list.count - 1)) Dqn_Fs_WriteFileF(&meta_file, "\n"); } Dqn_String8 atlas_path = Dqn_String8_InitF(scratch.allocator, "%.*s.png", DQN_STRING_FMT(dir)); Dqn_Log_InfoF("Generating atlas: %.*s", DQN_STRING_FMT(atlas_path)); stbi_write_png(atlas_path.data, atlas_size, atlas_size, 4, final_image, final_image_stride); return 0; }