538 lines
14 KiB
C
538 lines
14 KiB
C
/*
|
|
* BPG viewer
|
|
*
|
|
* Copyright (c) 2014-2015 Fabrice Bellard
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <math.h>
|
|
#include <getopt.h>
|
|
#include <inttypes.h>
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
#include <SDL/SDL.h>
|
|
#include <SDL/SDL_image.h>
|
|
|
|
#include "libbpg.h"
|
|
|
|
typedef enum {
|
|
BG_BLACK,
|
|
BG_TILED,
|
|
} BackgroundTypeEnum;
|
|
|
|
typedef struct {
|
|
SDL_Surface *img;
|
|
int delay; /* in ms */
|
|
} Frame;
|
|
|
|
typedef struct {
|
|
int screen_w, screen_h;
|
|
int win_w, win_h;
|
|
SDL_Surface *screen;
|
|
|
|
int img_w, img_h;
|
|
int frame_count;
|
|
Frame *frames;
|
|
int frame_index; /* index of the current frame */
|
|
int loop_counter;
|
|
int loop_count;
|
|
SDL_TimerID frame_timer_id;
|
|
|
|
int is_full_screen;
|
|
int pos_x, pos_y;
|
|
BackgroundTypeEnum background_type;
|
|
} DispContext;
|
|
|
|
static uint32_t timer_cb(uint32_t interval, void *param);
|
|
|
|
static inline int clamp_int(int val, int min_val, int max_val)
|
|
{
|
|
if (val < min_val)
|
|
return min_val;
|
|
else if (val > max_val)
|
|
return max_val;
|
|
else
|
|
return val;
|
|
}
|
|
|
|
Frame *bpg_load(FILE *f, int *pframe_count, int *ploop_count)
|
|
{
|
|
BPGDecoderContext *s;
|
|
BPGImageInfo bi_s, *bi = &bi_s;
|
|
uint8_t *buf;
|
|
int len, y;
|
|
SDL_Surface *img;
|
|
Frame *frames;
|
|
uint32_t rmask, gmask, bmask, amask;
|
|
int frame_count, i, delay_num, delay_den;
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
len = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
if (len < 0)
|
|
return NULL;
|
|
buf = malloc(len);
|
|
if (!buf)
|
|
return NULL;
|
|
if (fread(buf, 1, len, f) != len)
|
|
return NULL;
|
|
|
|
frames = NULL;
|
|
frame_count = 0;
|
|
|
|
s = bpg_decoder_open();
|
|
if (bpg_decoder_decode(s, buf, len) < 0)
|
|
goto fail;
|
|
bpg_decoder_get_info(s, bi);
|
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
rmask = 0xff000000;
|
|
gmask = 0x00ff0000;
|
|
bmask = 0x0000ff00;
|
|
amask = 0x000000ff;
|
|
#else
|
|
rmask = 0x000000ff;
|
|
gmask = 0x0000ff00;
|
|
bmask = 0x00ff0000;
|
|
amask = 0xff000000;
|
|
#endif
|
|
for(;;) {
|
|
if (bpg_decoder_start(s, BPG_OUTPUT_FORMAT_RGBA32) < 0)
|
|
break;
|
|
bpg_decoder_get_frame_duration(s, &delay_num, &delay_den);
|
|
frames = realloc(frames, sizeof(frames[0]) * (frame_count + 1));
|
|
img = SDL_CreateRGBSurface(SDL_HWSURFACE, bi->width, bi->height, 32,
|
|
rmask, gmask, bmask, amask);
|
|
if (!img)
|
|
goto fail;
|
|
|
|
SDL_LockSurface(img);
|
|
for(y = 0; y < bi->height; y++) {
|
|
bpg_decoder_get_line(s, (uint8_t *)img->pixels + y * img->pitch);
|
|
}
|
|
SDL_UnlockSurface(img);
|
|
frames[frame_count].img = img;
|
|
frames[frame_count].delay = (delay_num * 1000) / delay_den;
|
|
frame_count++;
|
|
}
|
|
bpg_decoder_close(s);
|
|
*pframe_count = frame_count;
|
|
*ploop_count = bi->loop_count;
|
|
return frames;
|
|
fail:
|
|
bpg_decoder_close(s);
|
|
for(i = 0; i < frame_count; i++) {
|
|
SDL_FreeSurface(frames[i].img);
|
|
}
|
|
free(frames);
|
|
*pframe_count = 0;
|
|
return NULL;
|
|
}
|
|
|
|
static void restart_frame_timer(DispContext *dc)
|
|
{
|
|
if (dc->frame_timer_id) {
|
|
/* XXX: the SDL timer API is not safe, so we remove the timer even if it already expired */
|
|
SDL_RemoveTimer(dc->frame_timer_id);
|
|
dc->frame_timer_id = 0;
|
|
}
|
|
dc->frame_timer_id =
|
|
SDL_AddTimer(dc->frames[dc->frame_index].delay, timer_cb, NULL);
|
|
}
|
|
|
|
int load_image(DispContext *dc, const char *filename)
|
|
{
|
|
SDL_Surface *img;
|
|
Frame *frames;
|
|
FILE *f;
|
|
uint8_t buf[BPG_DECODER_INFO_BUF_SIZE];
|
|
int len, i, frame_count, loop_count;
|
|
BPGImageInfo bi;
|
|
|
|
f = fopen(filename, "rb");
|
|
if (!f)
|
|
goto fail;
|
|
len = fread(buf, 1, sizeof(buf), f);
|
|
if (bpg_decoder_get_info_from_buf(&bi, NULL, buf, len) >= 0) {
|
|
fseek(f, 0, SEEK_SET);
|
|
frames = bpg_load(f, &frame_count, &loop_count);
|
|
if (!frames)
|
|
goto fail;
|
|
fclose(f);
|
|
} else {
|
|
/* use SDL image loader */
|
|
img = IMG_Load(filename);
|
|
if (!img) {
|
|
fail:
|
|
fprintf(stderr, "Could not load '%s'\n", filename);
|
|
return -1;
|
|
}
|
|
frame_count = 1;
|
|
frames = malloc(sizeof(dc->frames[0]) * frame_count);
|
|
frames[0].img = img;
|
|
frames[0].delay = 0;
|
|
loop_count = 1;
|
|
}
|
|
|
|
for(i = 0; i < dc->frame_count; i++) {
|
|
SDL_FreeSurface(dc->frames[i].img);
|
|
}
|
|
free(dc->frames);
|
|
if (dc->frame_timer_id) {
|
|
SDL_RemoveTimer(dc->frame_timer_id);
|
|
dc->frame_timer_id = 0;
|
|
}
|
|
|
|
dc->frame_count = frame_count;
|
|
dc->frames = frames;
|
|
dc->frame_index = 0;
|
|
dc->loop_counter = 0;
|
|
dc->loop_count = loop_count;
|
|
dc->img_w = dc->frames[0].img->w;
|
|
dc->img_h = dc->frames[0].img->h;
|
|
|
|
/* start the animation timer if needed */
|
|
if (dc->frame_count > 1) {
|
|
restart_frame_timer(dc);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void center_image(DispContext *dc)
|
|
{
|
|
dc->pos_x = clamp_int((dc->screen->w - dc->img_w) / 2, -32767, 32768);
|
|
dc->pos_y = clamp_int((dc->screen->h - dc->img_h) / 2, -32767, 32768);
|
|
}
|
|
|
|
void draw_image(DispContext *dc)
|
|
{
|
|
SDL_Rect r;
|
|
|
|
r.x = 0;
|
|
r.y = 0;
|
|
r.w = dc->screen->w;
|
|
r.h = dc->screen->h;
|
|
SDL_FillRect(dc->screen, &r, SDL_MapRGB(dc->screen->format, 0x00, 0x00, 0x00));
|
|
|
|
if (dc->background_type == BG_TILED) {
|
|
int x, y, tw, w, h, x2, y2, w1, h1, x1, y1;
|
|
uint32_t bgcolors[2];
|
|
|
|
tw = 16;
|
|
w = dc->img_w;
|
|
h = dc->img_h;
|
|
w1 = (w + tw - 1) / tw;
|
|
h1 = (h + tw - 1) / tw;
|
|
bgcolors[0] = SDL_MapRGB(dc->screen->format, 100, 100, 100);
|
|
bgcolors[1] = SDL_MapRGB(dc->screen->format, 150, 150, 150);
|
|
for(y = 0; y < h1; y++) {
|
|
for(x = 0; x < w1; x++) {
|
|
x1 = x * tw;
|
|
y1 = y * tw;
|
|
x2 = x1 + tw;
|
|
y2 = y1 + tw;
|
|
if (x2 > w)
|
|
x2 = w;
|
|
if (y2 > h)
|
|
y2 = h;
|
|
r.x = x1 + dc->pos_x;
|
|
r.y = y1 + dc->pos_y;
|
|
r.w = x2 - x1;
|
|
r.h = y2 - y1;
|
|
SDL_FillRect(dc->screen, &r, bgcolors[(x ^ y) & 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
r.x = dc->pos_x;
|
|
r.y = dc->pos_y;
|
|
r.w = 0;
|
|
r.h = 0;
|
|
SDL_BlitSurface (dc->frames[dc->frame_index].img, NULL, dc->screen, &r);
|
|
|
|
SDL_Flip(dc->screen);
|
|
}
|
|
|
|
void pan_image(DispContext *dc, int dx, int dy)
|
|
{
|
|
int dw, dh;
|
|
|
|
dw = dc->img_w - dc->screen->w;
|
|
dh = dc->img_h - dc->screen->h;
|
|
if (dw > 0) {
|
|
dc->pos_x += dx;
|
|
if (dc->pos_x < -dw)
|
|
dc->pos_x = -dw;
|
|
else if (dc->pos_x > 0)
|
|
dc->pos_x = 0;
|
|
}
|
|
if (dh > 0) {
|
|
dc->pos_y += dy;
|
|
if (dc->pos_y < -dh)
|
|
dc->pos_y = -dh;
|
|
else if (dc->pos_y > 0)
|
|
dc->pos_y = 0;
|
|
}
|
|
draw_image(dc);
|
|
}
|
|
|
|
static void set_caption(DispContext *dc, char **argv,
|
|
int image_index, int image_count)
|
|
{
|
|
char buf[1024];
|
|
const char *filename;
|
|
filename = argv[image_index];
|
|
snprintf(buf, sizeof(buf), "bpgview [%d of %d] - %s",
|
|
image_index + 1, image_count, filename);
|
|
SDL_WM_SetCaption(buf, buf);
|
|
}
|
|
|
|
static void open_window(DispContext *dc, int w, int h, int is_full_screen)
|
|
{
|
|
int flags;
|
|
|
|
flags = SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_HWACCEL;
|
|
if (is_full_screen)
|
|
flags |= SDL_FULLSCREEN;
|
|
else
|
|
flags |= SDL_RESIZABLE;
|
|
|
|
dc->screen = SDL_SetVideoMode(w, h, 32, flags);
|
|
if (!dc->screen) {
|
|
fprintf(stderr, "Could not init screen\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static uint32_t timer_cb(uint32_t interval, void *param)
|
|
{
|
|
SDL_Event event;
|
|
SDL_UserEvent userevent;
|
|
|
|
userevent.type = SDL_USEREVENT;
|
|
userevent.code = 0;
|
|
userevent.data1 = NULL;
|
|
userevent.data2 = NULL;
|
|
|
|
event.type = SDL_USEREVENT;
|
|
event.user = userevent;
|
|
|
|
SDL_PushEvent(&event);
|
|
return 0;
|
|
}
|
|
|
|
#define DEFAULT_W 640
|
|
#define DEFAULT_H 480
|
|
|
|
static void help(void)
|
|
{
|
|
const char *str;
|
|
str = "BPG Image Viewer version " CONFIG_BPG_VERSION "\n"
|
|
"usage: bpgview infile...\n"
|
|
"\n"
|
|
"Keys:\n"
|
|
"q, ESC quit\n"
|
|
"n, SPACE next image\n"
|
|
"p previous image\n"
|
|
"arrows pan\n"
|
|
"c center\n"
|
|
"b toggle background type\n";
|
|
#ifdef WIN32
|
|
MessageBox(NULL, str, "Error", MB_ICONERROR | MB_OK);
|
|
exit(1);
|
|
#else
|
|
printf("%s", str);
|
|
exit(1);
|
|
#endif
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int c, image_index, image_count, incr, i;
|
|
SDL_Event event;
|
|
DispContext dc_s, *dc = &dc_s;
|
|
const SDL_VideoInfo *vi;
|
|
|
|
for(;;) {
|
|
c = getopt(argc, argv, "h");
|
|
if (c == -1)
|
|
break;
|
|
switch(c) {
|
|
case 'h':
|
|
show_help:
|
|
help();
|
|
break;
|
|
default:
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (optind >= argc)
|
|
goto show_help;
|
|
|
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
|
|
fprintf(stderr, "Could not init SDL\n");
|
|
exit(1);
|
|
}
|
|
memset(dc, 0, sizeof(*dc));
|
|
|
|
vi = SDL_GetVideoInfo();
|
|
dc->screen_w = vi->current_w;
|
|
dc->screen_h = vi->current_h;
|
|
dc->is_full_screen = 0;
|
|
|
|
image_count = argc - optind;
|
|
image_index = 0;
|
|
if (load_image(dc, argv[optind + image_index]) < 0)
|
|
exit(1);
|
|
dc->background_type = BG_TILED;
|
|
|
|
{
|
|
int w, h;
|
|
|
|
if (image_count > 1 || (dc->img_w < 256 || dc->img_h < 256)) {
|
|
w = DEFAULT_W;
|
|
h = DEFAULT_H;
|
|
} else {
|
|
w = clamp_int(dc->img_w, 32, dc->screen_w);
|
|
h = clamp_int(dc->img_h, 32, dc->screen_h);
|
|
}
|
|
open_window(dc, w, h, 0);
|
|
set_caption(dc, argv + optind, image_index, image_count);
|
|
}
|
|
|
|
center_image(dc);
|
|
draw_image(dc);
|
|
|
|
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
|
|
|
|
for(;;) {
|
|
if (!SDL_WaitEvent(&event))
|
|
continue;
|
|
switch(event.type) {
|
|
case SDL_KEYDOWN:
|
|
switch (event.key.keysym.sym) {
|
|
case SDLK_ESCAPE:
|
|
case SDLK_q:
|
|
goto done;
|
|
case SDLK_SPACE: /* next image */
|
|
case SDLK_n:
|
|
incr = 1;
|
|
goto prev_next;
|
|
case SDLK_p: /* previous image */
|
|
incr = -1;
|
|
prev_next:
|
|
if (image_count > 1) {
|
|
for(i = 0; i < image_count; i++) {
|
|
image_index += incr;
|
|
if (image_index < 0)
|
|
image_index = image_count - 1;
|
|
else if (image_index >= image_count)
|
|
image_index = 0;
|
|
if (load_image(dc, argv[optind + image_index]) == 0)
|
|
break;
|
|
}
|
|
if (i == image_count)
|
|
exit(1);
|
|
set_caption(dc, argv + optind, image_index, image_count);
|
|
center_image(dc);
|
|
draw_image(dc);
|
|
}
|
|
break;
|
|
case SDLK_LEFT:
|
|
pan_image(dc, 32, 0);
|
|
break;
|
|
case SDLK_RIGHT:
|
|
pan_image(dc, -32, 0);
|
|
break;
|
|
case SDLK_UP:
|
|
pan_image(dc, 0, 32);
|
|
break;
|
|
case SDLK_DOWN:
|
|
pan_image(dc, 0, -32);
|
|
break;
|
|
case SDLK_c:
|
|
center_image(dc);
|
|
draw_image(dc);
|
|
break;
|
|
case SDLK_b:
|
|
dc->background_type ^= 1;
|
|
draw_image(dc);
|
|
break;
|
|
case SDLK_f:
|
|
dc->is_full_screen ^= 1;
|
|
if (dc->is_full_screen) {
|
|
/* save old windows size */
|
|
dc->win_w = dc->screen->w;
|
|
dc->win_h = dc->screen->h;
|
|
open_window(dc, dc->screen_w, dc->screen_h, 1);
|
|
} else {
|
|
open_window(dc, dc->win_w, dc->win_h, 0);
|
|
}
|
|
center_image(dc);
|
|
draw_image(dc);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case SDL_VIDEORESIZE:
|
|
{
|
|
open_window(dc, event.resize.w, event.resize.h, 0);
|
|
center_image(dc);
|
|
draw_image(dc);
|
|
}
|
|
break;
|
|
case SDL_QUIT:
|
|
goto done;
|
|
case SDL_MOUSEMOTION:
|
|
if (event.motion.state) {
|
|
pan_image(dc, event.motion.xrel, event.motion.yrel);
|
|
}
|
|
break;
|
|
case SDL_USEREVENT:
|
|
if (dc->frame_count > 1) {
|
|
/* show next frame */
|
|
if (dc->frame_index == (dc->frame_count - 1)) {
|
|
if (dc->loop_count == 0 ||
|
|
dc->loop_counter < (dc->loop_count - 1)) {
|
|
dc->frame_index = 0;
|
|
dc->loop_counter++;
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
dc->frame_index++;
|
|
}
|
|
draw_image(dc);
|
|
restart_frame_timer(dc);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
done:
|
|
|
|
SDL_FreeSurface(dc->screen);
|
|
return 0;
|
|
}
|