forked from libretro/RetroArch
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgfx_thumbnail.c
853 lines (735 loc) · 28.8 KB
/
gfx_thumbnail.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
/* 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
/* Structure containing all gfx_thumbnail
* global variables */
struct gfx_thumbnail_state
{
/* When streaming thumbnails, to minimise the processing
* of unnecessary images (i.e. when scrolling rapidly through
* playlists), we delay loading until an entry has been on screen
* for at least gfx_thumbnail_delay ms */
float stream_delay;
/* Duration in ms of the thumbnail 'fade in' animation */
float fade_duration;
/* Due to the asynchronous nature of thumbnail
* loading, it is quite possible to trigger a load
* then navigate to a different menu list before
* the load is complete/handled. As an additional
* safety check, we therefore tag the current menu
* list with counter value that is incremented whenever
* a list is cleared/set. This is sent as userdata when
* requesting a thumbnail, and the upload is only
* handled if the tag matches the most recent value
* at the time when the load completes */
uint64_t list_id;
};
typedef struct gfx_thumbnail_state gfx_thumbnail_state_t;
/* Utility structure, sent as userdata when pushing
* an image load */
typedef struct
{
gfx_thumbnail_t *thumbnail;
retro_time_t list_id;
} gfx_thumbnail_tag_t;
/* Global gfx_thumbnail_state structure */
static gfx_thumbnail_state_t gfx_thumb_state;
/* Global variable access */
/* Returns pointer to global gfx_thumbnail_state
* structure */
static gfx_thumbnail_state_t *gfx_thumb_get_ptr(void)
{
return &gfx_thumb_state;
}
/* 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_get_ptr();
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_get_ptr();
p_gfx_thumb->fade_duration = (duration >= 0.0f) ?
duration : DEFAULT_GFX_THUMBNAIL_FADE_DURATION;
}
/* Getters */
/* Fetches current streaming thumbnails request delay */
float gfx_thumbnail_get_stream_delay(void)
{
gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
return p_gfx_thumb->stream_delay;
}
/* Fetches current 'fade in' animation duration */
float gfx_thumbnail_get_fade_duration(void)
{
gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
return p_gfx_thumb->fade_duration;
}
/* Callbacks */
/* 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_animation_ctx_entry_t animation_entry;
gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
struct texture_image *img = (struct texture_image*)task_data;
gfx_thumbnail_tag_t *thumbnail_tag = (gfx_thumbnail_tag_t*)user_data;
/* 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;
/* Check we have a valid image */
if (!img)
goto end;
if ((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;
/* Trigger 'fade in' animation, if required */
if (p_gfx_thumb->fade_duration > 0.0f)
{
thumbnail_tag->thumbnail->alpha = 0.0f;
animation_entry.easing_enum = EASING_OUT_QUAD;
animation_entry.tag = (uintptr_t)&thumbnail_tag->thumbnail->alpha;
animation_entry.duration = p_gfx_thumb->fade_duration;
animation_entry.target_value = 1.0f;
animation_entry.subject = &thumbnail_tag->thumbnail->alpha;
animation_entry.cb = NULL;
animation_entry.userdata = NULL;
gfx_animation_push(&animation_entry);
}
else
thumbnail_tag->thumbnail->alpha = 1.0f;
end:
/* Clean up */
if (img)
{
image_texture_free(img);
free(img);
}
if (thumbnail_tag)
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_get_ptr();
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
)
{
const char *thumbnail_path = NULL;
bool has_thumbnail = false;
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))
has_thumbnail = gfx_thumbnail_get_path(path_data, thumbnail_id, &thumbnail_path);
/* Load thumbnail, if required */
if (has_thumbnail)
{
if (path_is_valid(thumbnail_path))
{
gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr();
gfx_thumbnail_tag_t *thumbnail_tag =
(gfx_thumbnail_tag_t*)calloc(1, sizeof(gfx_thumbnail_tag_t));
if (!thumbnail_tag)
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(
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};
if (!playlist)
return;
/* Get current image name */
if (!gfx_thumbnail_get_img_name(path_data, &img_name))
return;
/* 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))
return;
strlcpy(last_img_name, img_name, sizeof(last_img_name));
/* Get system name */
if (!gfx_thumbnail_get_system(path_data, &system))
return;
/* Trigger thumbnail download */
task_push_pl_entry_thumbnail_download(
system, playlist, (unsigned)idx,
false, true);
}
#endif
}
}
/* 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_get_ptr();
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))
return;
if (!path_is_valid(file_path))
return;
/* Load thumbnail */
thumbnail_tag = (gfx_thumbnail_tag_t*)calloc(1, sizeof(gfx_thumbnail_tag_t));
if (!thumbnail_tag)
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;
if (thumbnail->texture)
{
gfx_animation_ctx_tag tag = (uintptr_t)&thumbnail->alpha;
/* Unload texture */
video_driver_texture_unload(&thumbnail->texture);
/* Ensure any 'fade in' animation is killed */
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;
}
/* Stream processing */
/* 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, 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_get_ptr();
/* Check if stream delay timer has elapsed */
thumbnail->delay_timer += gfx_animation_get_delta_time();
if (thumbnail->delay_timer > p_gfx_thumb->stream_delay)
{
/* Sanity check */
if (!path_data || !playlist)
return;
/* Update thumbnail content */
if (!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;
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,
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_get_ptr();
float delta_time = gfx_animation_get_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 || !playlist)
return;
/* Update thumbnail content */
if (!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;
}
if (request_left)
{
gfx_thumbnail_reset(left_thumbnail);
left_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
}
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 display_aspect;
float thumbnail_aspect;
/* Sanity check */
if (!thumbnail || (width < 1) || (height < 1))
goto error;
if ((thumbnail->width < 1) || (thumbnail->height < 1))
goto error;
/* Account for display/thumbnail aspect ratio
* differences */
display_aspect = (float)width / (float)height;
thumbnail_aspect = (float)thumbnail->width / (float)thumbnail->height;
if (thumbnail_aspect > display_aspect)
{
*draw_width = (float)width;
*draw_height = (float)thumbnail->height * (*draw_width / (float)thumbnail->width);
}
else
{
*draw_height = (float)height;
*draw_width = (float)thumbnail->width * (*draw_height / (float)thumbnail->height);
}
/* 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;
return;
error:
*draw_width = 0.0f;
*draw_height = 0.0f;
return;
}
/* 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)
{
/* Sanity check */
if (!thumbnail ||
(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_rotate_draw_t rotate_draw;
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;
else 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);
gfx_display_blend_begin(userdata);
/* 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...
* Note that we also disable scaling here (scale_enable),
* since we handle scaling internally... */
rotate_draw.matrix = &mymat;
rotate_draw.rotation = 0.0f;
rotate_draw.scale_x = 1.0f;
rotate_draw.scale_y = 1.0f;
rotate_draw.scale_z = 1.0f;
rotate_draw.scale_enable = false;
gfx_display_rotate_z(&rotate_draw, 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 */
gfx_display_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 */
gfx_display_draw(&draw, userdata,
video_width, video_height);
gfx_display_blend_end(userdata);
}
}