ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
ImageAtlas.c File Reference
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "ImageAtlas.h"
#include "Log.h"

Go to the source code of this file.

Macros

#define _Class   _ImageAtlas
 

Functions

Class * _ImageAtlas (void)
 
static AtlasImage * addImage (ImageAtlas *self, Image *image)
 
static AtlasImage * addImageWithName (ImageAtlas *self, const char *name, Image *image)
 
static AtlasImage * addImageWithResourceName (ImageAtlas *self, const char *name, float pixelDensity)
 
static int compareImages (const void *a, const void *b)
 Sorts AtlasImages tallest first, then widest, so that shelf rows waste the least.
 
static bool compile (ImageAtlas *self)
 
static void dealloc (Object *self)
 
static AtlasImage * imageWithName (const ImageAtlas *self, const char *name)
 
static ImageAtlas * init (ImageAtlas *self)
 
static void initialize (Class *clazz)
 
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 rather than to the images, so that a failed attempt leaves them untouched.
 
static void renderDeviceWillReset (ImageAtlas *self)
 
static Texture * texture (ImageAtlas *self, RenderDevice *device)
 

Macro Definition Documentation

◆ _Class

#define _Class   _ImageAtlas

Definition at line 27 of file ImageAtlas.c.

Function Documentation

◆ _ImageAtlas()

Class * _ImageAtlas ( void  )

Definition at line 324 of file ImageAtlas.c.

324 {
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}
static void initialize(Class *clazz)
Definition ImageAtlas.c:306
Packs many Images into one sheet, backed by one Texture.
Definition ImageAtlas.h:49

◆ addImage()

static AtlasImage * addImage ( ImageAtlas *  self,
Image *  image 
)
static

Definition at line 59 of file ImageAtlas.c.

59 {
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}
static AtlasImage * initWithImage(AtlasImage *self, ImageAtlas *atlas, Image *image)
Definition AtlasImage.c:32
Class * _AtlasImage(void)
Definition AtlasImage.c:84
static Image * image(Theme *self, const char *name, float pixelDensity)
Definition Theme.c:82
An Image packed into an ImageAtlas.
Definition AtlasImage.h:41
Array * images
The AtlasImages, in the order added.
Definition ImageAtlas.h:76

◆ addImageWithName()

static AtlasImage * addImageWithName ( ImageAtlas *  self,
const char *  name,
Image *  image 
)
static

Definition at line 96 of file ImageAtlas.c.

96 {
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}
static String * name(const Font *self)
Definition Font.c:303
static AtlasImage * addImage(ImageAtlas *self, Image *image)
Definition ImageAtlas.c:59
unsigned generation
Incremented by every ImageAtlas::addImageWithName and ImageAtlas::compile, so that anything derived f...
Definition ImageAtlas.h:66

◆ addImageWithResourceName()

static AtlasImage * addImageWithResourceName ( ImageAtlas *  self,
const char *  name,
float  pixelDensity 
)
static

Definition at line 77 of file ImageAtlas.c.

77 {
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}
static Image * imageWithResourceName(const char *name, float pixelDensity)
Definition Image.c:74
#define MVC_LogError(fmt,...)
Definition Log.h:57
Image loading.
Definition Image.h:41

◆ compareImages()

static int compareImages ( const void *  a,
const void *  b 
)
static

Sorts AtlasImages tallest first, then widest, so that shelf rows waste the least.

Definition at line 122 of file ImageAtlas.c.

122 {
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}

◆ compile()

static bool compile ( ImageAtlas *  self)
static

Definition at line 170 of file ImageAtlas.c.

170 {
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}
static Image * imageWithSurface(SDL_Surface *surface)
Definition Image.c:82
static SDL_Size size(const Image *self)
Definition Image.c:416
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 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
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
int maxSize
The largest sheet dimension ImageAtlas::compile will attempt.
Definition ImageAtlas.h:90
Image * image
The compiled sheet, or NULL until ImageAtlas::compile.
Definition ImageAtlas.h:71
SDL_Surface * surface
The backing surface.
Definition Image.h:62

◆ dealloc()

static void dealloc ( Object *  self)
static
See also
Object::dealloc(Object *)

Definition at line 34 of file ImageAtlas.c.

34 {
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}
static Texture * texture(ImageAtlas *self, RenderDevice *device)
Definition ImageAtlas.c:289
static void dealloc(Object *self)
Definition ImageAtlas.c:34

◆ imageWithName()

static AtlasImage * imageWithName ( const ImageAtlas *  self,
const char *  name 
)
static

Definition at line 112 of file ImageAtlas.c.

112 {
113
114 assert(name);
115
116 return $(self->names, objectForKeyPath, name);
117}

◆ init()

static ImageAtlas * init ( ImageAtlas *  self)
static

Definition at line 260 of file ImageAtlas.c.

260 {
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}
static ImageAtlas * init(ImageAtlas *self)
Definition ImageAtlas.c:260
#define IMAGE_ATLAS_MAX_SIZE
Definition ImageAtlas.h:33

◆ initialize()

static void initialize ( Class *  clazz)
static
See also
Class::initialize(Class *)

Definition at line 306 of file ImageAtlas.c.

306 {
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}
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 bool compile(ImageAtlas *self)
Definition ImageAtlas.c:170
static AtlasImage * addImageWithResourceName(ImageAtlas *self, const char *name, float pixelDensity)
Definition ImageAtlas.c:77
void renderDeviceWillReset(ImageAtlas *self)
Releases this atlas' Texture ahead of a RenderDevice reset.
Definition ImageAtlas.c:281

◆ pack()

static bool pack ( AtlasImage **  images,
SDL_Rect *  rects,
size_t  count,
int  size 
)
static

Lays out the sorted images in shelves on a square of the given size, writing their rects to rects rather than to the images, so that a failed attempt leaves them untouched.

Returns
true if every image fit, false otherwise.

Definition at line 139 of file ImageAtlas.c.

139 {
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}

◆ renderDeviceWillReset()

static void renderDeviceWillReset ( ImageAtlas *  self)
static

Definition at line 281 of file ImageAtlas.c.

281 {
282 self->texture = release(self->texture);
283}

◆ texture()

static Texture * texture ( ImageAtlas *  self,
RenderDevice *  device 
)
static

Definition at line 289 of file ImageAtlas.c.

289 {
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}