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

View File

@ -4,105 +4,156 @@
#include "png_decoder.h" #include "png_decoder.h"
png_err_t png_decode (char *path, surface_t *image, int max_width, int max_height) { typedef struct {
spng_ctx *ctx;
enum spng_errno err = SPNG_OK;
path_t *file_path;
FILE *file; FILE *file;
size_t image_size;
spng_ctx *ctx;
struct spng_ihdr ihdr; struct spng_ihdr ihdr;
struct spng_row_info row_info;
surface_t *image;
uint8_t *row_buffer; 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; 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:/"); file_path = path_init("sd:/");
path_append(file_path, path); path_append(file_path, path);
if ((file = fopen(path_get(file_path), "r")) == NULL) { decoder->file = fopen(path_get(file_path), "r");
spng_ctx_free(ctx); path_free(file_path);
path_free(file_path); if (decoder->file == NULL) {
png_decoder_deinit(false);
return PNG_ERR_NO_FILE; return PNG_ERR_NO_FILE;
} }
path_free(file_path);
if (spng_set_png_file(ctx, file) != SPNG_OK) { if ((decoder->ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32)) == NULL) {
spng_ctx_free(ctx); png_decoder_deinit(false);
fclose(file); 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; return PNG_ERR_INT;
} }
if (spng_decoded_image_size(ctx, SPNG_FMT_RGB8, &image_size) != SPNG_OK) { if (spng_set_image_limits(decoder->ctx, max_width, max_height) != SPNG_OK) {
spng_ctx_free(ctx); png_decoder_deinit(false);
fclose(file); 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; return PNG_ERR_BAD_FILE;
} }
if (spng_decode_image(ctx, NULL, image_size, SPNG_FMT_RGB8, SPNG_DECODE_PROGRESSIVE) != SPNG_OK) { if (spng_decode_image(decoder->ctx, NULL, image_size, SPNG_FMT_RGB8, SPNG_DECODE_PROGRESSIVE) != SPNG_OK) {
spng_ctx_free(ctx); png_decoder_deinit(false);
fclose(file);
return PNG_ERR_BAD_FILE; return PNG_ERR_BAD_FILE;
} }
if (spng_get_ihdr(ctx, &ihdr) != SPNG_OK) { if (spng_get_ihdr(decoder->ctx, &decoder->ihdr) != SPNG_OK) {
spng_ctx_free(ctx); png_decoder_deinit(false);
fclose(file);
return PNG_ERR_BAD_FILE; return PNG_ERR_BAD_FILE;
} }
*image = surface_alloc(FMT_RGBA16, ihdr.width, ihdr.height); decoder->image = calloc(1, sizeof(surface_t));
if (image->buffer == NULL) { if (decoder->image == NULL) {
spng_ctx_free(ctx); png_decoder_deinit(false);
fclose(file);
return PNG_ERR_OUT_OF_MEM; return PNG_ERR_OUT_OF_MEM;
} }
if ((row_buffer = malloc(ihdr.width * 3)) == NULL) { *decoder->image = surface_alloc(FMT_RGBA16, decoder->ihdr.width, decoder->ihdr.height);
spng_ctx_free(ctx); if (decoder->image->buffer == NULL) {
fclose(file); png_decoder_deinit(true);
surface_free(image);
return PNG_ERR_OUT_OF_MEM; return PNG_ERR_OUT_OF_MEM;
} }
do { if ((decoder->row_buffer = malloc(decoder->ihdr.width * 3)) == NULL) {
if ((err = spng_get_row_info(ctx, &row_info)) != SPNG_OK) { png_decoder_deinit(true);
break; return PNG_ERR_OUT_OF_MEM;
}
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;
} }
decoder->callback = callback;
decoder->callback_data = callback_data;
return PNG_OK; 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 { typedef enum {
PNG_OK, PNG_OK,
PNG_ERR_INT, PNG_ERR_INT,
PNG_ERR_BUSY,
PNG_ERR_OUT_OF_MEM, PNG_ERR_OUT_OF_MEM,
PNG_ERR_NO_FILE, PNG_ERR_NO_FILE,
PNG_ERR_BAD_FILE, PNG_ERR_BAD_FILE,
} png_err_t; } 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 #endif

View File

@ -9,42 +9,19 @@
#include "utils/str_utils.h" #include "utils/str_utils.h"
#include "views.h" #include "views.h"
#ifndef ROM_BOXART_PATH #ifndef ROM_BOXART_PATH
#define ROM_BOXART_PATH "/menu/boxart/" #define ROM_BOXART_PATH "/menu/boxart"
#endif #endif
static FILINFO info; static FILINFO info;
static const char *n64_rom_extensions[] = { "z64", "n64", "v64", NULL }; static const char *n64_rom_extensions[] = { "z64", "n64", "v64", NULL };
static rom_header_t rom_header; static rom_header_t rom_header;
static bool boxart_image_loading;
static surface_t *boxart_image; 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) { static char *format_rom_endian (uint32_t endian) {
switch (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, " 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))); 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) if (boxart_image) {
{ uint16_t x = (640 - 150) - (boxart_image->width / 2);
rspq_block_run(cached_boxart_image_dl); 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) { static void draw (menu_t *menu, surface_t *d) {
layout_t *layout = layout_get(); layout_t *layout = layout_get();
const int text_x = layout->offset_x + layout->offset_text_x; const int text_x = layout->offset_x + layout->offset_text_x;
@ -317,17 +294,15 @@ static void draw (menu_t *menu, surface_t *d) {
// Text start // Text start
fragment_text_start(text_color); fragment_text_start(text_color);
if (file_has_extensions(info.fname, n64_rom_extensions)) { if (file_has_extensions(info.fname, n64_rom_extensions)) {
menu_fileinfo_draw_n64_rom_info(d, layout); menu_fileinfo_draw_n64_rom_info(d, layout);
} } else {
else {
menu_fileinfo_draw_unknown_info(d, layout); 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); fragment_text_start(text_color);
// Actions bar // Actions bar
text_y = layout->actions_y + layout->offset_text_y; text_y = layout->actions_y + layout->offset_text_y;
text_y += fragment_textf(text_x, text_y, "B: Exit"); text_y += fragment_textf(text_x, text_y, "B: Exit");
@ -335,16 +310,27 @@ static void draw (menu_t *menu, surface_t *d) {
rdpq_detach_show(); 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) { /* loads a PNG image for a given ROM ID from ROM_BOXART_PATH. e.g. sd:/menu/boxart/<id>.png */
if (cached_boxart_image_dl != NULL) { static void boxart_image_load (uint16_t id) {
rspq_block_free((rspq_block_t *) (arg)); 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) { static void deinit (menu_t *menu) {
if (boxart_image != NULL) { if (boxart_image_loading) {
rdpq_call_deferred(dl_free, cached_boxart_image_dl); png_decode_abort();
}
if (boxart_image) {
surface_free(boxart_image); surface_free(boxart_image);
free(boxart_image); free(boxart_image);
} }
@ -352,24 +338,30 @@ static void deinit (menu_t *menu) {
void view_file_info_init (menu_t *menu) { void view_file_info_init (menu_t *menu) {
boxart_image_loading = true;
boxart_image = NULL; 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_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) { if (f_stat(path_get(file), &info) != FR_OK) {
menu->next_mode = MENU_MODE_ERROR; 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)); rom_header = file_read_rom_header(path_get(file));
boxart_image_load(rom_header.metadata.unique_identifier); boxart_image_load(rom_header.metadata.unique_identifier);
} }
path_free(file); path_free(file);
} }
void view_file_info_display (menu_t *menu, surface_t *display) { void view_file_info_display (menu_t *menu, surface_t *display) {
process(menu); process(menu);
draw(menu, display); draw(menu, display);
if (menu->next_mode != MENU_MODE_FILE_INFO) { if (menu->next_mode != MENU_MODE_FILE_INFO) {

View File

@ -6,8 +6,8 @@
#include "views.h" #include "views.h"
static bool image_loading;
static surface_t *image; static surface_t *image;
static rspq_block_t *cached_image_dl;
static void process (menu_t *menu) { 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) { static void draw (menu_t *menu, surface_t *d) {
rdpq_attach_clear(d, NULL); rdpq_attach_clear(d, NULL);
if (image == NULL) { if (!image) {
fragment_loader(d); fragment_loader(d);
} else { } 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(); rdpq_detach_show();
} }
static void deffered_image_load (menu_t *menu, surface_t *d) { static void image_callback (png_err_t err, surface_t *decoded_image, void *callback_data) {
image = calloc(1, sizeof(surface_t)); image_loading = false;
image = decoded_image;
if (image == NULL) { if (err != PNG_OK) {
menu->next_mode = MENU_MODE_ERROR; menu_t *menu = (menu_t *) (callback_data);
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 {
menu->next_mode = MENU_MODE_ERROR; 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) { static void deinit (menu_t *menu) {
if (image != NULL) { if (image_loading) {
rdpq_call_deferred(dl_free, cached_image_dl); png_decode_abort();
}
if (image) {
surface_free(image); surface_free(image);
free(image); free(image);
} }
@ -70,8 +55,19 @@ static void deinit (menu_t *menu) {
void view_image_viewer_init (menu_t *menu) { void view_image_viewer_init (menu_t *menu) {
image_loading = false;
image = NULL; 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) { 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); draw(menu, display);
if (image == NULL) {
deffered_image_load(menu, display);
}
if (menu->next_mode != MENU_MODE_IMAGE_VIEWER) { if (menu->next_mode != MENU_MODE_IMAGE_VIEWER) {
deinit(menu); deinit(menu);
} }