/* Copyright (c) 2023 Alex Diener 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. Alex Diener alex@ludobloom.com */ #include "3dmodelio/TexturePacker.h" #include "gamemath/Scalar.h" #include "tileset/ImageCollection.h" #include "utilities/IOUtilities.h" #include #include ImageCollection * ImageCollection_create(ImageCollectionID identifier, const char * name, unsigned int imageCount, ImageCollectionEntry * images) { ImageCollection * imageCollection = malloc(sizeof(*imageCollection)); imageCollection->identifier = identifier; imageCollection->name = strdup_nullSafe(name); imageCollection->imageCount = imageCount; imageCollection->images = NULL; if (imageCount > 0) { imageCollection->images = malloc(imageCount * sizeof(*imageCollection->images)); for (unsigned int imageIndex = 0; imageIndex < imageCount; imageIndex++) { imageCollection->images[imageIndex] = images[imageIndex]; imageCollection->images[imageIndex].name = strdup(images[imageIndex].name); for (unsigned int sectionIndex = 0; sectionIndex < IMAGE_COLLECTION_SECTION_COUNT_MAX; sectionIndex++) { if (images[imageIndex].image[sectionIndex] == NULL) { imageCollection->images[imageIndex].image[sectionIndex] = NULL; } else { imageCollection->images[imageIndex].image[sectionIndex] = BitmapImage_copy(images[imageIndex].image[sectionIndex]); } } } } return imageCollection; } ImageCollection * ImageCollection_copy(ImageCollection * imageCollection) { return ImageCollection_create(imageCollection->identifier, imageCollection->name, imageCollection->imageCount, imageCollection->images); } void ImageCollection_dispose(ImageCollection * imageCollection) { free(imageCollection->name); for (unsigned int imageIndex = 0; imageIndex < imageCollection->imageCount; imageIndex++) { free(imageCollection->images[imageIndex].name); for (unsigned int sectionIndex = 0; sectionIndex < IMAGE_COLLECTION_SECTION_COUNT_MAX; sectionIndex++) { if (imageCollection->images[imageIndex].image[sectionIndex] != NULL) { BitmapImage_dispose(imageCollection->images[imageIndex].image[sectionIndex]); } } } free(imageCollection->images); free(imageCollection); } ImageCollection * ImageCollection_deserializeMinimal(compat_type(DeserializationContext *) deserializationContext, uint16_t formatVersion) { DeserializationContext * context = deserializationContext; if (formatVersion > IMAGE_COLLECTION_FORMAT_VERSION) { return NULL; } ImageCollectionID identifier = call_virtual(readUInt32, context, "id"); const char * name = call_virtual(readString, context, "name"); unsigned int imageCount = call_virtual(beginArray, context, "images"); if (imageCount > IMAGE_COLLECTION_ENTRY_COUNT_MAX) { return NULL; } ImageCollectionEntry images[imageCount]; for (unsigned int imageIndex = 0; imageIndex < imageCount; imageIndex++) { call_virtual(beginStructure, context, NULL); images[imageIndex].identifier = call_virtual(readUInt32, context, "id"); images[imageIndex].name = (char *) call_virtual(readString, context, "name"); for (unsigned int sectionIndex = 0; sectionIndex < IMAGE_COLLECTION_SECTION_COUNT_MAX; sectionIndex++) { images[imageIndex].image[sectionIndex] = NULL; images[imageIndex].imageDirtyValue[sectionIndex] = 0; } images[imageIndex].atlasEntry = RECT4f_EMPTY; call_virtual(endStructure, context); } call_virtual(endArray, context); if (context->status != SERIALIZATION_ERROR_OK) { return NULL; } return ImageCollection_create(identifier, name, imageCount, images); } ImageCollection * ImageCollection_deserialize(compat_type(DeserializationContext *) deserializationContext) { deserialize_implementation_v2(ImageCollection, IMAGE_COLLECTION); } void ImageCollection_serializeMinimal(ImageCollection * imageCollection, compat_type(SerializationContext *) serializationContext) { SerializationContext * context = serializationContext; call_virtual(writeUInt32, context, "id", imageCollection->identifier); call_virtual(writeString, context, "name", imageCollection->name); call_virtual(beginArray, context, "images"); for (unsigned int imageIndex = 0; imageIndex < imageCollection->imageCount; imageIndex++) { call_virtual(beginStructure, context, NULL); call_virtual(writeUInt32, context, "id", imageCollection->images[imageIndex].identifier); call_virtual(writeString, context, "name", imageCollection->images[imageIndex].name); call_virtual(endStructure, context); } call_virtual(endArray, context); } void ImageCollection_serialize(ImageCollection * imageCollection, compat_type(SerializationContext *) serializationContext) { serialize_implementation_v2(ImageCollection, imageCollection, IMAGE_COLLECTION); } void ImageCollection_resolveAtlasEntries(ImageCollection * imageCollection, TextureAtlas * textureAtlas) { for (unsigned int imageIndex = 0; imageIndex < imageCollection->imageCount; imageIndex++) { Rect4f atlasEntry = TextureAtlas_lookup(textureAtlas, HashTable_stringKey(imageCollection->images[imageIndex].name)); Vector2f dimensions = TextureAtlas_getEntryPixelDimensions(textureAtlas, atlasEntry); imageCollection->images[imageIndex].atlasEntry = atlasEntry; imageCollection->images[imageIndex].size = VECTOR2u(roundpositivef(dimensions.x), roundpositivef(dimensions.y)); } } void ImageCollection_unpackImageEntries(ImageCollection * imageCollection, BitmapImage * atlasImage, unsigned int sectionIndex) { for (unsigned int imageIndex = 0; imageIndex < imageCollection->imageCount; imageIndex++) { if (imageCollection->images[imageIndex].image[sectionIndex] != NULL) { BitmapImage_dispose(imageCollection->images[imageIndex].image[sectionIndex]); } imageCollection->images[imageIndex].image[sectionIndex] = TexturePacker_extractEntryImage(imageCollection->images[imageIndex].atlasEntry, atlasImage, true, false, true, true); } } ImageID ImageCollection_getUnusedImageID(ImageCollection * imageCollection, ImageID afterImageID) { if (afterImageID == UINT32_MAX) { ImageID imageIDMax = 0; for (unsigned int imageIndex = 0; imageIndex < imageCollection->imageCount; imageIndex++) { if (imageCollection->images[imageIndex].identifier > imageIDMax) { imageIDMax = imageCollection->images[imageIndex].identifier; } } return imageIDMax + 1; } ImageID closestImageID = afterImageID + 1; bool occupied; do { occupied = false; for (unsigned int imageIndex = 0; imageIndex < imageCollection->imageCount; imageIndex++) { if (imageCollection->images[imageIndex].identifier == closestImageID) { occupied = true; closestImageID++; } } } while (occupied); return closestImageID; } ImageCollectionEntry * ImageCollection_getImage(ImageCollection * imageCollection, ImageID imageID) { for (unsigned int imageIndex = 0; imageIndex < imageCollection->imageCount; imageIndex++) { if (imageCollection->images[imageIndex].identifier == imageID) { return &imageCollection->images[imageIndex]; } } return NULL; } unsigned int ImageCollection_getImageIndex(ImageCollection * imageCollection, ImageID imageID) { for (unsigned int imageIndex = 0; imageIndex < imageCollection->imageCount; imageIndex++) { if (imageCollection->images[imageIndex].identifier == imageID) { return imageIndex; } } return UINT_MAX; } void ImageCollection_addImage(ImageCollection * imageCollection, ImageCollectionEntry imageEntry) { ImageCollection_insertImage(imageCollection, imageCollection->imageCount, imageEntry); } void ImageCollection_insertImage(ImageCollection * imageCollection, unsigned int imageIndex, ImageCollectionEntry imageEntry) { assert(imageIndex <= imageCollection->imageCount); imageCollection->images = realloc(imageCollection->images, (imageCollection->imageCount + 1) * sizeof(*imageCollection->images)); for (unsigned int imageIndex2 = imageCollection->imageCount; imageIndex2 > imageIndex; imageIndex2--) { imageCollection->images[imageIndex2] = imageCollection->images[imageIndex2 - 1]; } imageCollection->images[imageIndex] = imageEntry; imageCollection->imageCount++; } void ImageCollection_removeImageAtIndex(ImageCollection * imageCollection, unsigned int imageIndex) { assert(imageIndex < imageCollection->imageCount); free(imageCollection->images[imageIndex].name); for (unsigned int sectionIndex = 0; sectionIndex < IMAGE_COLLECTION_SECTION_COUNT_MAX; sectionIndex++) { if (imageCollection->images[imageIndex].image[sectionIndex] != NULL) { BitmapImage_dispose(imageCollection->images[imageIndex].image[sectionIndex]); } } imageCollection->imageCount--; for (; imageIndex < imageCollection->imageCount; imageIndex++) { imageCollection->images[imageIndex] = imageCollection->images[imageIndex + 1]; } } void ImageCollection_reorderImage(ImageCollection * imageCollection, unsigned int fromIndex, unsigned int toIndex) { assert(fromIndex < imageCollection->imageCount); assert(toIndex < imageCollection->imageCount); ImageCollectionEntry swap = imageCollection->images[fromIndex]; int direction = (toIndex > fromIndex) * 2 - 1; for (unsigned int swapIndex = fromIndex; swapIndex != toIndex; swapIndex += direction) { imageCollection->images[swapIndex] = imageCollection->images[swapIndex + direction]; } imageCollection->images[toIndex] = swap; }