ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
ImageAtlas Struct Reference

Packs many Images into one sheet, backed by one Texture. More...

#include <ImageAtlas.h>

Inheritance diagram for ImageAtlas:

Public Member Functions

Class * _ImageAtlas (void)
 
AtlasImage * addImage (ImageAtlas *self, Image *image)
 Adds the given Image to this atlas.
 
AtlasImage * addImageWithName (ImageAtlas *self, const char *name, Image *image)
 Adds the given Image to this atlas, registered under name for ImageAtlas::imageWithName.
 
AtlasImage * addImageWithResourceName (ImageAtlas *self, const char *name, float pixelDensity)
 Loads the Image Resource by the given name and adds it to this atlas.
 
bool compile (ImageAtlas *self)
 Packs every added Image into a new sheet.
 
AtlasImage * imageWithName (const ImageAtlas *self, const char *name)
 
ImageAtlas * init (ImageAtlas *self)
 Initializes this ImageAtlas.
 
void renderDeviceWillReset (ImageAtlas *self)
 Releases this atlas' Texture ahead of a RenderDevice reset.
 
Texture * texture (ImageAtlas *self, RenderDevice *device)
 Resolves the sheet's Texture, creating it on the given device if necessary.
 

Data Fields

unsigned generation
 Incremented by every ImageAtlas::addImageWithName and ImageAtlas::compile, so that anything derived from this atlas' contents or layout can tell when it is stale.
 
Image * image
 The compiled sheet, or NULL until ImageAtlas::compile.
 
Array * images
 The AtlasImages, in the order added.
 
int maxSize
 The largest sheet dimension ImageAtlas::compile will attempt.
 
Object object
 The superclass.
 

Protected Attributes

ImageAtlasInterface * interface [0]
 The interface type.
 

Detailed Description

Packs many Images into one sheet, backed by one Texture.

The Renderer merges consecutive draws that share a Texture into one draw call, so a UI whose images all come from one atlas draws in a handful of calls rather than one per View. Add Images, then compile: every AtlasImage learns where its pixels landed, and the sheet's Texture is created on first request and shared by every View that draws from it.

Packing is by shelves: images are sorted by height, then laid out left to right in rows, onto the smallest power-of-two square that holds them, up to maxSize. There is no padding: the Renderer samples without mipmaps, so there are no mip levels to bleed across.

Definition at line 49 of file ImageAtlas.h.

Member 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 AtlasImage.c:74
Packs many Images into one sheet, backed by one Texture.
Definition ImageAtlas.h:49

◆ addImage()

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

Adds the given Image to this atlas.

Parameters
selfThe ImageAtlas.
imageThe Image. Its surface is shared with the returned AtlasImage.
Returns
The AtlasImage, owned by this atlas. Its rect is not valid until the next ImageAtlas::compile.

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
An Image packed into an ImageAtlas.
Definition AtlasImage.h:41
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

◆ addImageWithName()

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

Adds the given Image to this atlas, registered under name for ImageAtlas::imageWithName.

Names are unique: registering a name again replaces the mapping, while the previously registered AtlasImage remains in the atlas.

Parameters
selfThe ImageAtlas.
nameThe name.
imageThe Image. Its surface is shared with the returned AtlasImage.
Returns
The AtlasImage, owned by this atlas. Its rect is not valid until the next ImageAtlas::compile.

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
AtlasImage * addImage(ImageAtlas *self, Image *image)
Adds the given Image to this atlas.
Definition ImageAtlas.c:59
unsigned generation
Incremented by every ImageAtlas::addImageWithName and ImageAtlas::compile, so that anything derived f...
Definition ImageAtlas.h:66

◆ addImageWithResourceName()

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

Loads the Image Resource by the given name and adds it to this atlas.

Parameters
selfThe ImageAtlas.
nameThe Resource name.
Returns
The AtlasImage, owned by this atlas, or NULL if the Resource was not found.

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

◆ compile()

bool compile ( ImageAtlas *  self)

Packs every added Image into a new sheet.

Every AtlasImage's rect is reassigned, and the previous sheet and Texture are released, so a View drawing from this atlas MUST read rect and ImageAtlas::texture at draw time rather than caching either. Compiling repacks everything from scratch, so add all Images first and compile once.

Parameters
selfThe ImageAtlas.
Returns
true if every Image fit within maxSize, false otherwise.

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
int maxSize
The largest sheet dimension ImageAtlas::compile will attempt.
Definition ImageAtlas.h:90
SDL_Surface * surface
The backing surface.
Definition Image.h:62

◆ imageWithName()

AtlasImage * imageWithName ( const ImageAtlas *  self,
const char *  name 
)
Parameters
selfThe ImageAtlas.
nameThe name.
Returns
The AtlasImage registered under name via ImageAtlas::addImageWithName, or NULL.

Definition at line 112 of file ImageAtlas.c.

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

◆ init()

ImageAtlas * init ( ImageAtlas *  self)

Initializes this ImageAtlas.

Parameters
selfThe ImageAtlas.
Returns
The initialized ImageAtlas, or NULL on error.

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

◆ renderDeviceWillReset()

void renderDeviceWillReset ( ImageAtlas *  self)

Releases this atlas' Texture ahead of a RenderDevice reset.

An ImageAtlas is not a View, so nothing calls this on its behalf: every View that draws from an atlas MUST forward its own View::renderDeviceWillReset here. The call is idempotent, so shared atlases are fine.

Parameters
selfThe ImageAtlas.

Definition at line 281 of file ImageAtlas.c.

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

◆ texture()

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

Resolves the sheet's Texture, creating it on the given device if necessary.

Parameters
selfThe ImageAtlas.
deviceThe RenderDevice.
Returns
The Texture, owned by this atlas, or NULL if the atlas has not been compiled.

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}

Field Documentation

◆ generation

unsigned ImageAtlas::generation

Incremented by every ImageAtlas::addImageWithName and ImageAtlas::compile, so that anything derived from this atlas' contents or layout can tell when it is stale.

Definition at line 66 of file ImageAtlas.h.

◆ image

Image* ImageAtlas::image

The compiled sheet, or NULL until ImageAtlas::compile.

Definition at line 71 of file ImageAtlas.h.

◆ images

Array* ImageAtlas::images

The AtlasImages, in the order added.

Definition at line 76 of file ImageAtlas.h.

◆ interface

ImageAtlasInterface* ImageAtlas::interface[0]
protected

The interface type.

Definition at line 60 of file ImageAtlas.h.

◆ maxSize

int ImageAtlas::maxSize

The largest sheet dimension ImageAtlas::compile will attempt.

Sheets are power-of-two squares, so this SHOULD be a power of two; a value that is not rounds down to one. Defaults to IMAGE_ATLAS_MAX_SIZE, which every SDL_GPU backend supports.

Definition at line 90 of file ImageAtlas.h.

◆ object

Object ImageAtlas::object

The superclass.

Definition at line 54 of file ImageAtlas.h.


The documentation for this struct was generated from the following files: