From fecb6291c309392ddbe6a6ef1e7fe23eae7551b6 Mon Sep 17 00:00:00 2001 From: Nico B Date: Sat, 22 Jun 2024 19:26:30 +0100 Subject: [PATCH] [RFC] Downsample when decoding When used in very constrained systems (eg raspberry pi's) the memory pressure from Swayimg can be quite significant. It's very easy to bring down a platform with 512mb of RAM just by loading a few large pictures and keeping them in memory. Implementing this change would enable loaders to reduce image size, so that the actual pixels stored in memory are not more than those that will be rendered to screen. In my system, using not-very-large images (12mp) I see a reduction in memory usage of about 1/3, and faster render speeds. --- src/formats/jpeg.c | 18 +++++++++++++++++- src/formats/loader.c | 11 ++++++----- src/formats/loader.h | 13 ++++++++++--- src/image.c | 16 +++++++++++----- src/image.h | 9 ++++++++- src/imagelist.c | 12 ++++++++---- 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/formats/jpeg.c b/src/formats/jpeg.c index 561dff7..d4ff080 100644 --- a/src/formats/jpeg.c +++ b/src/formats/jpeg.c @@ -35,7 +35,7 @@ static void jpg_error_exit(j_common_ptr jpg) // JPEG loader implementation enum loader_status decode_jpeg(struct image* ctx, const uint8_t* data, - size_t size) + size_t size, size_t max_w, size_t max_h) { struct pixmap* pm; struct jpeg_decompress_struct jpg; @@ -59,6 +59,22 @@ enum loader_status decode_jpeg(struct image* ctx, const uint8_t* data, jpeg_create_decompress(&jpg); jpeg_mem_src(&jpg, data, size); jpeg_read_header(&jpg, TRUE); + + if (max_w > 0 && max_h > 0) { + // Find out the biggest scaler for our target resolution + while (max_w < jpg.image_width / jpg.scale_denom || + max_h < jpg.image_height / jpg.scale_denom) { + jpg.scale_denom++; + } + if (jpg.scale_denom > 1) { + jpg.scale_denom--; + } + + printf("XXXX SCALE %du = %du X %du\n", jpg.scale_denom, + jpg.image_width / jpg.scale_denom, + jpg.image_height / jpg.scale_denom); + } + jpeg_start_decompress(&jpg); #ifdef LIBJPEG_TURBO_VERSION jpg.out_color_space = JCS_EXT_BGRA; diff --git a/src/formats/loader.c b/src/formats/loader.c index 8940a84..c7dbf07 100644 --- a/src/formats/loader.c +++ b/src/formats/loader.c @@ -12,9 +12,10 @@ // Construct function name of loader #define LOADER_FUNCTION(name) decode_##name // Declaration of loader function -#define LOADER_DECLARE(name) \ - enum loader_status LOADER_FUNCTION(name)(struct image * ctx, \ - const uint8_t* data, size_t size) +#define LOADER_DECLARE(name) \ + enum loader_status LOADER_FUNCTION(name)(struct image * ctx, \ + const uint8_t* data, size_t size, \ + size_t max_w, size_t max_h) const char* supported_formats = "bmp, pnm, tga" #ifdef HAVE_LIBJPEG @@ -124,12 +125,12 @@ static const image_decoder decoders[] = { }; enum loader_status load_image(struct image* ctx, const uint8_t* data, - size_t size) + size_t size, size_t max_w, size_t max_h) { enum loader_status status = ldr_unsupported; for (size_t i = 0; i < sizeof(decoders) / sizeof(decoders[0]); ++i) { - switch (decoders[i](ctx, data, size)) { + switch (decoders[i](ctx, data, size, max_w, max_h)) { case ldr_success: return ldr_success; case ldr_unsupported: diff --git a/src/formats/loader.h b/src/formats/loader.h index 0ef0acd..b69bfce 100644 --- a/src/formats/loader.h +++ b/src/formats/loader.h @@ -24,17 +24,24 @@ extern const char* supported_formats; * @return loader status */ typedef enum loader_status (*image_decoder)(struct image* ctx, - const uint8_t* data, size_t size); + const uint8_t* data, size_t size, + size_t max_w, size_t max_h); /** - * Load image from memory buffer. + * Load image from memory buffer. If max_w and max_h are specified (both non + * zero), the loader will attempt to decode the image to the closest resolution + * that's at least as big as the specified target, respecting the aspect ratio + * of the source image. This is only done if the decoder supports downsampling + * on decode (eg jpg does) * @param ctx image context * @param data raw image data * @param size size of image data in bytes + * @param max_w Maximum render width for this image + * @param max_h Maximum render height for this image * @return loader status */ enum loader_status load_image(struct image* ctx, const uint8_t* data, - size_t size); + size_t size, size_t max_w, size_t max_h); /** * Print decoding problem description. diff --git a/src/image.c b/src/image.c index 19b4cc1..d250911 100644 --- a/src/image.c +++ b/src/image.c @@ -26,7 +26,8 @@ * @return image instance or NULL on errors */ static struct image* image_create(const char* path, const uint8_t* data, - size_t size) + size_t size, + struct target_resolution* tgt_res) { struct image* ctx; enum loader_status status; @@ -48,7 +49,11 @@ static struct image* image_create(const char* path, const uint8_t* data, ctx->file_size = size; // decode image - status = load_image(ctx, data, size); + if (tgt_res) { + status = load_image(ctx, data, size, tgt_res->w, tgt_res->h); + } else { + status = load_image(ctx, data, size, 0, 0); + } if (status != ldr_success) { if (status == ldr_unsupported) { image_print_error(ctx, "unsupported format"); @@ -64,7 +69,8 @@ static struct image* image_create(const char* path, const uint8_t* data, return ctx; } -struct image* image_from_file(const char* file) +struct image* image_from_file(const char* file, + struct target_resolution* tgt_res) { struct image* ctx = NULL; void* data = MAP_FAILED; @@ -89,7 +95,7 @@ struct image* image_from_file(const char* file) goto done; } - ctx = image_create(file, data, st.st_size); + ctx = image_create(file, data, st.st_size, tgt_res); done: if (data != MAP_FAILED) { @@ -132,7 +138,7 @@ struct image* image_from_stdin(void) } if (data) { - ctx = image_create(STDIN_FILE_NAME, data, size); + ctx = image_create(STDIN_FILE_NAME, data, size, NULL); } done: diff --git a/src/image.h b/src/image.h index 4d36880..9fd7da9 100644 --- a/src/image.h +++ b/src/image.h @@ -34,15 +34,22 @@ struct image_info { char* value; ///< Meta value }; +struct target_resolution { + size_t w; ///< Maximum width this image will render to (eg screen width) + size_t h; ///< Maximum height this image will render to (eg screen height) +}; + /** Name used for image, that is read from stdin through pipe. */ #define STDIN_FILE_NAME "{STDIN}" /** * Load image from file. * @param file path to the file to load + * @param target (maximum) resolution for this image. NULL for no maximum. * @return image context or NULL on errors */ -struct image* image_from_file(const char* file); +struct image* image_from_file(const char* file, + struct target_resolution* tgt_res); /** * Load image from stdin data. diff --git a/src/imagelist.c b/src/imagelist.c index a6d302e..f3966eb 100644 --- a/src/imagelist.c +++ b/src/imagelist.c @@ -329,7 +329,8 @@ static void* preloader_thread(__attribute__((unused)) void* data) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); - img = image_from_file(ctx.entries[index]->path); + struct target_resolution tgt_res = { .w = 1920, .h = 1080 }; + img = image_from_file(ctx.entries[index]->path, &tgt_res); if (img) { image_free(ctx.next); ctx.next = img; @@ -537,7 +538,8 @@ bool image_list_scan(const char** files, size_t num) if (strcmp(ctx.entries[ctx.index]->path, STDIN_FILE_NAME) == 0) { ctx.current = image_from_stdin(); } else { - ctx.current = image_from_file(ctx.entries[ctx.index]->path); + struct target_resolution tgt_res = { .w = 1920, .h = 1080 }; + ctx.current = image_from_file(ctx.entries[ctx.index]->path, &tgt_res); } if (!ctx.current && ((force_start && num == 1) || !image_list_jump(jump_next_file))) { @@ -610,7 +612,8 @@ bool image_list_reset(void) // reload current image image_free(ctx.current); - ctx.current = image_from_file(ctx.entries[ctx.index]->path); + struct target_resolution tgt_res = { .w = 1920, .h = 1080 }; + ctx.current = image_from_file(ctx.entries[ctx.index]->path, &tgt_res); if (ctx.current) { preloader_ctl(true); return true; @@ -661,7 +664,8 @@ bool image_list_jump(enum list_jump jump) image = ctx.prev; ctx.prev = NULL; } else { - image = image_from_file(ctx.entries[index]->path); + struct target_resolution tgt_res = { .w = 1920, .h = 1080 }; + image = image_from_file(ctx.entries[index]->path, &tgt_res); if (!image) { // not an image, remove entry from list free(ctx.entries[index]);