output image metadata

pull/1/head
cosmonaut 2022-07-22 16:05:54 -07:00
parent 943253add1
commit 6faceb9d16
5 changed files with 323 additions and 76 deletions

View File

@ -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
)

View File

@ -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);

View File

@ -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);
}

156
tools/cli/json_writer.h Normal file
View File

@ -0,0 +1,156 @@
#ifndef JSON_WRITER_H
#define JSON_WRITER_H
#include <string.h>
#include <stdint.h>
#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 */

View File

@ -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;