PNG decoder now loads image asynchronously

This commit is contained in:
Mateusz Faderewski 2023-07-26 23:01:39 +02:00
parent df6d9265a0
commit 9142bf9832
5 changed files with 194 additions and 151 deletions

View File

@ -2,13 +2,14 @@
#include <libdragon.h>
#include "boot/boot.h"
#include "actions.h"
#include "assets.h"
#include "boot/boot.h"
#include "flashcart/flashcart.h"
#include "menu_state.h"
#include "menu.h"
#include "mp3_player.h"
#include "png_decoder.h"
#include "settings.h"
#include "utils/fs.h"
#include "views/views.h"
@ -201,6 +202,8 @@ void menu_run (boot_params_t *boot_params) {
mixer_poll(audio_buffer, audio_buffer_length);
audio_write_end();
}
png_poll();
}
menu_deinit(menu);

View File

@ -4,105 +4,156 @@
#include "png_decoder.h"
png_err_t png_decode (char *path, surface_t *image, int max_width, int max_height) {
spng_ctx *ctx;
enum spng_errno err = SPNG_OK;
path_t *file_path;
typedef struct {
FILE *file;
size_t image_size;
spng_ctx *ctx;
struct spng_ihdr ihdr;
struct spng_row_info row_info;
surface_t *image;
uint8_t *row_buffer;
uint16_t *image_buffer;
image->buffer = NULL;
png_callback_t *callback;
void *callback_data;
} png_decoder_t;
if ((ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32)) == NULL) {
static png_decoder_t *decoder;
static void png_decoder_deinit (bool free_image) {
if (decoder != NULL) {
if (decoder->file != NULL) {
fclose(decoder->file);
}
if (decoder->ctx != NULL) {
spng_ctx_free(decoder->ctx);
}
if ((decoder->image != NULL) && free_image) {
surface_free(decoder->image);
free(decoder->image);
}
if (decoder->row_buffer != NULL) {
free(decoder->row_buffer);
}
free(decoder);
decoder = NULL;
}
}
png_err_t png_decode_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data) {
path_t *file_path;
size_t image_size;
if (decoder != NULL) {
return PNG_ERR_BUSY;
}
decoder = calloc(1, sizeof(png_decoder_t));
if (decoder == NULL) {
return PNG_ERR_OUT_OF_MEM;
}
if (spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE) != SPNG_OK) {
spng_ctx_free(ctx);
return PNG_ERR_INT;
}
if (spng_set_image_limits(ctx, max_width, max_height) != SPNG_OK) {
spng_ctx_free(ctx);
return PNG_ERR_INT;
}
file_path = path_init("sd:/");
path_append(file_path, path);
if ((file = fopen(path_get(file_path), "r")) == NULL) {
spng_ctx_free(ctx);
path_free(file_path);
decoder->file = fopen(path_get(file_path), "r");
path_free(file_path);
if (decoder->file == NULL) {
png_decoder_deinit(false);
return PNG_ERR_NO_FILE;
}
path_free(file_path);
if (spng_set_png_file(ctx, file) != SPNG_OK) {
spng_ctx_free(ctx);
fclose(file);
if ((decoder->ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32)) == NULL) {
png_decoder_deinit(false);
return PNG_ERR_OUT_OF_MEM;
}
if (spng_set_crc_action(decoder->ctx, SPNG_CRC_USE, SPNG_CRC_USE) != SPNG_OK) {
png_decoder_deinit(false);
return PNG_ERR_INT;
}
if (spng_decoded_image_size(ctx, SPNG_FMT_RGB8, &image_size) != SPNG_OK) {
spng_ctx_free(ctx);
fclose(file);
if (spng_set_image_limits(decoder->ctx, max_width, max_height) != SPNG_OK) {
png_decoder_deinit(false);
return PNG_ERR_INT;
}
if (spng_set_png_file(decoder->ctx, decoder->file) != SPNG_OK) {
png_decoder_deinit(false);
return PNG_ERR_INT;
}
if (spng_decoded_image_size(decoder->ctx, SPNG_FMT_RGB8, &image_size) != SPNG_OK) {
png_decoder_deinit(false);
return PNG_ERR_BAD_FILE;
}
if (spng_decode_image(ctx, NULL, image_size, SPNG_FMT_RGB8, SPNG_DECODE_PROGRESSIVE) != SPNG_OK) {
spng_ctx_free(ctx);
fclose(file);
if (spng_decode_image(decoder->ctx, NULL, image_size, SPNG_FMT_RGB8, SPNG_DECODE_PROGRESSIVE) != SPNG_OK) {
png_decoder_deinit(false);
return PNG_ERR_BAD_FILE;
}
if (spng_get_ihdr(ctx, &ihdr) != SPNG_OK) {
spng_ctx_free(ctx);
fclose(file);
if (spng_get_ihdr(decoder->ctx, &decoder->ihdr) != SPNG_OK) {
png_decoder_deinit(false);
return PNG_ERR_BAD_FILE;
}
*image = surface_alloc(FMT_RGBA16, ihdr.width, ihdr.height);
if (image->buffer == NULL) {
spng_ctx_free(ctx);
fclose(file);
decoder->image = calloc(1, sizeof(surface_t));
if (decoder->image == NULL) {
png_decoder_deinit(false);
return PNG_ERR_OUT_OF_MEM;
}
if ((row_buffer = malloc(ihdr.width * 3)) == NULL) {
spng_ctx_free(ctx);
fclose(file);
surface_free(image);
*decoder->image = surface_alloc(FMT_RGBA16, decoder->ihdr.width, decoder->ihdr.height);
if (decoder->image->buffer == NULL) {
png_decoder_deinit(true);
return PNG_ERR_OUT_OF_MEM;
}
do {
if ((err = spng_get_row_info(ctx, &row_info)) != SPNG_OK) {
break;
}
err = spng_decode_row(ctx, row_buffer, ihdr.width * 3);
if (err == SPNG_OK || err == SPNG_EOI) {
image_buffer = image->buffer + (row_info.row_num * image->stride);
for (int i = 0; i < ihdr.width * 3; i += 3) {
uint8_t r = row_buffer[i + 0] >> 3;
uint8_t g = row_buffer[i + 1] >> 3;
uint8_t b = row_buffer[i + 2] >> 3;
*image_buffer++ = (r << 11) | (g << 6) | (b << 1) | 1;
}
}
} while (err == SPNG_OK);
spng_ctx_free(ctx);
free(row_buffer);
fclose(file);
if (err != SPNG_EOI) {
surface_free(image);
return PNG_ERR_BAD_FILE;
if ((decoder->row_buffer = malloc(decoder->ihdr.width * 3)) == NULL) {
png_decoder_deinit(true);
return PNG_ERR_OUT_OF_MEM;
}
decoder->callback = callback;
decoder->callback_data = callback_data;
return PNG_OK;
}
void png_decode_abort (void) {
png_decoder_deinit(true);
}
void png_poll (void) {
if (decoder) {
enum spng_errno err;
struct spng_row_info row_info;
if ((err = spng_get_row_info(decoder->ctx, &row_info)) != SPNG_OK) {
decoder->callback(PNG_ERR_BAD_FILE, NULL, decoder->callback_data);
png_decoder_deinit(true);
return;
}
err = spng_decode_row(decoder->ctx, decoder->row_buffer, decoder->ihdr.width * 3);
if (err == SPNG_OK || err == SPNG_EOI) {
uint16_t *image_buffer = decoder->image->buffer + (row_info.row_num * decoder->image->stride);
for (int i = 0; i < decoder->ihdr.width * 3; i += 3) {
uint8_t r = decoder->row_buffer[i + 0] >> 3;
uint8_t g = decoder->row_buffer[i + 1] >> 3;
uint8_t b = decoder->row_buffer[i + 2] >> 3;
*image_buffer++ = (r << 11) | (g << 6) | (b << 1) | 1;
}
}
if (err == SPNG_EOI) {
decoder->callback(PNG_OK, decoder->image, decoder->callback_data);
png_decoder_deinit(false);
} else if (err != SPNG_OK) {
decoder->callback(PNG_ERR_BAD_FILE, NULL, decoder->callback_data);
png_decoder_deinit(true);
}
}
}

View File

@ -15,13 +15,18 @@
typedef enum {
PNG_OK,
PNG_ERR_INT,
PNG_ERR_BUSY,
PNG_ERR_OUT_OF_MEM,
PNG_ERR_NO_FILE,
PNG_ERR_BAD_FILE,
} png_err_t;
typedef void png_callback_t (png_err_t err, surface_t *decoded_image, void *callback_data);
png_err_t png_decode (char *path, surface_t *image, int max_width, int max_height);
png_err_t png_decode_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data);
void png_decode_abort (void);
void png_poll (void);
#endif

View File

@ -9,42 +9,19 @@
#include "utils/str_utils.h"
#include "views.h"
#ifndef ROM_BOXART_PATH
#define ROM_BOXART_PATH "/menu/boxart/"
#define ROM_BOXART_PATH "/menu/boxart"
#endif
static FILINFO info;
static const char *n64_rom_extensions[] = { "z64", "n64", "v64", NULL };
static rom_header_t rom_header;
static bool boxart_image_loading;
static surface_t *boxart_image;
static rspq_block_t *cached_boxart_image_dl;
/* loads a PNG image for a given ROM ID from ROM_BOXART_PATH. e.g. sd:/menu/boxart/<id>.png */
static void boxart_image_load (uint16_t id) {
char sd_boxart_path[26];
sprintf(sd_boxart_path, ROM_BOXART_PATH"%.2s.png", (char*)&(id));
boxart_image = calloc(1, sizeof(surface_t));
if (png_decode(sd_boxart_path, boxart_image, 158, 112) == PNG_OK) {
debugf("Found and decoded boxart image: %s\n", sd_boxart_path);
// FIXME: use relative layout.
uint16_t x = (640 - 150) - (boxart_image->width / 2);
uint16_t y = (480 - 150) - (boxart_image->height / 2);
rspq_block_begin();
rdpq_set_mode_copy(false);
rdpq_tex_blit(boxart_image, x, y, NULL);
cached_boxart_image_dl = rspq_block_end();
} else {
debugf("Error loading boxart image\n");
}
}
static char *format_rom_endian (uint32_t endian) {
switch (endian)
@ -291,15 +268,15 @@ static void menu_fileinfo_draw_n64_rom_info(surface_t *d, layout_t *layout) {
text_y += fragment_textf(text_x, text_y, " Save Type: %s\n", format_rom_save_type(rom_db_match_save_type(rom_header)));
text_y += fragment_textf(text_x, text_y, " Expansion PAK: %s\n\n", format_rom_memory_type(rom_db_match_expansion_pak(rom_header)));
if (boxart_image != NULL && cached_boxart_image_dl != NULL)
{
rspq_block_run(cached_boxart_image_dl);
if (boxart_image) {
uint16_t x = (640 - 150) - (boxart_image->width / 2);
uint16_t y = (480 - 150) - (boxart_image->height / 2);
rdpq_set_mode_copy(false);
rdpq_tex_blit(boxart_image, x, y, NULL);
}
}
static void draw (menu_t *menu, surface_t *d) {
layout_t *layout = layout_get();
const int text_x = layout->offset_x + layout->offset_text_x;
@ -317,15 +294,13 @@ static void draw (menu_t *menu, surface_t *d) {
// Text start
fragment_text_start(text_color);
if (file_has_extensions(info.fname, n64_rom_extensions)) {
menu_fileinfo_draw_n64_rom_info(d, layout);
}
else {
} else {
menu_fileinfo_draw_unknown_info(d, layout);
}
/* Ensure RDP mode and loaded texture dont mess up font drawing. */
// Ensure RDP mode and loaded texture dont mess up font drawing.
fragment_text_start(text_color);
// Actions bar
@ -335,16 +310,27 @@ static void draw (menu_t *menu, surface_t *d) {
rdpq_detach_show();
}
static void boxart_image_callback (png_err_t err, surface_t *decoded_image, void *callback_data) {
boxart_image_loading = false;
boxart_image = decoded_image;
}
static void dl_free (void *arg) {
if (cached_boxart_image_dl != NULL) {
rspq_block_free((rspq_block_t *) (arg));
/* loads a PNG image for a given ROM ID from ROM_BOXART_PATH. e.g. sd:/menu/boxart/<id>.png */
static void boxart_image_load (uint16_t id) {
char sd_boxart_path[32];
sprintf(sd_boxart_path, "%s/%.2s.png", ROM_BOXART_PATH, (char *) (&id));
if (png_decode_start(sd_boxart_path, 158, 112, boxart_image_callback, NULL) != PNG_OK) {
debugf("Error loading boxart image\n");
}
}
static void deinit (menu_t *menu) {
if (boxart_image != NULL) {
rdpq_call_deferred(dl_free, cached_boxart_image_dl);
if (boxart_image_loading) {
png_decode_abort();
}
if (boxart_image) {
surface_free(boxart_image);
free(boxart_image);
}
@ -352,24 +338,30 @@ static void deinit (menu_t *menu) {
void view_file_info_init (menu_t *menu) {
boxart_image_loading = true;
boxart_image = NULL;
cached_boxart_image_dl = NULL;
char *file_name = menu->browser.list[menu->browser.selected].name;
path_t *file = path_clone(menu->browser.directory);
path_push(file, menu->browser.list[menu->browser.selected].name);
path_push(file, file_name);
if (f_stat(path_get(file), &info) != FR_OK) {
menu->next_mode = MENU_MODE_ERROR;
}
if (file_has_extensions(info.fname, n64_rom_extensions)) {
if (file_has_extensions(file_name, n64_rom_extensions)) {
rom_header = file_read_rom_header(path_get(file));
boxart_image_load(rom_header.metadata.unique_identifier);
}
path_free(file);
}
void view_file_info_display (menu_t *menu, surface_t *display) {
process(menu);
draw(menu, display);
if (menu->next_mode != MENU_MODE_FILE_INFO) {

View File

@ -6,8 +6,8 @@
#include "views.h"
static bool image_loading;
static surface_t *image;
static rspq_block_t *cached_image_dl;
static void process (menu_t *menu) {
@ -19,50 +19,35 @@ static void process (menu_t *menu) {
static void draw (menu_t *menu, surface_t *d) {
rdpq_attach_clear(d, NULL);
if (image == NULL) {
if (!image) {
fragment_loader(d);
} else {
rspq_block_run(cached_image_dl);
uint16_t x = (d->width / 2) - (image->width / 2);
uint16_t y = (d->height / 2) - (image->height / 2);
rdpq_set_mode_copy(false);
rdpq_tex_blit(image, x, y, NULL);
}
rdpq_detach_show();
}
static void deffered_image_load (menu_t *menu, surface_t *d) {
image = calloc(1, sizeof(surface_t));
static void image_callback (png_err_t err, surface_t *decoded_image, void *callback_data) {
image_loading = false;
image = decoded_image;
if (image == NULL) {
menu->next_mode = MENU_MODE_ERROR;
return;
}
path_t *path = path_clone(menu->browser.directory);
path_push(path, menu->browser.list[menu->browser.selected].name);
if (png_decode(path_get(path), image, 640, 480) == PNG_OK) {
uint16_t x = (d->width / 2) - (image->width / 2);
uint16_t y = (d->height / 2) - (image->height / 2);
rspq_block_begin();
rdpq_set_mode_copy(false);
rdpq_tex_blit(image, x, y, NULL);
cached_image_dl = rspq_block_end();
} else {
if (err != PNG_OK) {
menu_t *menu = (menu_t *) (callback_data);
menu->next_mode = MENU_MODE_ERROR;
}
path_free(path);
}
static void dl_free (void *arg) {
rspq_block_free((rspq_block_t *) (arg));
}
static void deinit (menu_t *menu) {
if (image != NULL) {
rdpq_call_deferred(dl_free, cached_image_dl);
if (image_loading) {
png_decode_abort();
}
if (image) {
surface_free(image);
free(image);
}
@ -70,8 +55,19 @@ static void deinit (menu_t *menu) {
void view_image_viewer_init (menu_t *menu) {
image_loading = false;
image = NULL;
cached_image_dl = NULL;
path_t *path = path_clone(menu->browser.directory);
path_push(path, menu->browser.list[menu->browser.selected].name);
if (png_decode_start(path_get(path), 640, 480, image_callback, menu) == PNG_OK) {
image_loading = true;
} else {
menu->next_mode = MENU_MODE_ERROR;
}
path_free(path);
}
void view_image_viewer_display (menu_t *menu, surface_t *display) {
@ -79,10 +75,6 @@ void view_image_viewer_display (menu_t *menu, surface_t *display) {
draw(menu, display);
if (image == NULL) {
deffered_image_load(menu, display);
}
if (menu->next_mode != MENU_MODE_IMAGE_VIEWER) {
deinit(menu);
}