A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1213 lines 34 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * user intereface of image viewer 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21 22/* 23 * TODO: 24 * - check magick value in file header to determine image type. 25 */ 26#include "plugin.h" 27#include <lib/playback_control.h> 28#include <lib/helper.h> 29#include <lib/configfile.h> 30#include "imageviewer.h" 31#include "imageviewer_button.h" 32#include "image_decoder.h" 33 34 35#ifdef USEGSLIB 36GREY_INFO_STRUCT 37#endif 38 39/* Headings */ 40#define DIR_PREV 1 41#define DIR_NEXT -1 42#define DIR_NONE 0 43 44/******************************* Globals ***********************************/ 45 46/* Persistent configuration */ 47#define IMGVIEW_CONFIGFILE "imageviewer.cfg" 48#define IMGVIEW_SETTINGS_MINVERSION 1 49#define IMGVIEW_SETTINGS_VERSION 2 50 51/* Slideshow times */ 52#define SS_MIN_TIMEOUT 1 53#define SS_MAX_TIMEOUT 20 54#define SS_DEFAULT_TIMEOUT 5 55 56#ifdef HAVE_LCD_COLOR 57/* needed for value of settings */ 58#include "jpeg/yuv2rgb.h" 59#endif 60 61static struct imgview_settings settings = 62{ 63#ifdef HAVE_LCD_COLOR 64 COLOURMODE_COLOUR, 65 DITHER_NONE, 66#endif 67 SS_DEFAULT_TIMEOUT 68}; 69static struct imgview_settings old_settings; 70 71static struct configdata config[] = 72{ 73#ifdef HAVE_LCD_COLOR 74 { TYPE_ENUM, 0, COLOUR_NUM_MODES, { .int_p = &settings.jpeg_colour_mode }, 75 "Colour Mode", (char *[]){ "Colour", "Grayscale" } }, 76 { TYPE_ENUM, 0, DITHER_NUM_MODES, { .int_p = &settings.jpeg_dither_mode }, 77 "Dither Mode", (char *[]){ "None", "Ordered", "Diffusion" } }, 78#endif 79 { TYPE_INT, SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, 80 { .int_p = &settings.ss_timeout }, "Slideshow Time", NULL }, 81}; 82 83static void cb_progress(int current, int total); 84 85static struct imgdec_api iv_api = { 86 .settings = &settings, 87 .slideshow_enabled = false, 88 .running_slideshow = false, 89#ifdef DISK_SPINDOWN 90 .immediate_ata_off = false, 91#endif 92#ifdef USE_PLUG_BUF 93 .plug_buf = true, 94#endif 95 96 .cb_progress = cb_progress, 97 98#ifdef USEGSLIB 99 .gray_bitmap_part = myxlcd_ub_(gray_bitmap_part), 100#endif 101}; 102 103/**************** begin Application ********************/ 104 105 106/************************* Globals ***************************/ 107 108#ifdef HAVE_LCD_COLOR 109static fb_data rgb_linebuf[LCD_WIDTH]; /* Line buffer for scrolling when 110 DITHER_DIFFUSION is set */ 111#endif 112 113/* buffer to load image decoder */ 114static unsigned char* decoder_buf; 115static size_t decoder_buf_size; 116/* the remaining free part of the buffer for loaded+resized images */ 117static unsigned char* buf; 118static size_t buf_size; 119 120static int ds, ds_min, ds_max; /* downscaling and limits */ 121static struct image_info image_info; 122 123/* the current full file name */ 124static char np_file[MAX_PATH]; 125static int curfile = -1, direction = DIR_NEXT, entries = 0; 126 127/* list of the supported image files */ 128static char **file_pt; 129 130/* progress update tick */ 131static long next_progress_tick; 132 133static const struct image_decoder *imgdec = NULL; 134static enum image_type image_type = IMAGE_UNKNOWN; 135 136/************************* Implementation ***************************/ 137 138/* Read directory contents for scrolling. */ 139static void get_pic_list(bool single_file) 140{ 141 file_pt = (char **) buf; 142 143 if (single_file) 144 { 145 file_pt[0] = np_file; 146 buf_size -= sizeof(file_pt); 147 entries = 1; 148 curfile = 0; 149 return; 150 } 151 152 struct tree_context *tree = rb->tree_get_context(); 153 struct entry *dircache = rb->tree_get_entries(tree); 154 int i; 155 char *pname; 156 157 /* Remove path and leave only the name.*/ 158 pname = rb->strrchr(np_file,'/'); 159 pname++; 160 161 for (i = 0; i < tree->filesindir && buf_size > sizeof(char**); i++) 162 { 163 /* Add all files. Non-image files will be filtered out while loading. */ 164 if (!(dircache[i].attr & ATTR_DIRECTORY)) 165 { 166 file_pt[entries] = dircache[i].name; 167 /* Set Selected File. */ 168 if (!rb->strcmp(file_pt[entries], pname)) 169 curfile = entries; 170 entries++; 171 172 buf += (sizeof(char**)); 173 buf_size -= (sizeof(char**)); 174 } 175 } 176} 177 178static int change_filename(int direct) 179{ 180 bool file_erased = (file_pt[curfile] == NULL); 181 direction = direct; 182 183 curfile += (direct == DIR_PREV? entries - 1: 1); 184 if (curfile >= entries) 185 curfile -= entries; 186 187 if (file_erased) 188 { 189 /* remove 'erased' file names from list. */ 190 int count, i; 191 for (count = i = 0; i < entries; i++) 192 { 193 if (curfile == i) 194 curfile = count; 195 if (file_pt[i] != NULL) 196 file_pt[count++] = file_pt[i]; 197 } 198 entries = count; 199 } 200 201 if (entries == 0) 202 { 203 rb->splash(HZ, "No supported files"); 204 return PLUGIN_ERROR; 205 } 206 207 size_t np_file_length = rb->strlen(np_file); 208 size_t np_file_name_length = rb->strlen(rb->strrchr(np_file, '/')+1); 209 size_t avail_length = sizeof(np_file) - (np_file_length - np_file_name_length); 210 211 rb->snprintf(rb->strrchr(np_file, '/')+1, avail_length, "%s", file_pt[curfile]); 212 213 return PLUGIN_OTHER; 214} 215 216/* switch off overlay, for handling SYS_ events */ 217static void cleanup(void *parameter) 218{ 219 (void)parameter; 220#ifdef USEGSLIB 221 grey_show(false); 222#endif 223} 224 225#ifdef HAVE_LCD_COLOR 226static bool set_option_grayscale(void) 227{ 228 bool gray = settings.jpeg_colour_mode == COLOURMODE_GRAY; 229 rb->set_bool("Grayscale (Jpeg)", &gray); 230 settings.jpeg_colour_mode = gray ? COLOURMODE_GRAY : COLOURMODE_COLOUR; 231 return false; 232} 233 234static bool set_option_dithering(void) 235{ 236 static const struct opt_items dithering[DITHER_NUM_MODES] = { 237 [DITHER_NONE] = { STR(LANG_OFF) }, 238 [DITHER_ORDERED] = { STR(LANG_ORDERED) }, 239 [DITHER_DIFFUSION] = { STR(LANG_DIFFUSION) }, 240 }; 241 242 rb->set_option(rb->str(LANG_DITHERING), &settings.jpeg_dither_mode, RB_INT, 243 dithering, DITHER_NUM_MODES, NULL); 244 return false; 245} 246 247MENUITEM_FUNCTION(grayscale_item, 0, ID2P(LANG_GRAYSCALE), 248 set_option_grayscale, NULL, Icon_NOICON); 249MENUITEM_FUNCTION(dithering_item, 0, ID2P(LANG_DITHERING), 250 set_option_dithering, NULL, Icon_NOICON); 251MAKE_MENU(display_menu, "Display Options", NULL, Icon_NOICON, 252 &grayscale_item, &dithering_item); 253 254static void display_options(void) 255{ 256 rb->do_menu(&display_menu, NULL, NULL, false); 257} 258#endif /* HAVE_LCD_COLOR */ 259 260static int show_menu(void) /* return 1 to quit */ 261{ 262 int result; 263 264 enum menu_id 265 { 266 MIID_RETURN = 0, 267 MIID_TOGGLE_SS_MODE, 268 MIID_CHANGE_SS_MODE, 269#ifdef USE_PLUG_BUF 270 MIID_SHOW_PLAYBACK_MENU, 271#endif 272#ifdef HAVE_LCD_COLOR 273 MIID_DISPLAY_OPTIONS, 274#endif 275 MIID_QUIT, 276 }; 277 278 MENUITEM_STRINGLIST(menu, "Image Viewer Menu", NULL, 279 ID2P(LANG_RETURN), 280 ID2P(LANG_SLIDESHOW_MODE), 281 ID2P(LANG_SLIDESHOW_TIME), 282#ifdef USE_PLUG_BUF 283 ID2P(LANG_PLAYBACK_CONTROL), 284#endif 285#ifdef HAVE_LCD_COLOR 286 ID2P(LANG_MENU_DISPLAY_OPTIONS), 287#endif 288 ID2P(LANG_MENU_QUIT)); 289 290 static const struct opt_items slideshow[2] = { 291 { STR(LANG_OFF) }, 292 { STR(LANG_ON) }, 293 }; 294 295 result=rb->do_menu(&menu, NULL, NULL, false); 296 297 switch (result) 298 { 299 case MIID_RETURN: 300 break; 301 case MIID_TOGGLE_SS_MODE: 302 rb->set_option(rb->str(LANG_SLIDESHOW_MODE), &iv_api.slideshow_enabled, RB_BOOL, 303 slideshow , 2, NULL); 304 break; 305 case MIID_CHANGE_SS_MODE: 306 rb->set_int(rb->str(LANG_SLIDESHOW_TIME), "s", UNIT_SEC, 307 &settings.ss_timeout, NULL, 1, 308 SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, NULL); 309 break; 310 311#ifdef USE_PLUG_BUF 312 case MIID_SHOW_PLAYBACK_MENU: 313 if (iv_api.plug_buf) 314 { 315 playback_control(NULL); 316 } 317 else 318 { 319 rb->splash(HZ, ID2P(LANG_CANNOT_RESTART_PLAYBACK)); 320 } 321 break; 322#endif 323#ifdef HAVE_LCD_COLOR 324 case MIID_DISPLAY_OPTIONS: 325 display_options(); 326 break; 327#endif 328 case MIID_QUIT: 329 return 1; 330 break; 331 } 332 333#ifdef DISK_SPINDOWN 334 /* change ata spindown time based on slideshow time setting */ 335 iv_api.immediate_ata_off = false; 336 rb->storage_spindown(rb->global_settings->disk_spindown); 337 338 if (iv_api.slideshow_enabled) 339 { 340 if(settings.ss_timeout < 10) 341 { 342 /* slideshow times < 10s keep disk spinning */ 343 rb->storage_spindown(0); 344 } 345 else if (!rb->pcm_is_playing()) 346 { 347 /* slideshow times > 10s and not playing: ata_off after load */ 348 iv_api.immediate_ata_off = true; 349 } 350 } 351#endif 352#if LCD_DEPTH > 1 353 rb->lcd_set_backdrop(NULL); 354 rb->lcd_set_foreground(LCD_WHITE); 355 rb->lcd_set_background(LCD_BLACK); 356#endif 357 rb->lcd_clear_display(); 358 return 0; 359} 360 361#ifdef USE_PLUG_BUF 362static int ask_and_get_audio_buffer(const char *filename) 363{ 364 int button; 365#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_QUIT_PRE) 366 int lastbutton = BUTTON_NONE; 367#endif 368 rb->lcd_setfont(FONT_SYSFIXED); 369 rb->lcd_clear_display(); 370 rb->lcd_puts(0, 0, rb->strrchr(filename,'/')+1); 371 rb->lcd_puts(0, 1, "Not enough plugin memory!"); 372 rb->lcd_puts(0, 2, "Zoom In: Stop playback."); 373 if(entries > 1) 374 rb->lcd_puts(0, 3, "Left/Right: Skip File."); 375 rb->lcd_puts(0, 4, "Show Menu: Quit."); 376 rb->lcd_update(); 377 rb->lcd_setfont(FONT_UI); 378 379 rb->button_clear_queue(); 380 381 while (1) 382 { 383 if (iv_api.slideshow_enabled) 384 button = rb->button_get_w_tmo(settings.ss_timeout * HZ); 385 else 386 button = rb->button_get(true); 387 388 switch(button) 389 { 390 case IMGVIEW_ZOOM_IN: 391#ifdef IMGVIEW_ZOOM_PRE 392 if (lastbutton != IMGVIEW_ZOOM_PRE) 393 break; 394#endif 395 iv_api.plug_buf = false; 396 buf = rb->plugin_get_audio_buffer(&buf_size); 397 /*try again this file, now using the audio buffer */ 398 return PLUGIN_OTHER; 399#ifdef IMGVIEW_RC_MENU 400 case IMGVIEW_RC_MENU: 401#endif 402#ifdef IMGVIEW_QUIT 403 case IMGVIEW_QUIT: 404#ifdef IMGVIEW_QUIT_PRE 405 if (lastbutton != IMGVIEW_QUIT_PRE) 406 break; 407#endif 408#endif 409 /* Intentional fallthrough */ 410 case IMGVIEW_MENU: 411 return PLUGIN_OK; 412 413 case IMGVIEW_LEFT: 414 if(entries>1) 415 { 416 rb->lcd_clear_display(); 417 return change_filename(DIR_PREV); 418 } 419 break; 420 421 case IMGVIEW_RIGHT: 422 if(entries>1) 423 { 424 rb->lcd_clear_display(); 425 return change_filename(DIR_NEXT); 426 } 427 break; 428 case BUTTON_NONE: 429 if(entries>1) 430 { 431 rb->lcd_clear_display(); 432 return change_filename(direction); 433 } 434 break; 435 436 default: 437 if(rb->default_event_handler_ex(button, cleanup, NULL) 438 == SYS_USB_CONNECTED) 439 return PLUGIN_USB_CONNECTED; 440 } 441#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_QUIT_PRE) 442 if (button != BUTTON_NONE) 443 lastbutton = button; 444#endif 445 } 446} 447#endif /* USE_PLUG_BUF */ 448 449/* callback updating a progress meter while image decoding */ 450static void cb_progress(int current, int total) 451{ 452 /* do not yield or update the progress bar if we did so too recently */ 453 long now = *rb->current_tick; 454 if(!TIME_AFTER(now, next_progress_tick)) 455 return; 456 457 /* limit to 20fps */ 458 next_progress_tick = now + HZ/20; 459 460#ifndef USEGSLIB 461 /* in slideshow mode, keep gui interference to a minimum */ 462 const int size = (!iv_api.running_slideshow ? 8 : 4); 463#else 464 const int size = 8; 465 if(!iv_api.running_slideshow) 466#endif 467 { 468 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN], 469 0, LCD_HEIGHT-size, LCD_WIDTH, size, 470 total, 0, current, HORIZONTAL); 471 rb->lcd_update_rect(0, LCD_HEIGHT-size, LCD_WIDTH, size); 472 } 473 474 rb->yield(); /* be nice to the other threads */ 475} 476 477#define VSCROLL (LCD_HEIGHT/8) 478#define HSCROLL (LCD_WIDTH/10) 479 480/* Pan the viewing window right - move image to the left and fill in 481 the right-hand side */ 482static void pan_view_right(struct image_info *info) 483{ 484 int move; 485 486 move = MIN(HSCROLL, info->width - info->x - LCD_WIDTH); 487 if (move > 0) 488 { 489 mylcd_ub_scroll_left(move); /* scroll left */ 490 info->x += move; 491 imgdec->draw_image_rect(info, LCD_WIDTH - move, 0, 492 move, info->height-info->y); 493 mylcd_ub_update(); 494 } 495} 496 497/* Pan the viewing window left - move image to the right and fill in 498 the left-hand side */ 499static void pan_view_left(struct image_info *info) 500{ 501 int move; 502 503 move = MIN(HSCROLL, info->x); 504 if (move > 0) 505 { 506 mylcd_ub_scroll_right(move); /* scroll right */ 507 info->x -= move; 508 imgdec->draw_image_rect(info, 0, 0, move, info->height-info->y); 509 mylcd_ub_update(); 510 } 511} 512 513/* Pan the viewing window up - move image down and fill in 514 the top */ 515static void pan_view_up(struct image_info *info) 516{ 517 int move; 518 519 move = MIN(VSCROLL, info->y); 520 if (move > 0) 521 { 522 mylcd_ub_scroll_down(move); /* scroll down */ 523 info->y -= move; 524#ifdef HAVE_LCD_COLOR 525 if (image_type == IMAGE_JPEG 526 && settings.jpeg_dither_mode == DITHER_DIFFUSION) 527 { 528 /* Draw over the band at the top of the last update 529 caused by lack of error history on line zero. */ 530 move = MIN(move + 1, info->y + info->height); 531 } 532#endif 533 imgdec->draw_image_rect(info, 0, 0, info->width-info->x, move); 534 mylcd_ub_update(); 535 } 536} 537 538/* Pan the viewing window down - move image up and fill in 539 the bottom */ 540static void pan_view_down(struct image_info *info) 541{ 542 static fb_data *lcd_fb = NULL; 543 if (!lcd_fb) 544 { 545 struct viewport *vp_main = *(rb->screens[SCREEN_MAIN]->current_viewport); 546 lcd_fb = vp_main->buffer->fb_ptr; 547 } 548 549 int move; 550 551 move = MIN(VSCROLL, info->height - info->y - LCD_HEIGHT); 552 if (move > 0) 553 { 554 mylcd_ub_scroll_up(move); /* scroll up */ 555 info->y += move; 556#ifdef HAVE_LCD_COLOR 557 if (image_type == IMAGE_JPEG 558 && settings.jpeg_dither_mode == DITHER_DIFFUSION) 559 { 560 /* Save the line that was on the last line of the display 561 and draw one extra line above then recover the line with 562 image data that had an error history when it was drawn. 563 */ 564 move++, info->y--; 565 rb->memcpy(rgb_linebuf, 566 lcd_fb + (LCD_HEIGHT - move)*LCD_WIDTH, 567 LCD_WIDTH*sizeof (fb_data)); 568 } 569#endif 570 571 imgdec->draw_image_rect(info, 0, LCD_HEIGHT - move, 572 info->width-info->x, move); 573 574#ifdef HAVE_LCD_COLOR 575 if (image_type == IMAGE_JPEG 576 && settings.jpeg_dither_mode == DITHER_DIFFUSION) 577 { 578 /* Cover the first row drawn with previous image data. */ 579 rb->memcpy(lcd_fb + (LCD_HEIGHT - move)*LCD_WIDTH, 580 rgb_linebuf, LCD_WIDTH*sizeof (fb_data)); 581 info->y++; 582 } 583#endif 584 mylcd_ub_update(); 585 } 586} 587 588/* interactively scroll around the image */ 589static int scroll_bmp(struct image_info *info, bool initial_frame) 590{ 591 static long ss_timeout = 0; 592 593 int button; 594#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_MENU_PRE) \ 595 || defined(IMGVIEW_SLIDE_SHOW_PRE) || defined(IMGVIEW_QUIT_PRE) 596 static int lastbutton; 597 if (initial_frame) 598 lastbutton = BUTTON_NONE; 599 600#else 601 (void) initial_frame; 602#endif 603 604 if (!ss_timeout && iv_api.slideshow_enabled) 605 ss_timeout = *rb->current_tick + settings.ss_timeout * HZ; 606 607 while (true) 608 { 609 if (iv_api.slideshow_enabled) 610 { 611 if (info->frames_count > 1 && info->delay && 612 settings.ss_timeout * HZ > info->delay) 613 { 614 /* animated content and delay between subsequent frames 615 * is shorter then slideshow delay 616 */ 617 button = rb->button_get_w_tmo(info->delay); 618 } 619 else 620 button = rb->button_get_w_tmo(settings.ss_timeout * HZ); 621 } 622 else 623 { 624 if (info->frames_count > 1 && info->delay) 625 button = rb->button_get_w_tmo(info->delay); 626 else 627 button = rb->button_get(true); 628 } 629 630 iv_api.running_slideshow = false; 631 632 switch(button) 633 { 634 case IMGVIEW_LEFT: 635 if (entries > 1 && info->width <= LCD_WIDTH 636 && info->height <= LCD_HEIGHT) 637 { 638 int result = change_filename(DIR_PREV); 639 if (entries > 1) 640 return result; 641 } 642 /* fallthrough */ 643 case IMGVIEW_LEFT | BUTTON_REPEAT: 644 pan_view_left(info); 645 break; 646 647 case IMGVIEW_RIGHT: 648 if (entries > 1 && info->width <= LCD_WIDTH 649 && info->height <= LCD_HEIGHT) 650 { 651 int result = change_filename(DIR_NEXT); 652 if (entries > 1) 653 return result; 654 } 655 /* fallthrough */ 656 case IMGVIEW_RIGHT | BUTTON_REPEAT: 657 pan_view_right(info); 658 break; 659 660 case IMGVIEW_UP: 661 case IMGVIEW_UP | BUTTON_REPEAT: 662#ifdef IMGVIEW_SCROLL_UP 663 case IMGVIEW_SCROLL_UP: 664 case IMGVIEW_SCROLL_UP | BUTTON_REPEAT: 665#endif 666 pan_view_up(info); 667 break; 668 669 case IMGVIEW_DOWN: 670 case IMGVIEW_DOWN | BUTTON_REPEAT: 671#ifdef IMGVIEW_SCROLL_DOWN 672 case IMGVIEW_SCROLL_DOWN: 673 case IMGVIEW_SCROLL_DOWN | BUTTON_REPEAT: 674#endif 675 pan_view_down(info); 676 break; 677 678 case BUTTON_NONE: 679 if (iv_api.slideshow_enabled && entries > 1) 680 { 681 if (info->frames_count > 1) 682 { 683 /* animations */ 684 if (TIME_AFTER(*rb->current_tick, ss_timeout)) 685 { 686 iv_api.running_slideshow = true; 687 ss_timeout = 0; 688 return change_filename(DIR_NEXT); 689 } 690 else 691 return NEXT_FRAME; 692 } 693 else 694 { 695 /* still picture */ 696 iv_api.running_slideshow = true; 697 return change_filename(DIR_NEXT); 698 } 699 } 700 else 701 return NEXT_FRAME; 702 703 break; 704 705#ifdef IMGVIEW_SLIDE_SHOW 706 case IMGVIEW_SLIDE_SHOW: 707#ifdef IMGVIEW_SLIDE_SHOW_PRE 708 if (lastbutton != IMGVIEW_SLIDE_SHOW_PRE) 709 break; 710#endif 711#ifdef IMGVIEW_SLIDE_SHOW2 712 case IMGVIEW_SLIDE_SHOW2: 713#endif 714 iv_api.slideshow_enabled = !iv_api.slideshow_enabled; 715 break; 716#endif 717 718#ifdef IMGVIEW_NEXT_REPEAT 719 case IMGVIEW_NEXT_REPEAT: 720#endif 721 case IMGVIEW_NEXT: 722 if (entries > 1) 723 return change_filename(DIR_NEXT); 724 break; 725 726#ifdef IMGVIEW_PREVIOUS_REPEAT 727 case IMGVIEW_PREVIOUS_REPEAT: 728#endif 729 case IMGVIEW_PREVIOUS: 730 if (entries > 1) 731 return change_filename(DIR_PREV); 732 break; 733 734 case IMGVIEW_ZOOM_IN: 735#ifdef IMGVIEW_ZOOM_PRE 736 if (lastbutton != IMGVIEW_ZOOM_PRE) 737 break; 738 lastbutton = button; 739#endif 740 return ZOOM_IN; 741 break; 742 743 case IMGVIEW_ZOOM_OUT: 744#ifdef IMGVIEW_ZOOM_PRE 745 if (lastbutton != IMGVIEW_ZOOM_PRE) 746 break; 747 lastbutton = button; 748#endif 749 return ZOOM_OUT; 750 break; 751 752#ifdef IMGVIEW_RC_MENU 753 case IMGVIEW_RC_MENU: 754#endif 755 case IMGVIEW_MENU: 756#ifdef IMGVIEW_MENU_PRE 757 if (lastbutton != IMGVIEW_MENU_PRE) 758 break; 759#endif 760#ifdef USEGSLIB 761 grey_show(false); /* switch off greyscale overlay */ 762#endif 763 if (show_menu() == 1) 764 return PLUGIN_OK; 765 766#ifdef USEGSLIB 767 grey_show(true); /* switch on greyscale overlay */ 768#else 769 imgdec->draw_image_rect(info, 0, 0, 770 info->width-info->x, info->height-info->y); 771 mylcd_ub_update(); 772#endif 773 break; 774 775#ifdef IMGVIEW_QUIT 776 case IMGVIEW_QUIT: 777#ifdef IMGVIEW_QUIT_PRE 778 if (lastbutton != IMGVIEW_QUIT_PRE) 779 break; 780#endif 781 return PLUGIN_OK; 782 break; 783#endif 784 785 default: 786 if (rb->default_event_handler_ex(button, cleanup, NULL) 787 == SYS_USB_CONNECTED) 788 return PLUGIN_USB_CONNECTED; 789 break; 790 791 } /* switch */ 792#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_MENU_PRE) ||\ 793 defined(IMGVIEW_SLIDE_SHOW_PRE) || defined(IMGVIEW_QUIT_PRE) 794 if (button != BUTTON_NONE) 795 lastbutton = button; 796#endif 797 } /* while (true) */ 798} 799 800/********************* main function *************************/ 801 802/* how far can we zoom in without running out of memory */ 803static int min_downscale(int bufsize) 804{ 805 int downscale = 8; 806 807 if (imgdec->img_mem(8) > bufsize) 808 return 0; /* error, too large, even 1:8 doesn't fit */ 809 810 while (downscale > 1 && imgdec->img_mem(downscale/2) <= bufsize) 811 downscale /= 2; 812 813 return downscale; 814} 815 816/* how far can we zoom out, to fit image into the LCD */ 817static int max_downscale(struct image_info *info) 818{ 819 int downscale = 1; 820 821 while (downscale < 8 && (info->x_size/downscale > LCD_WIDTH 822 || info->y_size/downscale > LCD_HEIGHT)) 823 { 824 downscale *= 2; 825 } 826 827 return downscale; 828} 829 830/* set the view to the given center point, limit if necessary */ 831static void set_view(struct image_info *info, int cx, int cy) 832{ 833 int x, y; 834 835 /* plain center to available width/height */ 836 x = cx - MIN(LCD_WIDTH, info->width) / 2; 837 y = cy - MIN(LCD_HEIGHT, info->height) / 2; 838 839 /* limit against upper image size */ 840 x = MIN(info->width - LCD_WIDTH, x); 841 y = MIN(info->height - LCD_HEIGHT, y); 842 843 /* limit against negative side */ 844 x = MAX(0, x); 845 y = MAX(0, y); 846 847 info->x = x; /* set the values */ 848 info->y = y; 849} 850 851/* calculate the view center based on the bitmap position */ 852static void get_view(struct image_info *info, int *p_cx, int *p_cy) 853{ 854 *p_cx = info->x + MIN(LCD_WIDTH, info->width) / 2; 855 *p_cy = info->y + MIN(LCD_HEIGHT, info->height) / 2; 856} 857 858/* load, decode, display the image */ 859static int load_and_show(char *filename, struct image_info *info, 860 int offset, int filesize, int status) 861{ 862 int cx, cy; 863 ssize_t remaining; 864 865 if (status == IMAGE_UNKNOWN) { 866 /* file isn't supported image file, skip this. */ 867 file_pt[curfile] = NULL; 868 return change_filename(direction); 869 } 870 871reload_decoder: 872 rb->lcd_clear_display(); 873 874 if (image_type != status) /* type of image is changed, load decoder. */ 875 { 876 struct loader_info loader_info = { 877 status, &iv_api, decoder_buf, decoder_buf_size, 878 }; 879 image_type = status; 880 imgdec = load_decoder(&loader_info); 881 if (imgdec == NULL) 882 { 883 /* something is wrong */ 884 return PLUGIN_ERROR; 885 } 886#ifdef USE_PLUG_BUF 887 if(iv_api.plug_buf) 888 { 889 buf = loader_info.buffer; 890 buf_size = loader_info.size; 891 } 892#endif 893 } 894 rb->memset(info, 0, sizeof(*info)); 895 remaining = buf_size; 896 897 if (rb->button_get(false) == IMGVIEW_MENU) 898 status = PLUGIN_ABORT; 899 else 900 status = imgdec->load_image(filename, info, buf, &remaining, offset, filesize); 901 902 if (status == PLUGIN_JPEG_PROGRESSIVE) 903 { 904 status = IMAGE_JPEG_PROGRESSIVE; 905 goto reload_decoder; 906 } 907 908 if (status == PLUGIN_OUTOFMEM) 909 { 910#ifdef USE_PLUG_BUF 911 if(iv_api.plug_buf) 912 { 913 return ask_and_get_audio_buffer(filename); 914 } 915 else 916#endif 917 { 918 rb->splash(HZ, "Out of Memory"); 919 file_pt[curfile] = NULL; 920 return change_filename(direction); 921 } 922 } 923 else if (status == PLUGIN_ERROR) 924 { 925 file_pt[curfile] = NULL; 926 return change_filename(direction); 927 } 928 else if (status == PLUGIN_ABORT) { 929 rb->splash(HZ, "Aborted"); 930 return PLUGIN_OK; 931 } 932 933 ds_max = max_downscale(info); /* check display constraint */ 934 ds_min = min_downscale(remaining); /* check memory constraint */ 935 if (ds_min == 0) 936 { 937 if (imgdec->unscaled_avail) 938 { 939 /* Can not resize the image but original one is available, so use it. */ 940 ds_min = ds_max = 1; 941 } 942 else 943#ifdef USE_PLUG_BUF 944 if (iv_api.plug_buf) 945 { 946 return ask_and_get_audio_buffer(filename); 947 } 948 else 949#endif 950 { 951 rb->splash(HZ, "Too large"); 952 file_pt[curfile] = NULL; 953 return change_filename(direction); 954 } 955 } 956 else if (ds_max < ds_min) 957 ds_max = ds_min; 958 959 ds = ds_max; /* initialize setting */ 960 cx = info->x_size/ds/2; /* center the view */ 961 cy = info->y_size/ds/2; 962 963 /* used to loop through subimages in animated gifs */ 964 int frame = 0; 965 bool initial_frame = true; 966 do /* loop the image prepare and decoding when zoomed */ 967 { 968 status = imgdec->get_image(info, frame, ds); /* decode or fetch from cache */ 969 if (status == PLUGIN_ERROR) 970 { 971 file_pt[curfile] = NULL; 972 return change_filename(direction); 973 } 974 975 set_view(info, cx, cy); 976 977 if(!iv_api.running_slideshow && (info->frames_count == 1)) 978 { 979 rb->lcd_putsf(0, 3, "showing %dx%d", info->width, info->height); 980 rb->lcd_update(); 981 } 982 983 if (frame == 0) 984 mylcd_ub_clear_display(); 985 imgdec->draw_image_rect(info, 0, 0, 986 info->width-info->x, info->height-info->y); 987 mylcd_ub_update(); 988 989#ifdef USEGSLIB 990 grey_show(true); /* switch on greyscale overlay */ 991#endif 992 993 /* drawing is now finished, play around with scrolling 994 * until you press OFF or connect USB 995 */ 996 while (1) 997 { 998 status = scroll_bmp(info, initial_frame); 999 initial_frame = false; 1000 1001 if (status == ZOOM_IN) 1002 { 1003 if (ds > ds_min || (imgdec->unscaled_avail && ds > 1)) 1004 { 1005 /* if 1/1 is always available, jump ds from ds_min to 1. */ 1006 int zoom = (ds == ds_min)? ds_min: 2; 1007 ds /= zoom; /* reduce downscaling to zoom in */ 1008 get_view(info, &cx, &cy); 1009 cx *= zoom; /* prepare the position in the new image */ 1010 cy *= zoom; 1011 } 1012 else 1013 continue; 1014 } 1015 1016 if (status == ZOOM_OUT) 1017 { 1018 if (ds < ds_max) 1019 { 1020 /* if ds is 1 and ds_min is > 1, jump ds to ds_min. */ 1021 int zoom = (ds < ds_min)? ds_min: 2; 1022 ds *= zoom; /* increase downscaling to zoom out */ 1023 get_view(info, &cx, &cy); 1024 cx /= zoom; /* prepare the position in the new image */ 1025 cy /= zoom; 1026 mylcd_ub_clear_display(); 1027 } 1028 else 1029 continue; 1030 } 1031 1032 /* next frame in animated content */ 1033 if (status == NEXT_FRAME) 1034 frame = (frame + 1)%info->frames_count; 1035 1036 break; 1037 } 1038 1039#ifdef USEGSLIB 1040 if (info->frames_count <= 1) 1041 grey_show(false); /* switch off overlay */ 1042#endif 1043 rb->lcd_clear_display(); 1044 } 1045 while (status > PLUGIN_OTHER); 1046#ifdef USEGSLIB 1047 grey_show(false); /* switch off overlay */ 1048 rb->lcd_update(); 1049#endif 1050 return status; 1051} 1052 1053static bool find_album_art(int *offset, int *filesize, int *status) 1054{ 1055#ifndef HAVE_ALBUMART 1056 (void)offset;(void)filesize;(void)status; 1057 return false; 1058#else 1059 struct mp3entry *current_track = rb->audio_current_track(); 1060 1061 if (current_track == NULL) 1062 { 1063 return false; 1064 } 1065 1066 switch (current_track->albumart.type) 1067 { 1068 case AA_TYPE_BMP: 1069 (*status) = IMAGE_BMP; 1070 break; 1071 case AA_TYPE_PNG: 1072 (*status) = IMAGE_PNG; 1073 break; 1074 case AA_TYPE_JPG: 1075 (*status) = IMAGE_JPEG; 1076 break; 1077 default: 1078 (*status) = IMAGE_UNKNOWN; 1079 } 1080 1081 if (IMAGE_UNKNOWN == *status 1082 || AA_PREFER_IMAGE_FILE == rb->global_settings->album_art) 1083 { 1084 if (rb->search_albumart_files(current_track, "", np_file, MAX_PATH)) 1085 { 1086 (*status) = get_image_type(np_file, false); 1087 return true; 1088 } 1089 1090 if (*status == IMAGE_UNKNOWN) 1091 return false; 1092 } 1093 rb->strcpy(np_file, current_track->path); 1094 (*offset) = current_track->albumart.pos; 1095 (*filesize) = current_track->albumart.size; 1096 return true; 1097#endif 1098} 1099 1100/******************** Plugin entry point *********************/ 1101 1102enum plugin_status plugin_start(const void* parameter) 1103{ 1104 int condition; 1105#ifdef USEGSLIB 1106 long greysize; /* helper */ 1107#endif 1108 1109 int offset = 0, filesize = 0, status; 1110 1111 bool is_album_art = false; 1112 if (!parameter) 1113 { 1114 if (!find_album_art(&offset, &filesize, &status)) 1115 { 1116 rb->splash(HZ * 2, "No file"); 1117 return PLUGIN_ERROR; 1118 } 1119 1120 is_album_art = true; 1121 } 1122 else 1123 { 1124 rb->strcpy(np_file, parameter); 1125 if ((status = get_image_type(np_file, false)) == IMAGE_UNKNOWN) 1126 { 1127 rb->splash(HZ * 2, "Unsupported file"); 1128 return PLUGIN_ERROR; 1129 } 1130 } 1131 1132#ifdef USE_PLUG_BUF 1133 buf = rb->plugin_get_buffer(&buf_size); 1134#else 1135 decoder_buf = rb->plugin_get_buffer(&decoder_buf_size); 1136 buf = rb->plugin_get_audio_buffer(&buf_size); 1137#endif 1138 get_pic_list(is_album_art); 1139 1140#ifdef USEGSLIB 1141 if (!grey_init(buf, buf_size, GREY_ON_COP, 1142 LCD_WIDTH, LCD_HEIGHT, &greysize)) 1143 { 1144 rb->splash(HZ, "grey buf error"); 1145 return PLUGIN_ERROR; 1146 } 1147 buf += greysize; 1148 buf_size -= greysize; 1149#endif 1150 1151#ifdef USE_PLUG_BUF 1152 decoder_buf = buf; 1153 decoder_buf_size = buf_size; 1154 if(!rb->audio_status()) 1155 { 1156 iv_api.plug_buf = false; 1157 buf = rb->plugin_get_audio_buffer(&buf_size); 1158 } 1159#endif 1160 1161 /* should be ok to just load settings since the plugin itself has 1162 just been loaded from disk and the drive should be spinning */ 1163 configfile_load(IMGVIEW_CONFIGFILE, config, 1164 ARRAYLEN(config), IMGVIEW_SETTINGS_MINVERSION); 1165 rb->memcpy(&old_settings, &settings, sizeof (settings)); 1166 1167 /* Turn off backlight timeout */ 1168 backlight_ignore_timeout(); 1169 1170#if LCD_DEPTH > 1 1171 rb->lcd_set_backdrop(NULL); 1172 rb->lcd_set_foreground(LCD_WHITE); 1173 rb->lcd_set_background(LCD_BLACK); 1174#endif 1175 1176 do 1177 { 1178 condition = load_and_show(np_file, &image_info, offset, filesize, status); 1179 if (condition >= PLUGIN_OTHER) 1180 { 1181 if(!is_album_art) 1182 { 1183 /* suppress warning while running slideshow */ 1184 status = get_image_type(np_file, iv_api.running_slideshow); 1185 } 1186 continue; 1187 } 1188 break; 1189 } while (true); 1190 release_decoder(); 1191 1192 if (rb->memcmp(&settings, &old_settings, sizeof (settings))) 1193 { 1194 /* Just in case drive has to spin, keep it from looking locked */ 1195 rb->splash(0, "Saving Settings"); 1196 configfile_save(IMGVIEW_CONFIGFILE, config, 1197 ARRAYLEN(config), IMGVIEW_SETTINGS_VERSION); 1198 } 1199 1200#ifdef DISK_SPINDOWN 1201 /* set back ata spindown time in case we changed it */ 1202 rb->storage_spindown(rb->global_settings->disk_spindown); 1203#endif 1204 1205 /* Turn on backlight timeout (revert to settings) */ 1206 backlight_use_settings(); 1207 1208#ifdef USEGSLIB 1209 grey_release(); /* deinitialize */ 1210#endif 1211 1212 return condition; 1213}