RetroArch/gfx/gfx_thumbnail.c
zoltanvb 3ce56c5b42
Flexible thumbnail matching (#16040)
Add logic to handle 3 possible thumbnail names, in following order:
- most exact name derived from content file (same name, with .png extension)
- usual name derived from playlist (usually coming from database)
- shortened name up to first bracket, chopping off region/publisher etc. info

For local file system, names are checked always.
For thumbnail downloads, names are checked each time the item comes up
in the playlist, meaning that it may take going back and forth 3 times
for a thumbnail to appear. However, as a positive change, failed thumbnail
downloads are not repeated for the same playlist, which was not the case
earlier.
2023-12-27 02:26:46 -08:00

1063 lines
37 KiB
C

/* Copyright (C) 2010-2019 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (gfx_thumbnail.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <features/features_cpu.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#include "gfx_display.h"
#include "gfx_animation.h"
#include "gfx_thumbnail.h"
#include "../tasks/tasks_internal.h"
#define DEFAULT_GFX_THUMBNAIL_STREAM_DELAY 83.333333f
#define DEFAULT_GFX_THUMBNAIL_FADE_DURATION 166.66667f
/* Utility structure, sent as userdata when pushing
* an image load */
typedef struct
{
uint64_t list_id;
gfx_thumbnail_t *thumbnail;
} gfx_thumbnail_tag_t;
static gfx_thumbnail_state_t gfx_thumb_st = {0}; /* uint64_t alignment */
gfx_thumbnail_state_t *gfx_thumb_get_ptr(void)
{
return &gfx_thumb_st;
}
/* Setters */
/* When streaming thumbnails, sets time in ms that an
* entry must be on screen before an image load is
* requested
* > if 'delay' is negative, default value is set */
void gfx_thumbnail_set_stream_delay(float delay)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
p_gfx_thumb->stream_delay = (delay >= 0.0f) ?
delay : DEFAULT_GFX_THUMBNAIL_STREAM_DELAY;
}
/* Sets duration in ms of the thumbnail 'fade in'
* animation
* > If 'duration' is negative, default value is set */
void gfx_thumbnail_set_fade_duration(float duration)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
p_gfx_thumb->fade_duration = (duration >= 0.0f) ?
duration : DEFAULT_GFX_THUMBNAIL_FADE_DURATION;
}
/* Specifies whether 'fade in' animation should be
* triggered for missing thumbnails
* > When 'true', allows menu driver to animate
* any 'thumbnail unavailable' notifications */
void gfx_thumbnail_set_fade_missing(bool fade_missing)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
p_gfx_thumb->fade_missing = fade_missing;
}
/* Callbacks */
/* Fade animation callback - simply resets thumbnail
* 'fade_active' status */
static void gfx_thumbnail_fade_cb(void *userdata)
{
gfx_thumbnail_t *thumbnail = (gfx_thumbnail_t*)userdata;
if (thumbnail)
thumbnail->flags |= GFX_THUMB_FLAG_FADE_ACTIVE;
}
/* Initialises thumbnail 'fade in' animation */
static void gfx_thumbnail_init_fade(
gfx_thumbnail_state_t *p_gfx_thumb,
gfx_thumbnail_t *thumbnail)
{
/* Sanity check */
if (!thumbnail)
return;
/* A 'fade in' animation is triggered if:
* - Thumbnail is available
* - Thumbnail is missing and 'fade_missing' is enabled */
if ( (thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE)
|| (p_gfx_thumb->fade_missing
&& (thumbnail->status == GFX_THUMBNAIL_STATUS_MISSING)))
{
if (p_gfx_thumb->fade_duration > 0.0f)
{
gfx_animation_ctx_entry_t animation_entry;
thumbnail->alpha = 0.0f;
thumbnail->flags |= GFX_THUMB_FLAG_FADE_ACTIVE;
animation_entry.easing_enum = EASING_OUT_QUAD;
animation_entry.tag = (uintptr_t)&thumbnail->alpha;
animation_entry.duration = p_gfx_thumb->fade_duration;
animation_entry.target_value = 1.0f;
animation_entry.subject = &thumbnail->alpha;
animation_entry.cb = gfx_thumbnail_fade_cb;
animation_entry.userdata = thumbnail;
gfx_animation_push(&animation_entry);
}
else
thumbnail->alpha = 1.0f;
}
}
/* Used to process thumbnail data following completion
* of image load task */
static void gfx_thumbnail_handle_upload(
retro_task_t *task, void *task_data, void *user_data, const char *err)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
struct texture_image *img = (struct texture_image*)task_data;
gfx_thumbnail_tag_t *thumbnail_tag = (gfx_thumbnail_tag_t*)user_data;
bool fade_enabled = false;
/* Sanity check */
if (!thumbnail_tag)
goto end;
/* Ensure that we are operating on the correct
* thumbnail... */
if (thumbnail_tag->list_id != p_gfx_thumb->list_id)
goto end;
/* Only process image if we are waiting for it */
if (thumbnail_tag->thumbnail->status != GFX_THUMBNAIL_STATUS_PENDING)
goto end;
/* Sanity check: if thumbnail already has a texture,
* we're in some kind of weird error state - in this
* case, the best course of action is to just reset
* the thumbnail... */
if (thumbnail_tag->thumbnail->texture)
gfx_thumbnail_reset(thumbnail_tag->thumbnail);
/* Set thumbnail 'missing' status by default
* (saves a number of checks later) */
thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
/* If we reach this stage, thumbnail 'fade in'
* animations should be applied (based on current
* thumbnail status and global configuration) */
fade_enabled = true;
/* Check we have a valid image */
if (!img || (img->width < 1) || (img->height < 1))
goto end;
/* Upload texture to GPU */
if (!video_driver_texture_load(
img, TEXTURE_FILTER_MIPMAP_LINEAR,
&thumbnail_tag->thumbnail->texture))
goto end;
/* Cache dimensions */
thumbnail_tag->thumbnail->width = img->width;
thumbnail_tag->thumbnail->height = img->height;
/* Update thumbnail status */
thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_AVAILABLE;
end:
/* Clean up */
if (img)
{
image_texture_free(img);
free(img);
}
if (thumbnail_tag)
{
/* Trigger 'fade in' animation, if required */
if (fade_enabled)
gfx_thumbnail_init_fade(p_gfx_thumb,
thumbnail_tag->thumbnail);
free(thumbnail_tag);
}
}
/* Core interface */
/* When called, prevents the handling of any pending
* thumbnail load requests
* >> **MUST** be called before deleting any gfx_thumbnail_t
* objects passed to gfx_thumbnail_request() or
* gfx_thumbnail_process_stream(), otherwise
* heap-use-after-free errors *will* occur */
void gfx_thumbnail_cancel_pending_requests(void)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
p_gfx_thumb->list_id++;
}
/* Requests loading of the specified thumbnail
* - If operation fails, 'thumbnail->status' will be set to
* GFX_THUMBNAIL_STATUS_MISSING
* - If operation is successful, 'thumbnail->status' will be
* set to GFX_THUMBNAIL_STATUS_PENDING
* 'thumbnail' will be populated with texture info/metadata
* once the image load is complete
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* and gfx_thumbnail_set_content*()
* NOTE 2: 'playlist' and 'idx' are only required here for
* on-demand thumbnail download support
* (an annoyance...) */
void gfx_thumbnail_request(
gfx_thumbnail_path_data_t *path_data, enum gfx_thumbnail_id thumbnail_id,
playlist_t *playlist, size_t idx, gfx_thumbnail_t *thumbnail,
unsigned gfx_thumbnail_upscale_threshold,
bool network_on_demand_thumbnails)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
if (!path_data || !thumbnail)
return;
/* Reset thumbnail, then set 'missing' status by default
* (saves a number of checks later) */
gfx_thumbnail_reset(thumbnail);
thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
/* Update/extract thumbnail path */
if (gfx_thumbnail_is_enabled(path_data, thumbnail_id))
{
if (gfx_thumbnail_update_path(path_data, thumbnail_id))
{
const char *thumbnail_path = NULL;
if (gfx_thumbnail_get_path(path_data, thumbnail_id, &thumbnail_path))
{
/* Load thumbnail, if required */
if (path_is_valid(thumbnail_path))
{
gfx_thumbnail_tag_t *thumbnail_tag =
(gfx_thumbnail_tag_t*)malloc(sizeof(gfx_thumbnail_tag_t));
if (!thumbnail_tag)
goto end;
/* Configure user data */
thumbnail_tag->thumbnail = thumbnail;
thumbnail_tag->list_id = p_gfx_thumb->list_id;
/* Would like to cancel any existing image load tasks
* here, but can't see how to do it... */
if (task_push_image_load(
thumbnail_path, video_driver_supports_rgba(),
gfx_thumbnail_upscale_threshold,
gfx_thumbnail_handle_upload, thumbnail_tag))
thumbnail->status = GFX_THUMBNAIL_STATUS_PENDING;
}
#ifdef HAVE_NETWORKING
/* Handle on demand thumbnail downloads */
else if (network_on_demand_thumbnails)
{
const char *system = NULL;
const char *img_name = NULL;
static char last_img_name[PATH_MAX_LENGTH] = {0};
enum playlist_thumbnail_name_flags next_flag;
if (!playlist)
goto end;
/* Validate entry */
if (!gfx_thumbnail_get_img_name(path_data, &img_name, PLAYLIST_THUMBNAIL_FLAG_STD_NAME))
goto end;
/* Only trigger a thumbnail download if image
* name has changed since the last call of
* gfx_thumbnail_request()
* > Allows gfx_thumbnail_request() to be used
* for successive right/left thumbnail requests
* with minimal duplication of effort
* (i.e. task_push_pl_entry_thumbnail_download()
* will automatically cancel if a download for the
* existing playlist entry is pending, but the
* checks required for this involve significant
* overheads. We can avoid this entirely with
* a simple string comparison) */
if (string_is_equal(img_name, last_img_name))
goto end;
strlcpy(last_img_name, img_name, sizeof(last_img_name));
/* Get system name */
if (!gfx_thumbnail_get_system(path_data, &system))
goto end;
/* Apply flexible thumbnail naming: ROM file name - database name - short name */
next_flag = playlist_get_next_thumbnail_name_flag(playlist,idx);
if (next_flag == PLAYLIST_THUMBNAIL_FLAG_NONE)
goto end;
/* Trigger thumbnail download *
* Note: download will grab all 3 possible thumbnails, no matter
* what left/right thumbnails are set at the moment */
playlist_update_thumbnail_name_flag(playlist, idx, next_flag);
task_push_pl_entry_thumbnail_download(
system, playlist, (unsigned)idx,
false, true);
}
#endif
}
}
}
end:
/* Trigger 'fade in' animation, if required */
if (thumbnail->status != GFX_THUMBNAIL_STATUS_PENDING)
gfx_thumbnail_init_fade(p_gfx_thumb,
thumbnail);
}
/* Requests loading of a specific thumbnail image file
* (may be used, for example, to load savestate images)
* - If operation fails, 'thumbnail->status' will be set to
* MUI_THUMBNAIL_STATUS_MISSING
* - If operation is successful, 'thumbnail->status' will be
* set to MUI_THUMBNAIL_STATUS_PENDING
* 'thumbnail' will be populated with texture info/metadata
* once the image load is complete */
void gfx_thumbnail_request_file(
const char *file_path, gfx_thumbnail_t *thumbnail,
unsigned gfx_thumbnail_upscale_threshold)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
gfx_thumbnail_tag_t *thumbnail_tag = NULL;
if (!thumbnail)
return;
/* Reset thumbnail, then set 'missing' status by default
* (saves a number of checks later) */
gfx_thumbnail_reset(thumbnail);
thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
/* Check if file path is valid */
if ( string_is_empty(file_path)
|| !path_is_valid(file_path))
return;
/* Load thumbnail */
if (!(thumbnail_tag = (gfx_thumbnail_tag_t*)malloc(sizeof(gfx_thumbnail_tag_t))))
return;
/* Configure user data */
thumbnail_tag->thumbnail = thumbnail;
thumbnail_tag->list_id = p_gfx_thumb->list_id;
/* Would like to cancel any existing image load tasks
* here, but can't see how to do it... */
if (task_push_image_load(
file_path, video_driver_supports_rgba(),
gfx_thumbnail_upscale_threshold,
gfx_thumbnail_handle_upload, thumbnail_tag))
thumbnail->status = GFX_THUMBNAIL_STATUS_PENDING;
}
/* Resets (and free()s the current texture of) the
* specified thumbnail */
void gfx_thumbnail_reset(gfx_thumbnail_t *thumbnail)
{
if (!thumbnail)
return;
/* Unload texture */
if (thumbnail->texture)
video_driver_texture_unload(&thumbnail->texture);
/* Ensure any 'fade in' animation is killed */
if (thumbnail->flags & GFX_THUMB_FLAG_FADE_ACTIVE)
{
uintptr_t tag = (uintptr_t)&thumbnail->alpha;
gfx_animation_kill_by_tag(&tag);
}
/* Reset all parameters */
thumbnail->status = GFX_THUMBNAIL_STATUS_UNKNOWN;
thumbnail->texture = 0;
thumbnail->width = 0;
thumbnail->height = 0;
thumbnail->alpha = 0.0f;
thumbnail->delay_timer = 0.0f;
thumbnail->flags &= ~(GFX_THUMB_FLAG_FADE_ACTIVE
| GFX_THUMB_FLAG_CORE_ASPECT);
}
/* Stream processing */
/* Requests loading of the specified thumbnail via
* the stream interface
* - Must be called on each frame for the duration
* that specified thumbnail is on-screen
* - Actual load request is deferred by currently
* set stream delay
* - Function becomes a no-op once load request is
* made
* - Thumbnails loaded via this function must be
* deleted manually via gfx_thumbnail_reset()
* when they move off-screen
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* and gfx_thumbnail_set_content*()
* NOTE 2: 'playlist' and 'idx' are only required here for
* on-demand thumbnail download support
* (an annoyance...)
* NOTE 3: This function is intended for use in situations
* where each menu entry has a *single* thumbnail.
* If each entry has two thumbnails, use
* gfx_thumbnail_request_streams() for improved
* performance */
void gfx_thumbnail_request_stream(
gfx_thumbnail_path_data_t *path_data,
gfx_animation_t *p_anim,
enum gfx_thumbnail_id thumbnail_id,
playlist_t *playlist, size_t idx,
gfx_thumbnail_t *thumbnail,
unsigned gfx_thumbnail_upscale_threshold,
bool network_on_demand_thumbnails)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
/* Only process request if current status
* is GFX_THUMBNAIL_STATUS_UNKNOWN */
if ( !thumbnail
|| (thumbnail->status != GFX_THUMBNAIL_STATUS_UNKNOWN))
return;
/* Check if stream delay timer has elapsed */
thumbnail->delay_timer += p_anim->delta_time;
if (thumbnail->delay_timer > p_gfx_thumb->stream_delay)
{
/* Sanity check */
if (!path_data)
{
/* No path information
* > Reset thumbnail and set missing status
* to prevent repeated load attempts */
gfx_thumbnail_reset(thumbnail);
thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
thumbnail->alpha = 1.0f;
return;
}
/* Request image load */
gfx_thumbnail_request(
path_data, thumbnail_id, playlist, idx, thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
}
}
/* Requests loading of the specified thumbnails via
* the stream interface
* - Must be called on each frame for the duration
* that specified thumbnails are on-screen
* - Actual load request is deferred by currently
* set stream delay
* - Function becomes a no-op once load request is
* made
* - Thumbnails loaded via this function must be
* deleted manually via gfx_thumbnail_reset()
* when they move off-screen
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* and gfx_thumbnail_set_content*()
* NOTE 2: 'playlist' and 'idx' are only required here for
* on-demand thumbnail download support
* (an annoyance...)
* NOTE 3: This function is intended for use in situations
* where each menu entry has *two* thumbnails.
* If each entry only has a single thumbnail, use
* gfx_thumbnail_request_stream() for improved
* performance */
void gfx_thumbnail_request_streams(
gfx_thumbnail_path_data_t *path_data,
gfx_animation_t *p_anim,
playlist_t *playlist, size_t idx,
gfx_thumbnail_t *right_thumbnail,
gfx_thumbnail_t *left_thumbnail,
unsigned gfx_thumbnail_upscale_threshold,
bool network_on_demand_thumbnails)
{
bool process_right = false;
bool process_left = false;
if (!right_thumbnail || !left_thumbnail)
return;
/* Only process request if current status
* is GFX_THUMBNAIL_STATUS_UNKNOWN */
process_right = (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
process_left = (left_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
if (process_right || process_left)
{
/* Check if stream delay timer has elapsed */
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
float delta_time = p_anim->delta_time;
bool request_right = false;
bool request_left = false;
if (process_right)
{
right_thumbnail->delay_timer += delta_time;
request_right =
(right_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
}
if (process_left)
{
left_thumbnail->delay_timer += delta_time;
request_left =
(left_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
}
/* Check if one or more thumbnails should be requested */
if (request_right || request_left)
{
/* Sanity check */
if (!path_data)
{
/* No path information
* > Reset thumbnail and set missing status
* to prevent repeated load attempts */
if (request_right)
{
gfx_thumbnail_reset(right_thumbnail);
right_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
right_thumbnail->alpha = 1.0f;
}
if (request_left)
{
gfx_thumbnail_reset(left_thumbnail);
left_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
left_thumbnail->alpha = 1.0f;
}
return;
}
/* Request image load */
if (request_right)
gfx_thumbnail_request(
path_data, GFX_THUMBNAIL_RIGHT, playlist, idx, right_thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
if (request_left)
gfx_thumbnail_request(
path_data, GFX_THUMBNAIL_LEFT, playlist, idx, left_thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
}
}
}
/* Handles streaming of the specified thumbnail as it moves
* on/off screen
* - Must be called each frame for every on-screen entry
* - Must be called once for each entry as it moves off-screen
* (or can be called each frame - overheads are small)
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* NOTE 2: This function calls gfx_thumbnail_set_content*()
* NOTE 3: This function is intended for use in situations
* where each menu entry has a *single* thumbnail.
* If each entry has two thumbnails, use
* gfx_thumbnail_process_streams() for improved
* performance */
void gfx_thumbnail_process_stream(
gfx_thumbnail_path_data_t *path_data,
gfx_animation_t *p_anim,
enum gfx_thumbnail_id thumbnail_id,
playlist_t *playlist, size_t idx,
gfx_thumbnail_t *thumbnail,
bool on_screen,
unsigned gfx_thumbnail_upscale_threshold,
bool network_on_demand_thumbnails)
{
if (!thumbnail)
return;
if (on_screen)
{
/* Entry is on-screen
* > Only process if current status is
* GFX_THUMBNAIL_STATUS_UNKNOWN */
if (thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
{
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
/* Check if stream delay timer has elapsed */
thumbnail->delay_timer += p_anim->delta_time;
if (thumbnail->delay_timer > p_gfx_thumb->stream_delay)
{
/* Update thumbnail content */
if ( !path_data
|| !playlist
|| !gfx_thumbnail_set_content_playlist(path_data, playlist, idx))
{
/* Content is invalid
* > Reset thumbnail and set missing status */
gfx_thumbnail_reset(thumbnail);
thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
thumbnail->alpha = 1.0f;
return;
}
/* Request image load */
gfx_thumbnail_request(
path_data, thumbnail_id, playlist, idx, thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
}
}
}
else
{
/* Entry is off-screen
* > If status is GFX_THUMBNAIL_STATUS_UNKNOWN,
* thumbnail is already in a blank state - but we
* must ensure that delay timer is set to zero */
if (thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
thumbnail->delay_timer = 0.0f;
/* In all other cases, reset thumbnail */
else
gfx_thumbnail_reset(thumbnail);
}
}
/* Handles streaming of the specified thumbnails as they move
* on/off screen
* - Must be called each frame for every on-screen entry
* - Must be called once for each entry as it moves off-screen
* (or can be called each frame - overheads are small)
* NOTE 1: Must be called *after* gfx_thumbnail_set_system()
* NOTE 2: This function calls gfx_thumbnail_set_content*()
* NOTE 3: This function is intended for use in situations
* where each menu entry has *two* thumbnails.
* If each entry only has a single thumbnail, use
* gfx_thumbnail_process_stream() for improved
* performance */
void gfx_thumbnail_process_streams(
gfx_thumbnail_path_data_t *path_data,
gfx_animation_t *p_anim,
playlist_t *playlist, size_t idx,
gfx_thumbnail_t *right_thumbnail,
gfx_thumbnail_t *left_thumbnail,
bool on_screen,
unsigned gfx_thumbnail_upscale_threshold,
bool network_on_demand_thumbnails)
{
if (!right_thumbnail || !left_thumbnail)
return;
if (on_screen)
{
/* Entry is on-screen
* > Only process if current status is
* GFX_THUMBNAIL_STATUS_UNKNOWN */
bool process_right = (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
bool process_left = (left_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
if (process_right || process_left)
{
/* Check if stream delay timer has elapsed */
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
float delta_time = p_anim->delta_time;
bool request_right = false;
bool request_left = false;
if (process_right)
{
right_thumbnail->delay_timer += delta_time;
request_right =
(right_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
}
if (process_left)
{
left_thumbnail->delay_timer += delta_time;
request_left =
(left_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
}
/* Check if one or more thumbnails should be requested */
if (request_right || request_left)
{
/* Update thumbnail content */
if ( !path_data
|| !playlist
|| !gfx_thumbnail_set_content_playlist(path_data, playlist, idx))
{
/* Content is invalid
* > Reset thumbnail and set missing status */
if (request_right)
{
gfx_thumbnail_reset(right_thumbnail);
right_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
right_thumbnail->alpha = 1.0f;
}
if (request_left)
{
gfx_thumbnail_reset(left_thumbnail);
left_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
left_thumbnail->alpha = 1.0f;
}
return;
}
/* Request image load */
if (request_right)
gfx_thumbnail_request(
path_data, GFX_THUMBNAIL_RIGHT, playlist, idx, right_thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
if (request_left)
gfx_thumbnail_request(
path_data, GFX_THUMBNAIL_LEFT, playlist, idx, left_thumbnail,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
}
}
}
else
{
/* Entry is off-screen
* > If status is GFX_THUMBNAIL_STATUS_UNKNOWN,
* thumbnail is already in a blank state - but we
* must ensure that delay timer is set to zero
* > In all other cases, reset thumbnail */
if (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
right_thumbnail->delay_timer = 0.0f;
else
gfx_thumbnail_reset(right_thumbnail);
if (left_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
left_thumbnail->delay_timer = 0.0f;
else
gfx_thumbnail_reset(left_thumbnail);
}
}
/* Thumbnail rendering */
/* Determines the actual screen dimensions of a
* thumbnail when centred with aspect correct
* scaling within a rectangle of (width x height) */
void gfx_thumbnail_get_draw_dimensions(
gfx_thumbnail_t *thumbnail,
unsigned width, unsigned height, float scale_factor,
float *draw_width, float *draw_height)
{
float core_aspect;
float display_aspect;
float thumbnail_aspect;
video_driver_state_t *video_st = video_state_get_ptr();
/* Sanity check */
if ( !thumbnail
|| (width < 1)
|| (height < 1)
|| (thumbnail->width < 1)
|| (thumbnail->height < 1))
{
*draw_width = 0.0f;
*draw_height = 0.0f;
return;
}
/* Account for display/thumbnail/core aspect ratio
* differences */
display_aspect = (float)width / (float)height;
thumbnail_aspect = (float)thumbnail->width / (float)thumbnail->height;
core_aspect = ((thumbnail->flags & GFX_THUMB_FLAG_CORE_ASPECT)
&& video_st && video_st->av_info.geometry.aspect_ratio > 0)
? video_st->av_info.geometry.aspect_ratio
: thumbnail_aspect;
if (thumbnail_aspect > display_aspect)
{
*draw_width = (float)width;
*draw_height = (float)thumbnail->height * (*draw_width / (float)thumbnail->width);
if (thumbnail->flags & GFX_THUMB_FLAG_CORE_ASPECT)
{
*draw_height = *draw_height * (thumbnail_aspect / core_aspect);
if (*draw_height > height)
{
*draw_height = (float)height;
*draw_width = (float)thumbnail->width * (*draw_height / (float)thumbnail->height);
*draw_width = *draw_width / (thumbnail_aspect / core_aspect);
}
}
}
else
{
*draw_height = (float)height;
*draw_width = (float)thumbnail->width * (*draw_height / (float)thumbnail->height);
if (thumbnail->flags & GFX_THUMB_FLAG_CORE_ASPECT)
*draw_width = *draw_width / (thumbnail_aspect / core_aspect);
}
/* Account for scale factor
* > Side note: We cannot use the gfx_display_ctx_draw_t
* 'scale_factor' parameter for scaling thumbnails,
* since this clips off any part of the expanded image
* that extends beyond the bounding box. But even if
* it didn't, we can't get real screen dimensions
* without scaling manually... */
*draw_width *= scale_factor;
*draw_height *= scale_factor;
}
/* Draws specified thumbnail with specified alignment
* (and aspect correct scaling) within a rectangle of
* (width x height).
* 'shadow' defines an optional shadow effect (may be
* set to NULL if a shadow effect is not required).
* NOTE: Setting scale_factor > 1.0f will increase the
* size of the thumbnail beyond the limits of the
* (width x height) rectangle (alignment + aspect
* correct scaling is preserved). Use with caution */
void gfx_thumbnail_draw(
void *userdata,
unsigned video_width,
unsigned video_height,
gfx_thumbnail_t *thumbnail,
float x, float y, unsigned width, unsigned height,
enum gfx_thumbnail_alignment alignment,
float alpha, float scale_factor,
gfx_thumbnail_shadow_t *shadow)
{
gfx_display_t *p_disp = disp_get_ptr();
gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
/* Sanity check */
if (
!thumbnail
|| !dispctx
|| (width < 1)
|| (height < 1)
|| (alpha <= 0.0f)
|| (scale_factor <= 0.0f)
)
return;
/* Only draw thumbnail if it is available... */
if (thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE)
{
gfx_display_ctx_draw_t draw;
struct video_coords coords;
math_matrix_4x4 mymat;
float draw_width;
float draw_height;
float draw_x;
float draw_y;
float thumbnail_alpha = thumbnail->alpha * alpha;
float thumbnail_color[16] = {
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
/* Set thumbnail opacity */
if (thumbnail_alpha <= 0.0f)
return;
if (thumbnail_alpha < 1.0f)
gfx_display_set_alpha(thumbnail_color, thumbnail_alpha);
/* Get thumbnail dimensions */
gfx_thumbnail_get_draw_dimensions(
thumbnail, width, height, scale_factor,
&draw_width, &draw_height);
if (dispctx->blend_begin)
dispctx->blend_begin(userdata);
if (!p_disp->dispctx->handles_transform)
{
/* Perform 'rotation' step
* > Note that rotation does not actually work...
* > It rotates the image all right, but distorts it
* to fit the aspect of the bounding box while clipping
* off any 'corners' that extend beyond the bounding box
* > Since the result is visual garbage, we disable
* rotation entirely
* > But we still have to call gfx_display_rotate_z(),
* or nothing will be drawn...
*/
float cosine = 1.0f; /* cos(rad) = cos(0) = 1.0f */
float sine = 0.0f; /* sine(rad) = sine(0) = 0.0f */
gfx_display_rotate_z(p_disp, &mymat, cosine, sine, userdata);
}
/* Configure draw object
* > Note: Colour, width/height and position must
* be set *after* drawing any shadow effects */
coords.vertices = 4;
coords.vertex = NULL;
coords.tex_coord = NULL;
coords.lut_tex_coord = NULL;
draw.scale_factor = 1.0f;
draw.rotation = 0.0f;
draw.coords = &coords;
draw.matrix_data = &mymat;
draw.texture = thumbnail->texture;
draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP;
draw.pipeline_id = 0;
/* Set thumbnail alignment within bounding box */
switch (alignment)
{
case GFX_THUMBNAIL_ALIGN_TOP:
/* Centred horizontally */
draw_x = x + ((float)width - draw_width) / 2.0f;
/* Drawn at top of bounding box */
draw_y = (float)video_height - y - draw_height;
break;
case GFX_THUMBNAIL_ALIGN_BOTTOM:
/* Centred horizontally */
draw_x = x + ((float)width - draw_width) / 2.0f;
/* Drawn at bottom of bounding box */
draw_y = (float)video_height - y - (float)height;
break;
case GFX_THUMBNAIL_ALIGN_LEFT:
/* Drawn at left side of bounding box */
draw_x = x;
/* Centred vertically */
draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
break;
case GFX_THUMBNAIL_ALIGN_RIGHT:
/* Drawn at right side of bounding box */
draw_x = x + (float)width - draw_width;
/* Centred vertically */
draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
break;
case GFX_THUMBNAIL_ALIGN_CENTRE:
default:
/* Centred both horizontally and vertically */
draw_x = x + ((float)width - draw_width) / 2.0f;
draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
break;
}
/* Draw shadow effect, if required */
if (shadow)
{
/* Sanity check */
if ( (shadow->type != GFX_THUMBNAIL_SHADOW_NONE)
&& (shadow->alpha > 0.0f))
{
float shadow_width;
float shadow_height;
float shadow_x;
float shadow_y;
float shadow_color[16] = {
0.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
float shadow_alpha = thumbnail_alpha;
/* Set shadow opacity */
if (shadow->alpha < 1.0f)
shadow_alpha *= shadow->alpha;
gfx_display_set_alpha(shadow_color, shadow_alpha);
/* Configure shadow based on effect type
* > Not using a switch() here, since we've
* already eliminated GFX_THUMBNAIL_SHADOW_NONE */
if (shadow->type == GFX_THUMBNAIL_SHADOW_OUTLINE)
{
shadow_width = draw_width + (float)(shadow->outline.width * 2);
shadow_height = draw_height + (float)(shadow->outline.width * 2);
shadow_x = draw_x - (float)shadow->outline.width;
shadow_y = draw_y - (float)shadow->outline.width;
}
/* Default: GFX_THUMBNAIL_SHADOW_DROP */
else
{
shadow_width = draw_width;
shadow_height = draw_height;
shadow_x = draw_x + shadow->drop.x_offset;
shadow_y = draw_y - shadow->drop.y_offset;
}
/* Apply shadow draw object configuration */
coords.color = (const float*)shadow_color;
draw.width = (unsigned)shadow_width;
draw.height = (unsigned)shadow_height;
draw.x = shadow_x;
draw.y = shadow_y;
/* Draw shadow */
if (draw.height > 0 && draw.width > 0)
if (dispctx->draw)
dispctx->draw(&draw, userdata, video_width, video_height);
}
}
/* Final thumbnail draw object configuration */
coords.color = (const float*)thumbnail_color;
draw.width = (unsigned)draw_width;
draw.height = (unsigned)draw_height;
draw.x = draw_x;
draw.y = draw_y;
/* Draw thumbnail */
if (draw.height > 0 && draw.width > 0)
if (dispctx->draw)
dispctx->draw(&draw, userdata, video_width, video_height);
if (dispctx->blend_end)
dispctx->blend_end(userdata);
}
}