From 6faceb9d16fa4b4c5e90d4ec6afb533434d383c9 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 22 Jul 2022 16:05:54 -0700 Subject: [PATCH] output image metadata --- CMakeLists.txt | 1 + include/cram.h | 37 +++++----- src/cram.c | 140 +++++++++++++++++++++++++----------- tools/cli/json_writer.h | 156 ++++++++++++++++++++++++++++++++++++++++ tools/cli/main.c | 65 +++++++++++++---- 5 files changed, 323 insertions(+), 76 deletions(-) create mode 100644 tools/cli/json_writer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6774061..9591c60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ if(BUILD_CLI) add_executable(cramcli tools/cli/lib/dirent.h tools/cli/lib/stb_image_write.h + tools/cli/json_writer.h tools/cli/main.c ) diff --git a/include/cram.h b/include/cram.h index abfd845..9399a65 100644 --- a/include/cram.h +++ b/include/cram.h @@ -59,6 +59,14 @@ #define Cram_min min #define Cram_max max +#ifdef _WIN32 +#define SEPARATOR '\\' +#endif + +#ifdef __unix__ +#define SEPARATOR '/' +#endif + #ifdef __cplusplus extern "C" { @@ -90,28 +98,19 @@ typedef struct Cram_ContextCreateInfo typedef struct Cram_ImageData { - const char *path; + char *name; - uint32_t x; - uint32_t y; - uint32_t width; - uint32_t height; + int32_t x; + int32_t y; + int32_t width; + int32_t height; - uint32_t offsetX; - uint32_t offsetY; - uint32_t trimmedWidth; - uint32_t trimmedHeight; + int32_t offsetX; + int32_t offsetY; + int32_t untrimmedWidth; + int32_t untrimmedHeight; } Cram_ImageData; -typedef struct Cram_AtlasData -{ - const char *name; - uint32_t width; - uint32_t height; - Cram_ImageData *imageDatas; - uint32_t imageDataCount; -} Cram_AtlasData; - /* API definition */ CRAMAPI Cram_Context* Cram_Init(Cram_ContextCreateInfo *createInfo); @@ -121,7 +120,7 @@ CRAMAPI void Cram_AddFile(Cram_Context *context, const char *path); CRAMAPI int8_t Cram_Pack(Cram_Context *context); CRAMAPI void Cram_GetPixelData(Cram_Context *context, uint8_t **pPixelData, uint32_t *pWidth, uint32_t *pHeight); -CRAMAPI void Cram_GetAtlasData(Cram_Context *context, Cram_AtlasData **pAtlasData); +CRAMAPI void Cram_GetMetadata(Cram_Context *context, Cram_ImageData **pImage, uint32_t *pImageCount); CRAMAPI void Cram_Destroy(Cram_Context *context); diff --git a/src/cram.c b/src/cram.c index 07c7899..2516f99 100644 --- a/src/cram.c +++ b/src/cram.c @@ -53,14 +53,17 @@ typedef struct Rect int32_t w, h; } Rect; -typedef struct Cram_Image +typedef struct Cram_Image Cram_Image; + +struct Cram_Image { - const char *name; + char *name; + Rect originalRect; Rect rect; - uint8_t duplicate; /* FIXME: is there a better way to do this? */ - uint8_t *pixels; /* Will be NULL if duplicate! */ + Cram_Image *duplicateOf; + uint8_t *pixels; /* Will be NULL if duplicateOf is not NULL! */ size_t hash; -} Cram_Image; +}; typedef struct Cram_Internal_Context { @@ -73,11 +76,12 @@ typedef struct Cram_Internal_Context uint8_t *pixels; - Cram_Image *images; + Cram_Image **images; uint32_t imageCount; uint32_t imageCapacity; - Cram_AtlasData *atlasData; + Cram_ImageData *imageDatas; + uint32_t imageDataCount; } Cram_Internal_Context; typedef struct RectPackContext @@ -486,17 +490,32 @@ Cram_Context* Cram_Init(Cram_ContextCreateInfo *createInfo) context->padding = createInfo->padding; context->trim = createInfo->trim; - context->images = Cram_malloc(INITIAL_DATA_CAPACITY * sizeof(Cram_Image)); + 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; + context->imageDatas = NULL; + context->imageDataCount = 0; return (Cram_Context*) context; } +static char* Cram_Internal_GetImageName(const char *path) +{ + char *lastSeparator = strrchr(path, SEPARATOR) + 1; + size_t returnBytes = strlen(lastSeparator) + 1; + char *name = Cram_malloc(returnBytes); + int32_t i; + + for (i = 0; i < returnBytes; i += 1) + { + name[i] = lastSeparator[i]; + } + + return name; +} + void Cram_AddFile(Cram_Context *context, const char *path) { Cram_Internal_Context *internalContext = (Cram_Internal_Context*) context; @@ -509,12 +528,13 @@ void Cram_AddFile(Cram_Context *context, const char *path) if (internalContext->imageCapacity == internalContext->imageCount) { internalContext->imageCapacity *= 2; - internalContext->images = Cram_realloc(internalContext->images, internalContext->imageCapacity * sizeof(Cram_Image)); + internalContext->images = Cram_realloc(internalContext->images, internalContext->imageCapacity * sizeof(Cram_Image*)); } - image = &internalContext->images[internalContext->imageCount]; + image = Cram_malloc(sizeof(Cram_Image)); + internalContext->images[internalContext->imageCount] = image; - /* image->name = Cram_Internal_GetTrimmedPath(); */ + image->name = Cram_Internal_GetImageName(path); pixels = stbi_load( path, @@ -524,6 +544,11 @@ void Cram_AddFile(Cram_Context *context, const char *path) STBI_rgb_alpha ); + image->originalRect.x = 0; + image->originalRect.y = 0; + image->originalRect.w = width; + image->originalRect.h = height; + /* Check for trim */ if (internalContext->trim) { @@ -574,10 +599,7 @@ void Cram_AddFile(Cram_Context *context, const char *path) } else { - image->rect.x = 0; - image->rect.y = 0; - image->rect.w = width; - image->rect.h = height; + image->rect = image->originalRect; } /* copy and free source pixels */ @@ -595,15 +617,15 @@ void Cram_AddFile(Cram_Context *context, const char *path) image->hash = stbds_hash_bytes(image->pixels, image->rect.w * image->rect.h * 4, 0); /* check if this is a duplicate */ - image->duplicate = 0; + image->duplicateOf = NULL; for (i = 0; i < internalContext->imageCount; i += 1) { - if (!internalContext->images[i].duplicate) + if (!internalContext->images[i]->duplicateOf) { - if (Cram_Internal_IsImageEqual(image, &internalContext->images[i])) + if (Cram_Internal_IsImageEqual(image, internalContext->images[i])) { /* this is duplicate data! */ - image->duplicate = 1; + image->duplicateOf = image; Cram_free(image->pixels); image->pixels = NULL; break; @@ -623,15 +645,19 @@ int8_t Cram_Pack(Cram_Context *context) uint32_t numRects = 0; Rect *packerRect; Rect dstRect, srcRect; - int32_t i; + Cram_Image *image; uint32_t maxWidth = 0; uint32_t maxHeight = 0; + int32_t i; + + internalContext->imageDataCount = internalContext->imageCount; + internalContext->imageDatas = Cram_realloc(internalContext->imageDatas, sizeof(Cram_ImageData) * internalContext->imageDataCount); rectPackContext = Cram_Internal_InitRectPacker(internalContext->width, internalContext->height); for (i = 0; i < internalContext->imageCount; i += 1) { - if (!internalContext->images[i].duplicate) + if (!internalContext->images[i]->duplicateOf) { numRects += 1; } @@ -642,12 +668,12 @@ int8_t Cram_Pack(Cram_Context *context) numRects = 0; for (i = 0; i < internalContext->imageCount; i += 1) { - if (!internalContext->images[i].duplicate) + if (!internalContext->images[i]->duplicateOf) { packerRect = &packerRects[numRects]; - packerRect->w = internalContext->images[i].rect.w + internalContext->padding; - packerRect->h = internalContext->images[i].rect.h + internalContext->padding; + packerRect->w = internalContext->images[i]->rect.w + internalContext->padding; + packerRect->h = internalContext->images[i]->rect.h + internalContext->padding; numRects += 1; } @@ -661,12 +687,12 @@ int8_t Cram_Pack(Cram_Context *context) numRects = 0; for (i = 0; i < internalContext->imageCount; i += 1) { - if (!internalContext->images[i].duplicate) + if (!internalContext->images[i]->duplicateOf) { packerRect = &packerRects[numRects]; - internalContext->images[i].rect.x = packerRect->x; - internalContext->images[i].rect.y = packerRect->y; + internalContext->images[i]->rect.x = packerRect->x; + internalContext->images[i]->rect.y = packerRect->y; maxWidth = Cram_max(maxWidth, packerRect->x + packerRect->w); maxHeight = Cram_max(maxHeight, packerRect->y + packerRect->h); @@ -683,27 +709,48 @@ int8_t Cram_Pack(Cram_Context *context) for (i = 0; i < internalContext->imageCount; i += 1) { - if (!internalContext->images[i].duplicate) + if (!internalContext->images[i]->duplicateOf) { - 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; + 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; + 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, + (uint32_t*) internalContext->images[i]->pixels, + internalContext->images[i]->rect.w, &dstRect, &srcRect ); } + + if (internalContext->images[i]->duplicateOf) + { + image = internalContext->images[i]->duplicateOf; + } + else + { + image = internalContext->images[i]; + } + + internalContext->imageDatas[i].x = image->rect.x; + internalContext->imageDatas[i].y = image->rect.y; + internalContext->imageDatas[i].width = image->rect.w; + internalContext->imageDatas[i].height = image->rect.h; + + internalContext->imageDatas[i].offsetX = image->rect.x - image->originalRect.x; + internalContext->imageDatas[i].offsetY = image->rect.y - image->originalRect.y; + internalContext->imageDatas[i].untrimmedWidth = image->originalRect.w; + internalContext->imageDatas[i].untrimmedHeight = image->originalRect.h; + + internalContext->imageDatas[i].name = strdup(image->name); } Cram_free(packerRects); @@ -719,9 +766,12 @@ void Cram_GetPixelData(Cram_Context *context, uint8_t **pPixels, uint32_t *pWidt *pHeight = internalContext->height; } -void Cram_GetAtlasData(Cram_Context *context, Cram_AtlasData **pAtlasData) +void Cram_GetMetadata(Cram_Context *context, Cram_ImageData **pImage, uint32_t *pImageCount) { - *pAtlasData = ((Cram_Internal_Context*) context)->atlasData; + Cram_Internal_Context *internalContext = (Cram_Internal_Context*) context; + + *pImage = internalContext->imageDatas; + *pImageCount = internalContext->imageDataCount; } void Cram_Destroy(Cram_Context *context) @@ -736,13 +786,17 @@ void Cram_Destroy(Cram_Context *context) for (i = 0; i < internalContext->imageCount; i += 1) { - if (!internalContext->images[i].duplicate) + if (!internalContext->images[i]->duplicateOf) { - Cram_free(internalContext->images[i].pixels); + Cram_free(internalContext->images[i]->pixels); } + + Cram_free(internalContext->images[i]->name); + Cram_free(internalContext->images[i]); } + Cram_free(internalContext->name); Cram_free(internalContext->images); - Cram_free(internalContext->atlasData); + Cram_free(internalContext->imageDatas); Cram_free(internalContext); } diff --git a/tools/cli/json_writer.h b/tools/cli/json_writer.h new file mode 100644 index 0000000..5cbe6ce --- /dev/null +++ b/tools/cli/json_writer.h @@ -0,0 +1,156 @@ + +#ifndef JSON_WRITER_H +#define JSON_WRITER_H + +#include +#include + +#define INITIAL_JSON_OUTPUT_CAPACITY 2048 + +typedef struct JsonBuilder +{ + char *string; + size_t index; + size_t capacity; + size_t indentLevel; +} JsonBuilder; + +JsonBuilder* JsonBuilder_Init() +{ + JsonBuilder *builder = malloc(sizeof(JsonBuilder)); + + builder->string = malloc(INITIAL_JSON_OUTPUT_CAPACITY); + builder->capacity = INITIAL_JSON_OUTPUT_CAPACITY; + + builder->string[0] = '\0'; + + strcat(builder->string, "{\n"); + builder->indentLevel = 1; + builder->index = 2; + + return builder; +} + +void JsonBuilder_Internal_MaybeExpand(JsonBuilder *builder, size_t len) +{ + if (builder->capacity < builder->index + len) + { + builder->capacity = max(builder->index + len, builder->capacity * 2); + builder->string = realloc(builder->string, builder->capacity); + } +} + +void JsonBuilder_Internal_Indent(JsonBuilder *builder) +{ + int32_t i; + JsonBuilder_Internal_MaybeExpand(builder, builder->indentLevel); + + for (i = 0; i < builder->indentLevel; i += 1) + { + strcat(builder->string, "\t"); + } + + builder->index += builder->indentLevel; +} + +void JsonBuilder_Internal_RemoveTrailingComma(JsonBuilder *builder) +{ + if (builder->string[builder->index - 2] == ',') + { + builder->index -= 2; + builder->index += sprintf(&builder->string[builder->index], "\n"); + } +} + +void JsonBuilder_AppendProperty(JsonBuilder *builder, char *propertyName, char *propertyString, uint8_t isString) +{ + size_t lineLength; + + lineLength = strlen(propertyName) + strlen(propertyString) + 6; + if (isString) + { + lineLength += 2; + } + + JsonBuilder_Internal_MaybeExpand(builder, lineLength); + JsonBuilder_Internal_Indent(builder); + + builder->index += sprintf(&builder->string[builder->index], "\"%s\": ", propertyName); + + if (isString) + { + builder->index += sprintf(&builder->string[builder->index], "\"%s\",\n", propertyString); + } + else + { + builder->index += sprintf(&builder->string[builder->index], "%s,\n", propertyString); + } +} + +void JsonBuilder_AppendStringProperty(JsonBuilder *builder, char *propertyName, char *value) +{ + JsonBuilder_AppendProperty(builder, propertyName, value, 1); +} + +void JsonBuilder_AppendIntProperty(JsonBuilder *builder, char *propertyName, int32_t value) +{ + char buffer[65]; + itoa(value, buffer, 10); + JsonBuilder_AppendProperty(builder, propertyName, buffer, 0); +} + +void JsonBuilder_StartObject(JsonBuilder *builder) +{ + JsonBuilder_Internal_Indent(builder); + JsonBuilder_Internal_MaybeExpand(builder, 1); + builder->index += sprintf(&builder->string[builder->index], "{\n"); + builder->indentLevel += 1; +} + +void JsonBuilder_EndObject(JsonBuilder *builder) +{ + JsonBuilder_Internal_RemoveTrailingComma(builder); + + builder->indentLevel -= 1; + + JsonBuilder_Internal_Indent(builder); + JsonBuilder_Internal_MaybeExpand(builder, 3); + builder->index += sprintf(&builder->string[builder->index], "},\n"); +} + +void JsonBuilder_StartArrayProperty(JsonBuilder *builder, char *propertyName) +{ + JsonBuilder_Internal_Indent(builder); + JsonBuilder_Internal_MaybeExpand(builder, strlen(propertyName) + 6); + builder->index += sprintf(&builder->string[builder->index], "\"%s\": [\n", propertyName); + builder->indentLevel += 1; +} + +void JsonBuilder_FinishArrayProperty(JsonBuilder *builder) +{ + JsonBuilder_Internal_RemoveTrailingComma(builder); + + builder->indentLevel -= 1; + + JsonBuilder_Internal_Indent(builder); + JsonBuilder_Internal_MaybeExpand(builder, 3); + builder->index += sprintf(&builder->string[builder->index], "],\n"); +} + +void JsonBuilder_Finish(JsonBuilder *builder) +{ + builder->indentLevel = 0; + + JsonBuilder_Internal_RemoveTrailingComma(builder); + JsonBuilder_Internal_MaybeExpand(builder, 3); + + builder->index += sprintf(&builder->string[builder->index], "}\n"); +} + +void JsonBuilder_Destroy(JsonBuilder *builder) +{ + free(builder->string); + free(builder); +} + +#endif /* JSON_WRITER_H */ diff --git a/tools/cli/main.c b/tools/cli/main.c index 6a69e80..f5e620b 100644 --- a/tools/cli/main.c +++ b/tools/cli/main.c @@ -1,5 +1,6 @@ #include "cram.h" #include "dirent.h" +#include "json_writer.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" @@ -8,18 +9,6 @@ static Cram_Context *context; -#ifdef _WIN32 - -#define SEPARATOR "\\" - -#endif - -#ifdef __unix__ - -#define SEPARATOR "/" - -#endif - static const char* GetFilenameExtension(const char *filename) { const char *dot = strrchr(filename, '.'); @@ -52,7 +41,7 @@ static void dirwalk(char *dir) continue; } - sprintf(subname, "%s%s%s", dir, SEPARATOR, dp->d_name); + sprintf(subname, "%s%c%s", dir, SEPARATOR, dp->d_name); if (dp->d_type == DT_DIR) { @@ -102,6 +91,10 @@ int main(int argc, char *argv[]) char *inputDirPath = NULL; char *outputDirPath = NULL; char *imageOutputFilename; + char *metadataFilename; + JsonBuilder *jsonBuilder; + Cram_ImageData *imageDatas; + uint32_t imageCount; int32_t i; /* Set defaults */ @@ -195,8 +188,9 @@ int main(int argc, char *argv[]) return 1; } - Cram_GetPixelData(context, &pixelData, &width, &height); + /* output pixel data */ + Cram_GetPixelData(context, &pixelData, &width, &height); imageOutputFilename = Cram_malloc(strlen(createInfo.name) + 5); strcpy(imageOutputFilename, createInfo.name); strcat(imageOutputFilename, ".png"); @@ -210,7 +204,50 @@ int main(int argc, char *argv[]) width * 4 ); + /* output json */ + + Cram_GetMetadata(context, &imageDatas, &imageCount); + + jsonBuilder = JsonBuilder_Init(); + JsonBuilder_AppendStringProperty(jsonBuilder, "Name", createInfo.name); + JsonBuilder_AppendIntProperty(jsonBuilder, "Width", width); + JsonBuilder_AppendIntProperty(jsonBuilder, "Height", height); + JsonBuilder_StartArrayProperty(jsonBuilder, "Images"); + for (i = 0; i < imageCount; i += 1) + { + JsonBuilder_StartObject(jsonBuilder); + JsonBuilder_AppendStringProperty(jsonBuilder, "Name", imageDatas[i].name); + JsonBuilder_AppendIntProperty(jsonBuilder, "X", imageDatas[i].x); + JsonBuilder_AppendIntProperty(jsonBuilder, "Y", imageDatas[i].y); + JsonBuilder_AppendIntProperty(jsonBuilder, "W", imageDatas[i].width); + JsonBuilder_AppendIntProperty(jsonBuilder, "H", imageDatas[i].height); + JsonBuilder_AppendIntProperty(jsonBuilder, "OffsetX", imageDatas[i].offsetX); + JsonBuilder_AppendIntProperty(jsonBuilder, "OffsetY", imageDatas[i].offsetY); + JsonBuilder_AppendIntProperty(jsonBuilder, "UntrimmedWidth", imageDatas[i].untrimmedWidth); + JsonBuilder_AppendIntProperty(jsonBuilder, "UntrimmedHeight", imageDatas[i].untrimmedHeight); + JsonBuilder_EndObject(jsonBuilder); + } + JsonBuilder_FinishArrayProperty(jsonBuilder); + JsonBuilder_Finish(jsonBuilder); + + metadataFilename = Cram_malloc(strlen(createInfo.name) + 6); + strcpy(metadataFilename, createInfo.name); + strcat(metadataFilename, ".json"); + + FILE *jsonOutput = fopen(metadataFilename, "w"); + if (!jsonOutput) + { + fprintf(stderr, "Could not open JSON file for writing!"); + return 1; + } + + fprintf(jsonOutput, jsonBuilder->string); + + JsonBuilder_Destroy(jsonBuilder); + fclose(jsonOutput); + Cram_free(imageOutputFilename); + Cram_free(metadataFilename); Cram_Destroy(context); return 0;