/* Cram - A texture packing system in C * * Copyright (c) 2022 Evan Hemsley * * This software is provided 'as-is', without any express or implied warranty. * In no event will the authors be held liable for any damages arising from * the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software in a * product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * * Evan "cosmonaut" Hemsley * */ #include "cram.h" #ifdef _MSC_VER #include #include #include #include #endif /* _MSC_VER */ /* TODO: ifndefs here */ #define Cram_assert assert #define Cram_qsort qsort #define Cram_malloc malloc #define Cram_realloc realloc #define Cram_free free #define Cram_memcpy memcpy #define Cram_memset memset #define Cram_strdup strdup #define Cram_max max #define STBRP_ASSERT Cram_assert #define STBRP_SORT Cram_qsort #define STBRP_STATIC #define STB_RECT_PACK_IMPLEMENTATION #include "stb_rect_pack.h" #define STBI_ASSERT Cram_assert #define STBI_MALLOC Cram_malloc #define STBI_REALLOC Cram_realloc #define STBI_FREE Cram_free #define STBI_ONLY_PNG #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define INITIAL_DATA_CAPACITY 8 /* Structures */ typedef struct Rect { int32_t x, y; int32_t w, h; } Rect; typedef struct Cram_Image { const char *name; Rect rect; uint8_t *pixels; } Cram_Image; typedef struct Cram_Internal_Context { const char *name; uint32_t width; uint32_t height; uint32_t padding; uint8_t trim; uint8_t *pixels; Cram_Image *images; uint32_t imageCount; uint32_t imageCapacity; Cram_AtlasData *atlasData; } Cram_Internal_Context; /* API functions */ uint32_t Wellspring_LinkedVersion(void) { return WELLSPRING_COMPILED_VERSION; } Cram_Context* Cram_Init(Cram_ContextCreateInfo *createInfo) { Cram_Internal_Context *context = Cram_malloc(sizeof(Cram_Internal_Context)); context->name = Cram_strdup(createInfo->name); context->width = createInfo->maxDimension; context->height = createInfo->maxDimension; context->padding = createInfo->padding; context->trim = createInfo->trim; context->images = Cram_malloc(INITIAL_DATA_CAPACITY * sizeof(Cram_Image)); context->imageCapacity = INITIAL_DATA_CAPACITY; context->imageCount = 0; context->atlasData = Cram_malloc(sizeof(Cram_AtlasData)); context->pixels = NULL; return (Cram_Context*) context; } static inline uint32_t Cram_Internal_GetPixelIndex(uint32_t x, uint32_t y, uint32_t width) { return x + y * width; } /* width and height of source and destination rects must be the same! */ static int8_t Cram_Internal_CopyPixels( uint32_t *dstPixels, uint32_t dstPixelWidth, uint32_t *srcPixels, uint32_t srcPixelWidth, Rect *dstRect, Rect *srcRect ) { int32_t i, j; int32_t dstPixelIndex, srcPixelIndex; if (dstRect->w != srcRect->w || dstRect->h != srcRect->h) { return -1; } for (i = 0; i < dstRect->w; i += 1) { for (j = 0; j < dstRect->h; j += 1) { dstPixelIndex = Cram_Internal_GetPixelIndex(i + dstRect->x, j + dstRect->y, dstPixelWidth); srcPixelIndex = Cram_Internal_GetPixelIndex(i + srcRect->x, j + srcRect->y, srcPixelWidth); dstPixels[dstPixelIndex] = srcPixels[srcPixelIndex]; } } return 0; } static uint8_t Cram_Internal_IsRowClear(uint32_t* pixels, uint32_t rowIndex, uint32_t width) { int32_t i; for (i = 0; i < width; i += 1) { if ((pixels[Cram_Internal_GetPixelIndex(i, rowIndex, width)] & 0xFF) > 0) { return 0; } } return 1; } static uint8_t Cram_Internal_IsColumnClear(uint32_t* pixels, uint32_t columnIndex, uint32_t width, uint32_t height) { int32_t i; for (i = 0; i < height; i += 1) { if ((pixels[Cram_Internal_GetPixelIndex(columnIndex, i, width)] & 0xFF) > 0) { return 0; } } return 1; } void Cram_AddFile(Cram_Context *context, const char *path) { Cram_Internal_Context *internalContext = (Cram_Internal_Context*) context; Cram_Image *image; uint8_t *pixels; int32_t leftTrim, topTrim, rightTrim, bottomTrim; int32_t width, height, numChannels; int32_t i; if (internalContext->imageCapacity == internalContext->imageCount) { internalContext->imageCapacity *= 2; internalContext->images = Cram_realloc(internalContext->images, internalContext->imageCapacity * sizeof(Cram_Image)); } image = &internalContext->images[internalContext->imageCount]; /* image->name = Cram_Internal_GetTrimmedPath(); */ pixels = stbi_load( path, &width, &height, &numChannels, STBI_rgb_alpha ); /* Check for trim */ if (internalContext->trim) { topTrim = 0; for (i = 0; i < height; i += 1) { if (!Cram_Internal_IsRowClear(pixels, i, width)) { topTrim = i; break; } } leftTrim = 0; for (i = 0; i < width; i += 1) { if (!Cram_Internal_IsColumnClear(pixels, i, width, height)) { leftTrim = i; break; } } bottomTrim = height; for (i = height - 1; i >= topTrim; i -= 1) { if (!Cram_Internal_IsRowClear(pixels, i, width)) { bottomTrim = i; break; } } rightTrim = width; for (i = width - 1; i >= leftTrim; i -= 1) { if (!Cram_Internal_IsColumnClear(pixels, i, width, height)) { rightTrim = i; break; } } image->rect.x = leftTrim; image->rect.y = topTrim; image->rect.w = rightTrim - leftTrim; image->rect.h = bottomTrim - topTrim; } else { image->rect.x = 0; image->rect.y = 0; image->rect.w = width; image->rect.h = height; } /* copy */ image->pixels = Cram_malloc(image->rect.w * image->rect.h * 4); Rect dstRect; dstRect.x = 0; dstRect.y = 0; dstRect.w = image->rect.w; dstRect.h = image->rect.h; Cram_Internal_CopyPixels((uint32_t*) image->pixels, image->rect.w, (uint32_t*) pixels, width, &dstRect, &image->rect); stbi_image_free(pixels); internalContext->imageCount += 1; } int8_t Cram_Pack(Cram_Context *context) { stbrp_context rectPackContext; Cram_Internal_Context *internalContext = (Cram_Internal_Context*) context; uint32_t numNodes = internalContext->width; stbrp_node *nodes = Cram_malloc(sizeof(stbrp_node) * numNodes); stbrp_rect *rects = Cram_malloc(sizeof(stbrp_rect) * internalContext->imageCount); stbrp_rect *rect; Rect dstRect, srcRect; int32_t i; uint32_t maxWidth = 0; uint32_t maxHeight = 0; stbrp_init_target(&rectPackContext, internalContext->width, internalContext->height, nodes, numNodes); for (i = 0; i < internalContext->imageCount; i += 1) { rect = &rects[i]; rect->w = internalContext->images[i].rect.w + internalContext->padding; rect->h = internalContext->images[i].rect.h + internalContext->padding; } stbrp_pack_rects(&rectPackContext, rects, internalContext->imageCount); for (i = 0; i < internalContext->imageCount; i += 1) { rect = &rects[i]; if (rect->was_packed) { internalContext->images[i].rect.x = rect->x; internalContext->images[i].rect.y = rect->y; maxWidth = Cram_max(maxWidth, rect->x + rect->w); maxHeight = Cram_max(maxHeight, rect->y + rect->h); } else { return -1; } } internalContext->width = maxWidth; internalContext->height = maxHeight; internalContext->pixels = Cram_realloc(internalContext->pixels, internalContext->width * internalContext->height * 4); Cram_memset(internalContext->pixels, 0, internalContext->width * internalContext->height * 4); for (i = 0; i < internalContext->imageCount; i += 1) { dstRect.x = internalContext->images[i].rect.x; dstRect.y = internalContext->images[i].rect.y; dstRect.w = internalContext->images[i].rect.w; dstRect.h = internalContext->images[i].rect.h; srcRect.x = 0; srcRect.y = 0; srcRect.w = internalContext->images[i].rect.w; srcRect.h = internalContext->images[i].rect.h; Cram_Internal_CopyPixels( (uint32_t*) internalContext->pixels, internalContext->width, (uint32_t*) internalContext->images[i].pixels, internalContext->images[i].rect.w, &dstRect, &srcRect ); } Cram_free(nodes); Cram_free(rects); return 0; } void Cram_GetPixelData(Cram_Context *context, uint8_t **pPixels, uint32_t *pWidth, uint32_t *pHeight) { Cram_Internal_Context *internalContext = (Cram_Internal_Context*) context; *pPixels = internalContext->pixels; *pWidth = internalContext->width; *pHeight = internalContext->height; } void Cram_GetAtlasData(Cram_Context *context, Cram_AtlasData **pAtlasData) { *pAtlasData = ((Cram_Internal_Context*) context)->atlasData; } void Cram_Destroy(Cram_Context *context) { Cram_Internal_Context *internalContext = (Cram_Internal_Context*) context; if (internalContext->pixels != NULL) { Cram_free(internalContext); } Cram_free(internalContext->images); Cram_free(internalContext->atlasData); Cram_free(internalContext); }