diff --git a/gallery/480x320.png b/gallery/480x320.png new file mode 100644 index 0000000..d3f3187 Binary files /dev/null and b/gallery/480x320.png differ diff --git a/gallery/640x480.png b/gallery/640x480.png new file mode 100644 index 0000000..3a4cc2d Binary files /dev/null and b/gallery/640x480.png differ diff --git a/gallery/768x480.png b/gallery/768x480.png new file mode 100644 index 0000000..422bd4f Binary files /dev/null and b/gallery/768x480.png differ diff --git a/gallery/800x600.png b/gallery/800x600.png new file mode 100644 index 0000000..e74aeee Binary files /dev/null and b/gallery/800x600.png differ diff --git a/gallery/Makefile b/gallery/Makefile new file mode 100644 index 0000000..44adba6 --- /dev/null +++ b/gallery/Makefile @@ -0,0 +1,13 @@ +all: test + +gallery.o: gallery.c + $(CC) -Wall -c -o $@ $< $(CFLAGS) + +test: gallery.o test.c + $(CC) -Wall -o $@ $^ $(CFLAGS) + +tidy: + rm -f *.o + +clean: tidy + rm -f test diff --git a/gallery/gallery.c b/gallery/gallery.c new file mode 100644 index 0000000..87abfe7 --- /dev/null +++ b/gallery/gallery.c @@ -0,0 +1,206 @@ +// vim: set ts=8 sw=8 noet tw=80: + +/** + * gallery.c - Claudio Maggioni + * + * External sources of information used: + * - man section 3 for the several library functions; + * - libpng.org documentation of PNG header an IHDR; + * - https://codereview.stackexchange.com/q/149751 for inspiration of ntohl + * function implementation. + */ + +#include "gallery.h" +#include +#include +#include +#include + +struct image { + uint32_t width; + uint32_t height; + char * name; +}; + +struct image_ll { + struct image_ll* prev; + struct image first; + struct image_ll* next; +}; + +struct gallery { + size_t size; + struct image_ll* images; +}; + +static struct image_ll* find_by_name(struct gallery*, const char*); +static uint32_t ntohl (uint32_t); +static struct image_ll* ll_remove(struct gallery*, struct image_ll*); + +const size_t INIT_CAP = 128; +const uint8_t HEADER[8] = { '\211', 'P', 'N', 'G', '\r', '\n', '\032', '\n' }; + +struct image_ll* find_by_name(struct gallery* g, const char* name) { + for (struct image_ll* i = g->images; i; i = i->next) { + if (i->first.name == name || !strcmp(i->first.name, name)) { + return i; + } + } + + return NULL; +} + +struct image_ll* ll_remove(struct gallery* g, struct image_ll* i) { + if (i->next) { + i->next->prev = i->prev; + } + + if (i->prev) { + i->prev->next = i->next; + } + + struct image_ll* n = i->next; + free(i); + g->size--; + + if (g->size == 0) { + g->images = NULL; + } + return n; +} + +// inspired by https://codereview.stackexchange.com/q/149751 +uint32_t ntohl (const uint32_t nlong) { + uint8_t* data = (uint8_t*) &nlong; + + return ((uint32_t) data[3]) | + ((uint32_t) data[2] << 8) | + ((uint32_t) data[1] << 16) | + ((uint32_t) data[0] << 24); +} + +struct gallery *gallery_new() { + struct gallery* g = malloc(sizeof(struct gallery)); + + if (!g) { + return NULL; + } + + g->images = NULL; + g->size = 0; + return g; +} + +void gallery_destroy(struct gallery *g) { + while (g->images) { + struct image_ll* i = g->images; + g->images = g->images->next; + free(i); + } + free(g); +} + +/* + * remark: gallery_add(...) returns undocumented error code -3 when the malloc + * for the new image data struct fails + */ +int gallery_add(struct gallery *g, char* filename) { + if (find_by_name(g, filename)) { + return -1; + } + + FILE* f = fopen(filename, "r"); + + if (!f) { + return -2; + } + + uint8_t file_head[8]; + if (fread(&file_head, 1, 8, f) != 8) { + fclose(f); + return -2; + } + + if (memcmp(file_head, HEADER, sizeof(HEADER))) { + return -2; + } + + if (fseek(f, 8, SEEK_CUR) == -1) { + perror("fseek failed"); + fclose(f); + return -2; + } + + struct image_ll* new = malloc(sizeof(struct image_ll)); + if (!new) { + fclose(f); + return -3; + } + + if (fread(&new->first, 8, 1, f) != 1) { + free(new); + fclose(f); + return -2; + } + + new->first.width = ntohl(new->first.width); + new->first.height = ntohl(new->first.height); + new->first.name = filename; + new->prev = NULL; + new->next = g->images; + + if (g->images) { + g->images->prev = new; + } + + g->images = new; + g->size++; + + return 1; +} + +int gallery_rm(struct gallery* g, char* filename) { + struct image_ll* i = find_by_name(g, filename); + + if (!i) { + return 0; + } + + ll_remove(g, i); + return 1; +} + +int gallery_count(struct gallery* g) { + return g->size; +} + +int gallery_filter(struct gallery *g, int (*f)(char *name, int w, int h)) { + for (struct image_ll* i = g->images; i;) { + if (!f(i->first.name, i->first.width, i->first.height)) { + i = ll_remove(g, i); + } else { + i = i->next; + } + } + + return g->size; +} + +char* gallery_bestfit(struct gallery* g, int max_width, int max_height) { + struct image* best = NULL; + + for (struct image_ll* i = g->images; i; i = i->next) { + uint32_t w = i->first.width; + uint32_t h = i->first.height; + if (w > max_width || h > max_height) { + continue; + } + + if (!best || w * h > best->width * best->height) { + best = &i->first; + } + } + + return best ? best->name : NULL; +} + diff --git a/gallery/gallery.h b/gallery/gallery.h new file mode 100644 index 0000000..3c0c469 --- /dev/null +++ b/gallery/gallery.h @@ -0,0 +1,43 @@ +#ifndef __GALLERY_H__ +#define __GALLERY_H__ + +struct gallery; + +/* Constructor: allocates memory and returns a pointer to a new gallery. + */ +struct gallery *gallery_new(); + +/* Destructor: frees all the memory allocated to the gallery. + */ +void gallery_destroy(struct gallery *g); + +/* Adds a PNG by filename to the gallery. If the filename is already in the + * gallery, returns -1; if the file does not exist, or if it is not a valid PNG + * format, returns -2; otherwise, returns 1 on success. + */ +int gallery_add(struct gallery *g, char *filename); + +/* Removes a photo from the gallery. Returns 1 on successful removal, otherwise + * 0 if the photo is not in the gallery. + */ +int gallery_rm(struct gallery *g, char *filename); + +/* Returns the number of photos in the gallery. + */ +int gallery_count(struct gallery *g); + +/* Removes photos from the gallery that do not match a predicate function. The + * predicate is passed the filename, width and height of each photo. The + * predicate should return 1 to keep the photo, and 0 to remove the photo. The + * function returns the number of photos present after filtering. + */ +int gallery_filter(struct gallery *g, + int (*f)(char *filename, int width, int height)); + +/* Returns the filename of the photo that is the best fit for the dimensions + * width*height. If there are multiple best fits, any may be returned. If there + * are no photos that fit, returns NULL. + */ +char *gallery_bestfit(struct gallery *g, int max_width, int max_height); + +#endif // __GALLERY_H__ diff --git a/gallery/test.c b/gallery/test.c new file mode 100644 index 0000000..e1145f1 --- /dev/null +++ b/gallery/test.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +#include "gallery.h" + +int main() { + struct gallery *g = gallery_new(); + + assert(gallery_add(g, "480x320.png") == 1); + assert(gallery_count(g) == 1); + + assert(gallery_add(g, "640x480.png") == 1); + assert(gallery_count(g) == 2); + + assert(gallery_add(g, "480x320.png") == -1); // already exists + assert(gallery_count(g) == 2); + + assert(gallery_add(g, "test.c") == -2); // invalid PNG format + assert(gallery_count(g) == 2); + + assert(strcmp(gallery_bestfit(g, 640, 480), "640x480.png") == 0); + assert(strcmp(gallery_bestfit(g, 640, 481), "640x480.png") == 0); + assert(strcmp(gallery_bestfit(g, 640, 479), "480x320.png") == 0); + assert(gallery_bestfit(g, 0, 0) == NULL); + + int myfilter(char *fn, int w, int h) { return w < 640; } + assert(gallery_filter(g, myfilter) == 1); + assert(gallery_count(g) == 1); + + assert(gallery_rm(g, "480x320.png") == 1); + assert(gallery_count(g) == 0); + + gallery_destroy(g); + + return 0; +}