ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
ImageAtlas.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for SDL3 and C.
3 * Copyright (C) 2014 Jay Dolan <jay@jaydolan.com>
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 * Permission is granted to anyone to use this software for any purpose,
8 * including commercial applications, and to alter it and redistribute it
9 * freely, subject to the following restrictions:
10 * 1. The origin of this software must not be misrepresented; you must not
11 * claim that you wrote the original software. If you use this software
12 * in a product, an acknowledgment in the product documentation would be
13 * appreciated but is not required.
14 * 2. Altered source versions must be plainly marked as such, and must not be
15 * misrepresented as being the original software.
16 */
17
18
19#include <assert.h>
20#include <math.h>
21#include <stdlib.h>
22#include <string.h>
23
24#include "ImageAtlas.h"
25#include "Log.h"
26
27#define _Class _ImageAtlas
28
29#pragma mark - Object
30
34static void dealloc(Object *self) {
35
36 ImageAtlas *this = (ImageAtlas *) self;
37
38 // An AtlasImage retained elsewhere (by an ImageView, say) outlives this atlas: sever its
39 // back-pointer so that it degrades to drawing nothing rather than dereferencing freed memory
40 for (size_t i = 0; i < this->images->count; i++) {
41 AtlasImage *image = $(this->images, objectAtIndex, i);
42 image->atlas = NULL;
43 }
44
45 release(this->texture);
46 release(this->image);
47 release(this->images);
48 release(this->names);
49
50 super(Object, self, dealloc);
51}
52
53#pragma mark - ImageAtlas
54
60
61 assert(image);
62 assert(!$((Object *) image, isKindOfClass, _AtlasImage()));
63
64 AtlasImage *atlasImage = $(alloc(AtlasImage), initWithImage, self, image);
65 assert(atlasImage);
66
67 $(self->images, addObject, atlasImage);
68 release(atlasImage);
69
70 return atlasImage;
71}
72
77static AtlasImage *addImageWithResourceName(ImageAtlas *self, const char *name, float pixelDensity) {
78
79 AtlasImage *atlasImage = NULL;
80
81 Image *image = $$(Image, imageWithResourceName, name, pixelDensity);
82 if (image) {
83 atlasImage = $(self, addImage, image);
84 release(image);
85 } else {
86 MVC_LogError("Image %s not found\n", name);
87 }
88
89 return atlasImage;
90}
91
96static AtlasImage *addImageWithName(ImageAtlas *self, const char *name, Image *image) {
97
98 assert(name);
99
100 AtlasImage *atlasImage = $(self, addImage, image);
101
102 $(self->names, setObjectForKeyPath, atlasImage, name);
103 self->generation++;
104
105 return atlasImage;
106}
107
112static AtlasImage *imageWithName(const ImageAtlas *self, const char *name) {
113
114 assert(name);
115
116 return $(self->names, objectForKeyPath, name);
117}
118
122static int compareImages(const void *a, const void *b) {
123
124 const SDL_Surface *sa = (*(const AtlasImage **) a)->image.surface;
125 const SDL_Surface *sb = (*(const AtlasImage **) b)->image.surface;
126
127 if (sb->h != sa->h) {
128 return sb->h - sa->h;
129 }
130
131 return sb->w - sa->w;
132}
133
139static bool pack(AtlasImage **images, SDL_Rect *rects, size_t count, int size) {
140
141 int x = 0, y = 0, row = 0;
142
143 for (size_t i = 0; i < count; i++) {
144
145 const SDL_Surface *surface = images[i]->image.surface;
146
147 if (x + surface->w > size) {
148 x = 0;
149 y += row;
150 row = 0;
151 }
152
153 if (y + surface->h > size) {
154 return false;
155 }
156
157 rects[i] = MakeRect(x, y, surface->w, surface->h);
158
159 x += surface->w;
160 row = max(row, surface->h);
161 }
162
163 return true;
164}
165
170static bool compile(ImageAtlas *self) {
171
172 self->generation++;
173
174 const size_t count = self->images->count;
175 if (count == 0) {
176 return true;
177 }
178
179 AtlasImage **images = calloc(count, sizeof(AtlasImage *));
180 assert(images);
181
182 SDL_Rect *rects = calloc(count, sizeof(SDL_Rect));
183 assert(rects);
184
185 size_t area = 0;
186 int size = 1;
187
188 for (size_t i = 0; i < count; i++) {
189 images[i] = $(self->images, objectAtIndex, i);
190
191 const SDL_Surface *surface = images[i]->image.surface;
192 area += (size_t) surface->w * (size_t) surface->h;
193 size = max(size, max(surface->w, surface->h));
194 }
195
196 qsort(images, count, sizeof(AtlasImage *), compareImages);
197
198 size = max(size, (int) ceil(sqrt((double) area)));
199
200 int sheetSize = 1;
201 while (sheetSize < size) {
202 sheetSize <<= 1;
203 }
204
205 bool packed = false;
206 while (sheetSize <= self->maxSize) {
207 if (pack(images, rects, count, sheetSize)) {
208 packed = true;
209 break;
210 }
211 sheetSize <<= 1;
212 }
213
214 if (packed) {
215
216 for (size_t i = 0; i < count; i++) {
217 images[i]->rect = rects[i];
218 }
219
220 SDL_Surface *sheet = SDL_CreateSurface(sheetSize, sheetSize, SDL_PIXELFORMAT_RGBA32);
221 assert(sheet);
222
223 SDL_FillSurfaceRect(sheet, NULL, 0);
224
225 for (size_t i = 0; i < count; i++) {
226
227 const AtlasImage *image = images[i];
228 const SDL_Surface *surface = image->image.surface;
229 const size_t pitch = surface->w * SDL_BYTESPERPIXEL(surface->format);
230
231 for (int y = 0; y < surface->h; y++) {
232 memcpy((Uint8 *) sheet->pixels + (image->rect.y + y) * sheet->pitch + image->rect.x * SDL_BYTESPERPIXEL(sheet->format),
233 (const Uint8 *) surface->pixels + y * surface->pitch,
234 pitch);
235 }
236 }
237
238 release(self->image);
239 self->image = $$(Image, imageWithSurface, sheet);
240 assert(self->image);
241
242 SDL_DestroySurface(sheet);
243
244 self->texture = release(self->texture);
245
246 } else {
247 MVC_LogError("%zu images (%zu texels) do not fit within %dx%d\n", count, area, self->maxSize, self->maxSize);
248 }
249
250 free(rects);
251 free(images);
252
253 return packed;
254}
255
260static ImageAtlas *init(ImageAtlas *self) {
261
262 self = (ImageAtlas *) super(Object, self, init);
263 if (self) {
264
265 self->images = $$(Array, array);
266 assert(self->images);
267
268 self->names = $$(Dictionary, dictionary);
269 assert(self->names);
270
272 }
273
274 return self;
275}
276
282 self->texture = release(self->texture);
283}
284
289static Texture *texture(ImageAtlas *self, RenderDevice *device) {
290
291 assert(device);
292
293 if (self->texture == NULL && self->image) {
294 self->texture = $(device, createTextureFromSurface, self->image->surface, SDL_GPU_TEXTUREUSAGE_SAMPLER, false);
295 assert(self->texture);
296 }
297
298 return self->texture;
299}
300
301#pragma mark - Class lifecycle
302
306static void initialize(Class *clazz) {
307
308 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
309
310 ((ImageAtlasInterface *) clazz->interface)->addImage = addImage;
311 ((ImageAtlasInterface *) clazz->interface)->addImageWithName = addImageWithName;
312 ((ImageAtlasInterface *) clazz->interface)->addImageWithResourceName = addImageWithResourceName;
313 ((ImageAtlasInterface *) clazz->interface)->compile = compile;
314 ((ImageAtlasInterface *) clazz->interface)->imageWithName = imageWithName;
315 ((ImageAtlasInterface *) clazz->interface)->init = init;
316 ((ImageAtlasInterface *) clazz->interface)->renderDeviceWillReset = renderDeviceWillReset;
317 ((ImageAtlasInterface *) clazz->interface)->texture = texture;
318}
319
324Class *_ImageAtlas(void) {
325 static Class *clazz;
326 static Once once;
327
328 do_once(&once, {
329 clazz = _initialize(&(const ClassDef) {
330 .name = "ImageAtlas",
331 .superclass = _Object(),
332 .instanceSize = sizeof(ImageAtlas),
333 .interfaceSize = sizeof(ImageAtlasInterface),
334 .initialize = initialize,
335 });
336 });
337
338 return clazz;
339}
340
341#undef _Class
static AtlasImage * initWithImage(AtlasImage *self, ImageAtlas *atlas, Image *image)
Definition AtlasImage.c:32
Class * _AtlasImage(void)
Definition AtlasImage.c:84
static String * name(const Font *self)
Definition Font.c:303
static Image * imageWithSurface(SDL_Surface *surface)
Definition Image.c:82
static Image * imageWithResourceName(const char *name, float pixelDensity)
Definition Image.c:74
static SDL_Size size(const Image *self)
Definition Image.c:416
static AtlasImage * addImageWithName(ImageAtlas *self, const char *name, Image *image)
Definition ImageAtlas.c:96
static AtlasImage * imageWithName(const ImageAtlas *self, const char *name)
Definition ImageAtlas.c:112
static void renderDeviceWillReset(ImageAtlas *self)
Definition ImageAtlas.c:281
static ImageAtlas * init(ImageAtlas *self)
Definition ImageAtlas.c:260
static bool compile(ImageAtlas *self)
Definition ImageAtlas.c:170
static AtlasImage * addImageWithResourceName(ImageAtlas *self, const char *name, float pixelDensity)
Definition ImageAtlas.c:77
Class * _ImageAtlas(void)
Definition ImageAtlas.c:324
static Texture * texture(ImageAtlas *self, RenderDevice *device)
Definition ImageAtlas.c:289
static int compareImages(const void *a, const void *b)
Sorts AtlasImages tallest first, then widest, so that shelf rows waste the least.
Definition ImageAtlas.c:122
static void dealloc(Object *self)
Definition ImageAtlas.c:34
static AtlasImage * addImage(ImageAtlas *self, Image *image)
Definition ImageAtlas.c:59
static void initialize(Class *clazz)
Definition ImageAtlas.c:306
static bool pack(AtlasImage **images, SDL_Rect *rects, size_t count, int size)
Lays out the sorted images in shelves on a square of the given size, writing their rects to rects rat...
Definition ImageAtlas.c:139
Packs many Images into one sheet, backed by one Texture.
#define IMAGE_ATLAS_MAX_SIZE
Definition ImageAtlas.h:33
View logging facilities via SDL_Log.
#define MVC_LogError(fmt,...)
Definition Log.h:57
static Image * image(Theme *self, const char *name, float pixelDensity)
Definition Theme.c:82
An Image packed into an ImageAtlas.
Definition AtlasImage.h:41
SDL_Rect rect
This AtlasImage's rectangle within the atlas sheet, in texels.
Definition AtlasImage.h:66
Image image
The superclass.
Definition AtlasImage.h:46
Packs many Images into one sheet, backed by one Texture.
Definition ImageAtlas.h:49
int maxSize
The largest sheet dimension ImageAtlas::compile will attempt.
Definition ImageAtlas.h:90
void renderDeviceWillReset(ImageAtlas *self)
Releases this atlas' Texture ahead of a RenderDevice reset.
Definition ImageAtlas.c:281
Image * image
The compiled sheet, or NULL until ImageAtlas::compile.
Definition ImageAtlas.h:71
Array * images
The AtlasImages, in the order added.
Definition ImageAtlas.h:76
unsigned generation
Incremented by every ImageAtlas::addImageWithName and ImageAtlas::compile, so that anything derived f...
Definition ImageAtlas.h:66
Image loading.
Definition Image.h:41
SDL_Surface * surface
The backing surface.
Definition Image.h:62