diff --git a/meson.build b/meson.build index e3d9663..e98b542 100644 --- a/meson.build +++ b/meson.build @@ -145,7 +145,6 @@ endif # source files sources = [ - 'src/canvas.c', 'src/config.c', 'src/font.c', 'src/image.c', diff --git a/src/canvas.c b/src/canvas.c deleted file mode 100644 index 61227d7..0000000 --- a/src/canvas.c +++ /dev/null @@ -1,372 +0,0 @@ -// SPDX-License-Identifier: MIT -// Canvas used to render images and text to window buffer. -// Copyright (C) 2022 Artem Senichev - -#include "canvas.h" - -#include "config.h" -#include "str.h" -#include "ui.h" - -#include -#include - -// Background modes -#define COLOR_TRANSPARENT 0xff000000 -#define BACKGROUND_GRID 0xfe000000 - -// Background grid parameters -#define GRID_STEP 10 -#define GRID_COLOR1 0xff333333 -#define GRID_COLOR2 0xff4c4c4c - -// Scale thresholds -#define MIN_SCALE 10 // pixels -#define MAX_SCALE 100.0 // factor - -#define max(x, y) ((x) > (y) ? (x) : (y)) -#define min(x, y) ((x) < (y) ? (x) : (y)) - -/** Scaling operations. */ -enum canvas_scale { - scale_fit_optimal, ///< Fit to window, but not more than 100% - scale_fit_window, ///< Fit to window size - scale_fit_width, ///< Fit width to window width - scale_fit_height, ///< Fit height to window height - scale_fill_window, ///< Fill the window - scale_real_size, ///< Real image size (100%) -}; - -// clang-format off -static const char* scale_names[] = { - [scale_fit_optimal] = "optimal", - [scale_fit_window] = "fit", - [scale_fit_width] = "width", - [scale_fit_height] = "height", - [scale_fill_window] = "fill", - [scale_real_size] = "real", -}; -// clang-format on - -/** Canvas context. */ -struct canvas { - argb_t image_bkg; ///< Image background mode/color - argb_t window_bkg; ///< Window background mode/color - bool antialiasing; ///< Anti-aliasing (bicubic interpolation) - - bool fixed; ///< Fix canvas position - - enum canvas_scale initial_scale; ///< Initial scale - float scale; ///< Current scale factor of the image - - struct rect image; ///< Image position and size -}; - -static struct canvas ctx = { - .image_bkg = BACKGROUND_GRID, - .window_bkg = COLOR_TRANSPARENT, - .fixed = true, -}; - -/** - * Fix canvas position. - */ -static void fix_position(bool force) -{ - const ssize_t wnd_width = ui_get_width(); - const ssize_t wnd_height = ui_get_height(); - const ssize_t img_width = ctx.scale * ctx.image.width; - const ssize_t img_height = ctx.scale * ctx.image.height; - - if (force || ctx.fixed) { - // bind to window border - if (ctx.image.x > 0 && ctx.image.x + img_width > wnd_width) { - ctx.image.x = 0; - } - if (ctx.image.y > 0 && ctx.image.y + img_height > wnd_height) { - ctx.image.y = 0; - } - if (ctx.image.x < 0 && ctx.image.x + img_width < wnd_width) { - ctx.image.x = wnd_width - img_width; - } - if (ctx.image.y < 0 && ctx.image.y + img_height < wnd_height) { - ctx.image.y = wnd_height - img_height; - } - - // centering small image - if (img_width <= wnd_width) { - ctx.image.x = wnd_width / 2 - img_width / 2; - } - if (img_height <= wnd_height) { - ctx.image.y = wnd_height / 2 - img_height / 2; - } - } - - // don't let canvas to be far out of window - if (ctx.image.x + img_width < 0) { - ctx.image.x = -img_width; - } - if (ctx.image.x > wnd_width) { - ctx.image.x = wnd_width; - } - if (ctx.image.y + img_height < 0) { - ctx.image.y = -img_height; - } - if (ctx.image.y > wnd_height) { - ctx.image.y = wnd_height; - } -} - -/** - * Set fixed scale for the image. - * @param sc scale to set - */ -static void set_scale(enum canvas_scale sc) -{ - const size_t wnd_width = ui_get_width(); - const size_t wnd_height = ui_get_height(); - const float scale_w = 1.0 / ((float)ctx.image.width / wnd_width); - const float scale_h = 1.0 / ((float)ctx.image.height / wnd_height); - - switch (sc) { - case scale_fit_optimal: - ctx.scale = min(scale_w, scale_h); - if (ctx.scale > 1.0) { - ctx.scale = 1.0; - } - break; - case scale_fit_window: - ctx.scale = min(scale_w, scale_h); - break; - case scale_fit_width: - ctx.scale = scale_w; - break; - case scale_fit_height: - ctx.scale = scale_h; - break; - case scale_fill_window: - ctx.scale = max(scale_w, scale_h); - break; - case scale_real_size: - ctx.scale = 1.0; // 100 % - break; - } - - // center viewport - ctx.image.x = wnd_width / 2 - (ctx.scale * ctx.image.width) / 2; - ctx.image.y = wnd_height / 2 - (ctx.scale * ctx.image.height) / 2; - - fix_position(true); -} - -/** - * Zoom in/out. - * @param percent percentage increment to current scale - */ -static void zoom(ssize_t percent) -{ - const double wnd_half_w = ui_get_width() / 2; - const double wnd_half_h = ui_get_height() / 2; - const float step = (ctx.scale / 100) * percent; - - // get current center - const double center_x = wnd_half_w / ctx.scale - ctx.image.x / ctx.scale; - const double center_y = wnd_half_h / ctx.scale - ctx.image.y / ctx.scale; - - if (percent > 0) { - ctx.scale += step; - if (ctx.scale > MAX_SCALE) { - ctx.scale = MAX_SCALE; - } - } else { - const float scale_w = (float)MIN_SCALE / ctx.image.width; - const float scale_h = (float)MIN_SCALE / ctx.image.height; - const float scale_min = max(scale_w, scale_h); - ctx.scale += step; - if (ctx.scale < scale_min) { - ctx.scale = scale_min; - } - } - - // restore center - ctx.image.x = wnd_half_w - center_x * ctx.scale; - ctx.image.y = wnd_half_h - center_y * ctx.scale; - - fix_position(false); -} - -/** - * Custom section loader, see `config_loader` for details. - */ -static enum config_status load_config(const char* key, const char* value) -{ - enum config_status status = cfgst_invalid_value; - - if (strcmp(key, CANVAS_CFG_ANTIALIASING) == 0) { - if (config_to_bool(value, &ctx.antialiasing)) { - status = cfgst_ok; - } - } else if (strcmp(key, CANVAS_CFG_SCALE) == 0) { - const ssize_t index = str_index(scale_names, value, 0); - if (index >= 0) { - ctx.initial_scale = index; - status = cfgst_ok; - } - } else if (strcmp(key, CANVAS_CFG_TRANSPARENCY) == 0) { - if (strcmp(value, "grid") == 0) { - ctx.image_bkg = BACKGROUND_GRID; - status = cfgst_ok; - } else if (strcmp(value, "none") == 0) { - ctx.image_bkg = COLOR_TRANSPARENT; - status = cfgst_ok; - } else if (config_to_color(value, &ctx.image_bkg)) { - status = cfgst_ok; - } - } else if (strcmp(key, CANVAS_CFG_BACKGROUND) == 0) { - if (strcmp(value, "none") == 0) { - ctx.window_bkg = COLOR_TRANSPARENT; - status = cfgst_ok; - } else if (config_to_color(value, &ctx.window_bkg)) { - status = cfgst_ok; - } - } else if (strcmp(key, CANVAS_CFG_FIXED) == 0) { - if (config_to_bool(value, &ctx.fixed)) { - status = cfgst_ok; - } - } else { - status = cfgst_invalid_key; - } - - return status; -} - -void canvas_init(void) -{ - // register configuration loader - config_add_loader(GENERAL_CONFIG_SECTION, load_config); -} - -void canvas_reset_window(void) -{ - fix_position(true); -} - -void canvas_reset_image(size_t width, size_t height) -{ - ctx.image.x = 0; - ctx.image.y = 0; - ctx.image.width = width; - ctx.image.height = height; - ctx.scale = 0; - set_scale(ctx.initial_scale); -} - -void canvas_swap_image_size(void) -{ - const ssize_t diff = (ssize_t)ctx.image.width - ctx.image.height; - const ssize_t shift = (ctx.scale * diff) / 2; - const size_t old_width = ctx.image.width; - - ctx.image.x += shift; - ctx.image.y -= shift; - ctx.image.width = ctx.image.height; - ctx.image.height = old_width; - - fix_position(false); -} - -void canvas_draw_image(struct pixmap* wnd, const struct image* img, - size_t frame) -{ - const struct pixmap* pm = &img->frames[frame].pm; - const size_t width = ctx.scale * ctx.image.width; - const size_t height = ctx.scale * ctx.image.height; - - // clear window background - const argb_t wnd_color = (ctx.window_bkg == COLOR_TRANSPARENT - ? 0 - : ARGB_SET_A(0xff) | ctx.window_bkg); - pixmap_inverse_fill(wnd, ctx.image.x, ctx.image.y, width, height, - wnd_color); - - // clear image background - if (img->alpha) { - if (ctx.image_bkg == BACKGROUND_GRID) { - pixmap_grid(wnd, ctx.image.x, ctx.image.y, width, height, - ui_get_scale() * GRID_STEP, GRID_COLOR1, GRID_COLOR2); - } else { - const argb_t color = (ctx.image_bkg == COLOR_TRANSPARENT - ? wnd_color - : ARGB_SET_A(0xff) | ctx.image_bkg); - pixmap_fill(wnd, ctx.image.x, ctx.image.y, width, height, color); - } - } - - // put image on window surface - pixmap_put(wnd, pm, ctx.image.x, ctx.image.y, ctx.scale, img->alpha, - ctx.scale == 1.0 ? false : ctx.antialiasing); -} - -bool canvas_move(bool horizontal, ssize_t percent) -{ - const ssize_t old_x = ctx.image.x; - const ssize_t old_y = ctx.image.y; - - if (horizontal) { - ctx.image.x += (ui_get_width() / 100) * percent; - } else { - ctx.image.y += (ui_get_height() / 100) * percent; - } - - fix_position(false); - - return (ctx.image.x != old_x || ctx.image.y != old_y); -} - -bool canvas_drag(int dx, int dy) -{ - const ssize_t old_x = ctx.image.x; - const ssize_t old_y = ctx.image.y; - - ctx.image.x += dx; - ctx.image.y += dy; - - fix_position(false); - - return (ctx.image.x != old_x || ctx.image.y != old_y); -} - -void canvas_zoom(const char* op) -{ - ssize_t percent = 0; - - if (!op || !*op) { - return; - } - - for (size_t i = 0; i < sizeof(scale_names) / sizeof(scale_names[0]); ++i) { - if (strcmp(op, scale_names[i]) == 0) { - set_scale(i); - return; - } - } - - if (str_to_num(op, 0, &percent, 0) && percent != 0 && percent > -1000 && - percent < 1000) { - zoom(percent); - return; - } - - fprintf(stderr, "Invalid zoom operation: \"%s\"\n", op); -} - -float canvas_get_scale(void) -{ - return ctx.scale; -} - -bool canvas_switch_aa(void) -{ - ctx.antialiasing = !ctx.antialiasing; - return ctx.antialiasing; -} diff --git a/src/canvas.h b/src/canvas.h deleted file mode 100644 index 883b22f..0000000 --- a/src/canvas.h +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MIT -// Canvas used to render images and text to window buffer. -// Copyright (C) 2022 Artem Senichev - -#pragma once - -#include "image.h" -#include "info.h" - -// Configuration parameters -#define CANVAS_CFG_ANTIALIASING "antialiasing" -#define CANVAS_CFG_SCALE "scale" -#define CANVAS_CFG_TRANSPARENCY "transparency" -#define CANVAS_CFG_BACKGROUND "background" -#define CANVAS_CFG_FIXED "fixed" - -/** - * Initialize canvas context. - */ -void canvas_init(void); - -/** - * Reset window parameters. - */ -void canvas_reset_window(void); - -/** - * Reset image position, size and scale. - * @param width,height new image size - */ -void canvas_reset_image(size_t width, size_t height); - -/** - * Recalculate position after rotating image on 90 degree. - */ -void canvas_swap_image_size(void); - -/** - * Draw image on window. - * @param wnd destination window - * @param img image to draw - * @param frame frame number to draw - */ -void canvas_draw_image(struct pixmap* wnd, const struct image* img, - size_t frame); - -/** - * Move viewport. - * @param horizontal axis along which to move (false for vertical) - * @param percent percentage increment to current position - * @return true if coordinates were changed - */ -bool canvas_move(bool horizontal, ssize_t percent); - -/** - * Move viewport. - * @param dx,dy delta between current and new position - * @return true if coordinates were changed - */ -bool canvas_drag(int dx, int dy); - -/** - * Zoom image. - * @param op zoom operation name - */ -void canvas_zoom(const char* op); - -/** - * Get current scale. - * @return current scale, 1.0 = 100% - */ -float canvas_get_scale(void); - -/** - * Switch antialiasing. - * @return current state - */ -bool canvas_switch_aa(void); diff --git a/src/info.c b/src/info.c index 9fbb148..a986fd0 100644 --- a/src/info.c +++ b/src/info.c @@ -4,7 +4,6 @@ #include "info.h" -#include "canvas.h" #include "config.h" #include "imagelist.h" #include "str.h" @@ -340,7 +339,7 @@ void info_set_mode(const char* mode) } } -void info_update(size_t frame_idx) +void info_update(size_t frame_idx, float scale) { const struct image_entry entry = image_list_current(); const struct image* image = entry.image; @@ -389,7 +388,7 @@ void info_update(size_t frame_idx) } if (is_visible(info_scale)) { - const size_t scale_percent = canvas_get_scale() * 100; + const size_t scale_percent = scale * 100; if (ctx.scale != scale_percent) { ctx.scale = scale_percent; snprintf(buffer, sizeof(buffer), "%ld%%", ctx.scale); diff --git a/src/info.h b/src/info.h index 2143a5d..2bcc060 100644 --- a/src/info.h +++ b/src/info.h @@ -45,8 +45,9 @@ void info_set_mode(const char* mode); /** * Refresh info data. * @param frame_idx index of the current frame + * @param scale current scale factor */ -void info_update(size_t frame_idx); +void info_update(size_t frame_idx, float scale); /** * Set status text. diff --git a/src/main.c b/src/main.c index 945ac9e..ec9387c 100644 --- a/src/main.c +++ b/src/main.c @@ -3,7 +3,6 @@ // Copyright (C) 2020 Artem Senichev #include "buildcfg.h" -#include "canvas.h" #include "config.h" #include "font.h" #include "formats/loader.h" @@ -39,7 +38,7 @@ static const struct cmdarg arguments[] = { { 'o', "order", "ORDER", "set sort order for image list: none/[alpha]/random", IMGLIST_CFG_SECTION, IMGLIST_CFG_ORDER, NULL }, { 's', "scale", "SCALE", "set initial image scale: [optimal]/fit/width/height/fill/real", - GENERAL_CONFIG_SECTION, CANVAS_CFG_SCALE, NULL }, + GENERAL_CONFIG_SECTION, VIEWER_CFG_SCALE, NULL }, { 'l', "slideshow", NULL, "activate slideshow mode on startup", GENERAL_CONFIG_SECTION, VIEWER_CFG_SLIDESHOW, "yes" }, { 'f', "fullscreen", NULL, "show image in full screen mode", @@ -207,7 +206,6 @@ int main(int argc, char* argv[]) font_create(); info_create(); image_list_init(); - canvas_init(); ui_init(); viewer_init(); text_init(); diff --git a/src/pixmap.c b/src/pixmap.c index cc27446..9c22f5d 100644 --- a/src/pixmap.c +++ b/src/pixmap.c @@ -7,9 +7,6 @@ #include #include -#define max(a, b) ((a) > (b) ? (a) : (b)) -#define min(a, b) ((a) < (b) ? (a) : (b)) - /** * Alpha blending. * @param img color of image's pixel diff --git a/src/pixmap.h b/src/pixmap.h index a93a695..dfd013f 100644 --- a/src/pixmap.h +++ b/src/pixmap.h @@ -34,6 +34,9 @@ typedef uint32_t argb_t; #define ARGB_SET_ABGR(c) \ ((c & 0xff00ff00) | ARGB_SET_R(ARGB_GET_B(c)) | ARGB_SET_B(ARGB_GET_R(c))) +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) < (b) ? (a) : (b)) + /** Size description. */ struct size { size_t width; diff --git a/src/viewer.c b/src/viewer.c index 3aaef59..7bf3a1f 100644 --- a/src/viewer.c +++ b/src/viewer.c @@ -5,7 +5,6 @@ #include "viewer.h" #include "buildcfg.h" -#include "canvas.h" #include "config.h" #include "imagelist.h" #include "info.h" @@ -22,9 +21,51 @@ #include #include +// Background modes +#define COLOR_TRANSPARENT 0xff000000 +#define BACKGROUND_GRID 0xfe000000 + +// Background grid parameters +#define GRID_STEP 10 +#define GRID_COLOR1 0xff333333 +#define GRID_COLOR2 0xff4c4c4c + +// Scale thresholds +#define MIN_SCALE 10 // pixels +#define MAX_SCALE 100.0 // factor + +/** Scaling operations. */ +enum fixed_scale { + scale_fit_optimal, ///< Fit to window, but not more than 100% + scale_fit_window, ///< Fit to window size + scale_fit_width, ///< Fit width to window width + scale_fit_height, ///< Fit height to window height + scale_fill_window, ///< Fill the window + scale_real_size, ///< Real image size (100%) +}; + +// clang-format off +static const char* scale_names[] = { + [scale_fit_optimal] = "optimal", + [scale_fit_window] = "fit", + [scale_fit_width] = "width", + [scale_fit_height] = "height", + [scale_fill_window] = "fill", + [scale_real_size] = "real", +}; +// clang-format on + /** Viewer context. */ struct viewer { - size_t frame; ///< Index of the current frame + ssize_t img_x, img_y; ///< Top left corner of the image + size_t frame; ///< Index of the current frame + argb_t image_bkg; ///< Image background mode/color + argb_t window_bkg; ///< Window background mode/color + bool antialiasing; ///< Anti-aliasing mode on/off + bool fixed; ///< Fix image position + + enum fixed_scale scale_init; ///< Initial scale + float scale; ///< Current scale factor of the image struct text_surface* help; ///< Help lines size_t help_sz; ///< Number of lines in help @@ -37,63 +78,221 @@ struct viewer { size_t slideshow_time; ///< Slideshow image display time (seconds) }; -static struct viewer ctx = { .animation_enable = true, - .animation_fd = -1, - .slideshow_enable = false, - .slideshow_fd = -1, - .slideshow_time = 3 }; +static struct viewer ctx = { + .image_bkg = BACKGROUND_GRID, + .window_bkg = COLOR_TRANSPARENT, + .fixed = true, + .animation_enable = true, + .animation_fd = -1, + .slideshow_enable = false, + .slideshow_fd = -1, + .slideshow_time = 3, +}; -static void switch_help(void) +/** + * Fix up image position. + * @param force true to ignore current config setting + */ +static void fixup_position(bool force) { - if (ctx.help) { - for (size_t i = 0; i < ctx.help_sz; i++) { - free(ctx.help[i].data); + const ssize_t wnd_width = ui_get_width(); + const ssize_t wnd_height = ui_get_height(); + + const struct image_entry entry = image_list_current(); + const struct pixmap* img = &entry.image->frames[ctx.frame].pm; + const ssize_t img_width = ctx.scale * img->width; + const ssize_t img_height = ctx.scale * img->height; + + if (force || ctx.fixed) { + // bind to window border + if (ctx.img_x > 0 && ctx.img_x + img_width > wnd_width) { + ctx.img_x = 0; } - free(ctx.help); - ctx.help = NULL; - ctx.help_sz = 0; - } else { - ctx.help_sz = 0; - ctx.help = calloc(1, key_bindings_size * sizeof(struct text_surface)); - if (ctx.help) { - for (size_t i = 0; i < key_bindings_size; i++) { - if (key_bindings[i].help) { - font_render(key_bindings[i].help, &ctx.help[ctx.help_sz++]); - } - } + if (ctx.img_y > 0 && ctx.img_y + img_height > wnd_height) { + ctx.img_y = 0; + } + if (ctx.img_x < 0 && ctx.img_x + img_width < wnd_width) { + ctx.img_x = wnd_width - img_width; + } + if (ctx.img_y < 0 && ctx.img_y + img_height < wnd_height) { + ctx.img_y = wnd_height - img_height; } + + // centering small image + if (img_width <= wnd_width) { + ctx.img_x = wnd_width / 2 - img_width / 2; + } + if (img_height <= wnd_height) { + ctx.img_y = wnd_height / 2 - img_height / 2; + } + } + + // don't let canvas to be far out of window + if (ctx.img_x + img_width < 0) { + ctx.img_x = -img_width; + } + if (ctx.img_x > wnd_width) { + ctx.img_x = wnd_width; + } + if (ctx.img_y + img_height < 0) { + ctx.img_y = -img_height; + } + if (ctx.img_y > wnd_height) { + ctx.img_y = wnd_height; } } /** - * Switch to the next or previous frame. - * @param forward switch direction - * @return false if there is only one frame in the image + * Move image (viewport). + * @param horizontal axis along which to move (false for vertical) + * @param positive direction (increase/decrease) + * @param params optional move step in percents */ -static bool next_frame(bool forward) +static bool move_image(bool horizontal, bool positive, const char* params) { - size_t index = ctx.frame; - const struct image_entry entry = image_list_current(); + const ssize_t old_x = ctx.img_x; + const ssize_t old_y = ctx.img_y; + ssize_t step = 10; // in % - if (forward) { - if (++index >= entry.image->num_frames) { - index = 0; + if (params) { + ssize_t val; + if (str_to_num(params, 0, &val, 0) && val > 0 && val <= 1000) { + step = val; + } else { + fprintf(stderr, "Invalid move step: \"%s\"\n", params); } + } + + if (!positive) { + step = -step; + } + + if (horizontal) { + ctx.img_x += (ui_get_width() / 100) * step; } else { - if (index-- == 0) { - index = entry.image->num_frames - 1; - } + ctx.img_y += (ui_get_height() / 100) * step; } - if (index == ctx.frame) { - return false; + + fixup_position(false); + + return (ctx.img_x != old_x || ctx.img_y != old_y); +} + +/** + * Rotate image 90 degrees. + * @param clockwise rotation direction + */ +static void rotate_image(bool clockwise) +{ + const struct image_entry entry = image_list_current(); + const struct pixmap* img = &entry.image->frames[ctx.frame].pm; + const ssize_t diff = (ssize_t)img->width - img->height; + const ssize_t shift = (ctx.scale * diff) / 2; + + image_rotate(entry.image, clockwise ? 90 : 270); + ctx.img_x += shift; + ctx.img_y -= shift; + fixup_position(false); +} + +/** + * Set fixed scale for the image. + * @param sc scale to set + */ +static void scale_image(enum fixed_scale sc) +{ + const struct image_entry entry = image_list_current(); + const struct pixmap* img = &entry.image->frames[ctx.frame].pm; + const size_t wnd_width = ui_get_width(); + const size_t wnd_height = ui_get_height(); + const float scale_w = 1.0 / ((float)img->width / wnd_width); + const float scale_h = 1.0 / ((float)img->height / wnd_height); + + switch (sc) { + case scale_fit_optimal: + ctx.scale = min(scale_w, scale_h); + if (ctx.scale > 1.0) { + ctx.scale = 1.0; + } + break; + case scale_fit_window: + ctx.scale = min(scale_w, scale_h); + break; + case scale_fit_width: + ctx.scale = scale_w; + break; + case scale_fit_height: + ctx.scale = scale_h; + break; + case scale_fill_window: + ctx.scale = max(scale_w, scale_h); + break; + case scale_real_size: + ctx.scale = 1.0; // 100 % + break; } - ctx.frame = index; - return true; + // center viewport + ctx.img_x = wnd_width / 2 - (ctx.scale * img->width) / 2; + ctx.img_y = wnd_height / 2 - (ctx.scale * img->height) / 2; + + fixup_position(true); } /** - * Start animation if image supports it. + * Zoom in/out. + * @param params zoom operation + */ +void zoom_image(const char* params) +{ + ssize_t percent = 0; + ssize_t fixed_scale; + + if (!params || !*params) { + return; + } + + // check for fixed scale type + fixed_scale = str_index(scale_names, params, 0); + if (fixed_scale >= 0) { + scale_image(fixed_scale); + } else if (str_to_num(params, 0, &percent, 0) && percent != 0 && + percent > -1000 && percent < 1000) { + // zoom in % + const double wnd_half_w = ui_get_width() / 2; + const double wnd_half_h = ui_get_height() / 2; + const float step = (ctx.scale / 100) * percent; + const double center_x = wnd_half_w / ctx.scale - ctx.img_x / ctx.scale; + const double center_y = wnd_half_h / ctx.scale - ctx.img_y / ctx.scale; + + if (percent > 0) { + ctx.scale += step; + if (ctx.scale > MAX_SCALE) { + ctx.scale = MAX_SCALE; + } + } else { + const struct image_entry entry = image_list_current(); + const struct pixmap* img = &entry.image->frames[ctx.frame].pm; + const float scale_w = (float)MIN_SCALE / img->width; + const float scale_h = (float)MIN_SCALE / img->height; + const float scale_min = max(scale_w, scale_h); + ctx.scale += step; + if (ctx.scale < scale_min) { + ctx.scale = scale_min; + } + } + + // restore center + ctx.img_x = wnd_half_w - center_x * ctx.scale; + ctx.img_y = wnd_half_h - center_y * ctx.scale; + fixup_position(false); + } else { + fprintf(stderr, "Invalid zoom operation: \"%s\"\n", params); + } +} + +/** + * Start/stop animation if image supports it. * @param enable state to set */ static void animation_ctl(bool enable) @@ -115,7 +314,7 @@ static void animation_ctl(bool enable) } /** - * Start slide show. + * Start/stop slide show. * @param enable state to set */ static void slideshow_ctl(bool enable) @@ -131,15 +330,19 @@ static void slideshow_ctl(bool enable) } /** - * Reset state after loading new file. + * Reset state to defaults. */ static void reset_state(void) { const struct image_entry entry = image_list_current(); - const struct pixmap* pm = &entry.image->frames[0].pm; ctx.frame = 0; - canvas_reset_image(pm->width, pm->height); + ctx.img_x = 0; + ctx.img_y = 0; + ctx.scale = 0; + scale_image(ctx.scale_init); + fixup_position(true); + ui_set_title(entry.image->file_name); animation_ctl(true); slideshow_ctl(ctx.slideshow_enable); @@ -159,6 +362,52 @@ static bool next_file(enum list_jump jump) return true; } +/** + * Switch to the next or previous frame. + * @param forward switch direction + * @return false if there is only one frame in the image + */ +static bool next_frame(bool forward) +{ + size_t index = ctx.frame; + const struct image_entry entry = image_list_current(); + + if (forward) { + if (++index >= entry.image->num_frames) { + index = 0; + } + } else { + if (index-- == 0) { + index = entry.image->num_frames - 1; + } + } + if (index == ctx.frame) { + return false; + } + + ctx.frame = index; + return true; +} + +/** + * Animation timer event handler. + */ +static void on_animation_timer(void) +{ + next_frame(true); + animation_ctl(true); + ui_redraw(); +} + +/** + * Slideshow timer event handler. + */ +static void on_slideshow_timer(void) +{ + slideshow_ctl(next_file(jump_next_file)); + ui_redraw(); +} + /** * Execute system command for the current image. * @param expr command expression @@ -209,48 +458,63 @@ static void execute_command(const char* expr) } /** - * Move viewport. - * @param horizontal axis along which to move (false for vertical) - * @param positive direction (increase/decrease) - * @param params optional move step in percents + * Show/hide help layer. */ -static bool move_viewport(bool horizontal, bool positive, const char* params) +static void switch_help(void) { - ssize_t percent = 10; - - if (params) { - ssize_t val; - if (str_to_num(params, 0, &val, 0) && val > 0 && val <= 1000) { - percent = val; - } else { - fprintf(stderr, "Invalid move step: \"%s\"\n", params); + if (ctx.help) { + for (size_t i = 0; i < ctx.help_sz; i++) { + free(ctx.help[i].data); + } + free(ctx.help); + ctx.help = NULL; + ctx.help_sz = 0; + } else { + ctx.help_sz = 0; + ctx.help = calloc(1, key_bindings_size * sizeof(struct text_surface)); + if (ctx.help) { + for (size_t i = 0; i < key_bindings_size; i++) { + if (key_bindings[i].help) { + font_render(key_bindings[i].help, &ctx.help[ctx.help_sz++]); + } + } } } - - if (!positive) { - percent = -percent; - } - - return canvas_move(horizontal, percent); } /** - * Animation timer event handler. + * Draw image. + * @param wnd pixel map of target window */ -static void on_animation_timer(void) +static void draw_image(struct pixmap* wnd) { - next_frame(true); - animation_ctl(true); - ui_redraw(); -} + const struct image_entry entry = image_list_current(); + const struct pixmap* img = &entry.image->frames[ctx.frame].pm; + const size_t width = ctx.scale * img->width; + const size_t height = ctx.scale * img->height; + + // clear window background + const argb_t wnd_color = (ctx.window_bkg == COLOR_TRANSPARENT + ? 0 + : ARGB_SET_A(0xff) | ctx.window_bkg); + pixmap_inverse_fill(wnd, ctx.img_x, ctx.img_y, width, height, wnd_color); + + // clear image background + if (entry.image->alpha) { + if (ctx.image_bkg == BACKGROUND_GRID) { + pixmap_grid(wnd, ctx.img_x, ctx.img_y, width, height, + ui_get_scale() * GRID_STEP, GRID_COLOR1, GRID_COLOR2); + } else { + const argb_t color = (ctx.image_bkg == COLOR_TRANSPARENT + ? wnd_color + : ARGB_SET_A(0xff) | ctx.image_bkg); + pixmap_fill(wnd, ctx.img_x, ctx.img_y, width, height, color); + } + } -/** - * Slideshow timer event handler. - */ -static void on_slideshow_timer(void) -{ - slideshow_ctl(next_file(jump_next_file)); - ui_redraw(); + // put image on window surface + pixmap_put(wnd, img, ctx.img_x, ctx.img_y, ctx.scale, entry.image->alpha, + ctx.scale == 1.0 ? false : ctx.antialiasing); } /** @@ -258,20 +522,51 @@ static void on_slideshow_timer(void) */ static enum config_status load_config(const char* key, const char* value) { - enum config_status status = cfgst_invalid_key; + enum config_status status = cfgst_invalid_value; - if (strcmp(key, VIEWER_CFG_SLIDESHOW) == 0) { - status = config_to_bool(value, &ctx.slideshow_enable) - ? cfgst_ok - : cfgst_invalid_value; + if (strcmp(key, VIEWER_CFG_ANTIALIASING) == 0) { + if (config_to_bool(value, &ctx.antialiasing)) { + status = cfgst_ok; + } + } else if (strcmp(key, VIEWER_CFG_SCALE) == 0) { + const ssize_t index = str_index(scale_names, value, 0); + if (index >= 0) { + ctx.scale_init = index; + status = cfgst_ok; + } + } else if (strcmp(key, VIEWER_CFG_TRANSPARENCY) == 0) { + if (strcmp(value, "grid") == 0) { + ctx.image_bkg = BACKGROUND_GRID; + status = cfgst_ok; + } else if (strcmp(value, "none") == 0) { + ctx.image_bkg = COLOR_TRANSPARENT; + status = cfgst_ok; + } else if (config_to_color(value, &ctx.image_bkg)) { + status = cfgst_ok; + } + } else if (strcmp(key, VIEWER_CFG_BACKGROUND) == 0) { + if (strcmp(value, "none") == 0) { + ctx.window_bkg = COLOR_TRANSPARENT; + status = cfgst_ok; + } else if (config_to_color(value, &ctx.window_bkg)) { + status = cfgst_ok; + } + } else if (strcmp(key, VIEWER_CFG_FIXED) == 0) { + if (config_to_bool(value, &ctx.fixed)) { + status = cfgst_ok; + } + } else if (strcmp(key, VIEWER_CFG_SLIDESHOW) == 0) { + if (config_to_bool(value, &ctx.slideshow_enable)) { + status = cfgst_ok; + } } else if (strcmp(key, VIEWER_CFG_SLIDESHOW_TIME) == 0) { ssize_t num; if (str_to_num(value, 0, &num, 0) && num != 0 && num <= 86400) { ctx.slideshow_time = num; status = cfgst_ok; - } else { - status = cfgst_invalid_value; } + } else { + status = cfgst_invalid_key; } return status; @@ -324,11 +619,8 @@ void viewer_reset(void) void viewer_on_redraw(struct pixmap* window) { - const struct image_entry entry = image_list_current(); - - info_update(ctx.frame); - - canvas_draw_image(window, entry.image, ctx.frame); + info_update(ctx.frame, ctx.scale); + draw_image(window); // put text info blocks on window surface for (size_t i = 0; i < INFO_POSITION_NUM; ++i) { @@ -339,7 +631,6 @@ void viewer_on_redraw(struct pixmap* window) text_print(window, pos, lines, lines_num); } } - if (ctx.help) { text_print_centered(window, ctx.help, ctx.help_sz); } @@ -350,7 +641,7 @@ void viewer_on_redraw(struct pixmap* window) void viewer_on_resize(void) { - canvas_reset_window(); + fixup_position(false); reset_state(); } @@ -420,29 +711,27 @@ void viewer_on_keyboard(xkb_keysym_t key, uint8_t mods) ui_toggle_fullscreen(); break; case kb_step_left: - redraw = move_viewport(true, true, kbind->params); + redraw = move_image(true, true, kbind->params); break; case kb_step_right: - redraw = move_viewport(true, false, kbind->params); + redraw = move_image(true, false, kbind->params); break; case kb_step_up: - redraw = move_viewport(false, true, kbind->params); + redraw = move_image(false, true, kbind->params); break; case kb_step_down: - redraw = move_viewport(false, false, kbind->params); + redraw = move_image(false, false, kbind->params); break; case kb_zoom: - canvas_zoom(kbind->params); + zoom_image(kbind->params); redraw = true; break; case kb_rotate_left: - image_rotate(image_list_current().image, 270); - canvas_swap_image_size(); + rotate_image(false); redraw = true; break; case kb_rotate_right: - image_rotate(image_list_current().image, 90); - canvas_swap_image_size(); + rotate_image(true); redraw = true; break; case kb_flip_vertical: @@ -454,8 +743,9 @@ void viewer_on_keyboard(xkb_keysym_t key, uint8_t mods) redraw = true; break; case kb_antialiasing: + ctx.antialiasing = !ctx.antialiasing; info_set_status("Anti-aliasing %s", - canvas_switch_aa() ? "on" : "off"); + ctx.antialiasing ? "on" : "off"); redraw = true; break; case kb_reload: @@ -492,7 +782,14 @@ void viewer_on_keyboard(xkb_keysym_t key, uint8_t mods) void viewer_on_drag(int dx, int dy) { - if (canvas_drag(dx, dy)) { + const ssize_t old_x = ctx.img_x; + const ssize_t old_y = ctx.img_y; + + ctx.img_x += dx; + ctx.img_y += dy; + + if (ctx.img_x != old_x || ctx.img_y != old_y) { + fixup_position(false); ui_redraw(); } } diff --git a/src/viewer.h b/src/viewer.h index 54c20e8..5575f47 100644 --- a/src/viewer.h +++ b/src/viewer.h @@ -8,6 +8,11 @@ #include "pixmap.h" // Configuration parameters +#define VIEWER_CFG_SCALE "scale" +#define VIEWER_CFG_ANTIALIASING "antialiasing" +#define VIEWER_CFG_FIXED "fixed" +#define VIEWER_CFG_TRANSPARENCY "transparency" +#define VIEWER_CFG_BACKGROUND "background" #define VIEWER_CFG_SLIDESHOW "slideshow" #define VIEWER_CFG_SLIDESHOW_TIME "slideshow_time"