nsxiv/image.c

912 lines
21 KiB
C
Raw Normal View History

2021-10-28 12:41:16 +02:00
/* Copyright 2011-2020 Bert Muennich
2022-02-09 08:09:42 +01:00
* Copyright 2021-2022 nsxiv contributors
2011-01-17 14:57:59 +01:00
*
* This file is a part of nsxiv.
*
* nsxiv is free software; you can redistribute it and/or modify
2013-02-08 21:52:41 +01:00
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* nsxiv is distributed in the hope that it will be useful,
2013-02-08 21:52:41 +01:00
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nsxiv. If not, see <http://www.gnu.org/licenses/>.
2011-01-17 14:57:59 +01:00
*/
#include "nsxiv.h"
#define _IMAGE_CONFIG
#include "config.h"
#include <errno.h>
#include <stdlib.h>
2011-09-06 11:09:57 +02:00
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#if HAVE_LIBEXIF
#include <libexif/exif-data.h>
#endif
Rework the build system (#19) * Added simple configure script Added simple script to autodetect if optional dependencies are installed and enable/disable them as needed. Note this solution uses the compiler directly instead of an external program like pkgconfig so it doesn't require any extra dependencies. It is intended to work with any arbitrary compiler; it has been tested with tcc and gcc. There are some "breaking" changes hidden here - HAVE_GIFLIB was renamed to HAVE_LIBGIF to match HAVE_LIBEXIF - Simply typing `make` will no longer try to build with optional dependencies * use implicit RM variable * General clean-up in configure - Use printf instead of echo - Format style * honor env PREFIX, use LDLIBS * Revert "General clean-up in configure" This reverts commit 8683c179dbf273a330f9a224a4d481a7bea42c5f. * honor env LDFLAGS if set * Don't set OPTIONAL_LIBS in configure * make OBJ all caps * follow suckless style build system - remove configure script. - HAVE_LIBGIF and HAVE_LIBEXIF defaults back to 1 - unload several varibales onto config.mk - make version all-caps - add -O2 optimization - use CPPFLAGS for includes and defines * Revert "follow suckless style build system" This reverts commit 8bf75b1f68d72df349edba8d998d4659dd956dd8. * Generate config.mk from make * Inlined configure in Makefile * update docs * cleanups - changes to config.mk should trigger a rebuild - remove potentially confusing variables form Makefile * Use install instead of mkdir/cp/chmod when sensible * fixup! Inlined configure in Makefile * Don't generate config.mk on rm -f *.o sxiv * update docs and cleanups - make config.mk silent - mention editing config.mk in README * fallback to 0 if user edits config.mk in unexpected way * add comment on config.mk * remove invalid comment configure script is removed * slight restructure - make version all caps - restructure variables that users may want to edit to top - use CPPFLAGS for defines - add some comments - remove needless echos since we have verbose output now * add echos back Co-authored-by: NRK <nrk@disroot.org> Co-authored-by: Berke Kocaoğlu <berke.kocaoglu@metu.edu.tr>
2021-09-14 16:42:57 +02:00
#if HAVE_LIBGIF
#include <gif_lib.h>
enum { DEF_GIF_DELAY = 75 };
#endif
#if HAVE_LIBWEBP
#include <webp/decode.h>
#include <webp/demux.h>
enum { DEF_WEBP_DELAY = 75 };
#endif
#define ZOOM_MIN (zoom_levels[0] / 100)
#define ZOOM_MAX (zoom_levels[ARRLEN(zoom_levels)-1] / 100)
static int calc_cache_size(void)
{
int cache;
long pages, page_size;
if (CACHE_SIZE_MEM_PERCENTAGE <= 0)
return 0;
pages = sysconf(_SC_PHYS_PAGES);
page_size = sysconf(_SC_PAGE_SIZE);
if (pages < 0 || page_size < 0)
return CACHE_SIZE_FALLBACK;
cache = (pages/100) * CACHE_SIZE_MEM_PERCENTAGE;
cache *= page_size;
return MIN(cache, CACHE_SIZE_LIMIT);
}
void img_init(img_t *img, win_t *win)
{
imlib_context_set_display(win->env.dpy);
imlib_context_set_visual(win->env.vis);
imlib_context_set_colormap(win->env.cmap);
imlib_set_cache_size(calc_cache_size());
img->im = NULL;
img->win = win;
img->scalemode = options->scalemode;
img->zoom = options->zoom;
img->zoom = MAX(img->zoom, ZOOM_MIN);
img->zoom = MIN(img->zoom, ZOOM_MAX);
img->checkpan = false;
img->dirty = false;
img->aa = ANTI_ALIAS;
img->alpha = ALPHA_LAYER;
img->multi.cap = img->multi.cnt = 0;
img->multi.animate = options->animate;
img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0;
img->multi.length = 0;
img->cmod = imlib_create_color_modifier();
imlib_context_set_color_modifier(img->cmod);
img_change_gamma(img, options->gamma);
img->ss.on = options->slideshow > 0;
2016-12-01 20:33:24 +01:00
img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10;
}
#if HAVE_LIBEXIF
void exif_auto_orientate(const fileinfo_t *file)
{
ExifData *ed;
ExifEntry *entry;
int byte_order, orientation = 0;
if ((ed = exif_data_new_from_file(file->path)) == NULL)
return;
byte_order = exif_data_get_byte_order(ed);
entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION);
if (entry != NULL)
orientation = exif_get_short(entry->data, byte_order);
exif_data_unref(ed);
switch (orientation) {
case 5:
imlib_image_orientate(1);
/* fall through */
case 2:
imlib_image_flip_vertical();
break;
case 3:
imlib_image_orientate(2);
break;
case 7:
imlib_image_orientate(1);
/* fall through */
case 4:
imlib_image_flip_horizontal();
break;
case 6:
imlib_image_orientate(1);
break;
case 8:
imlib_image_orientate(3);
break;
}
}
#endif
#if HAVE_LIBGIF || HAVE_LIBWEBP
static void img_multiframe_context_set(img_t *img)
{
if (img->multi.cnt > 1) {
imlib_context_set_image(img->im);
imlib_free_image();
img->im = img->multi.frames[0].im;
} else if (img->multi.cnt == 1) {
imlib_context_set_image(img->multi.frames[0].im);
imlib_free_image();
img->multi.cnt = 0;
}
imlib_context_set_image(img->im);
}
#endif
Rework the build system (#19) * Added simple configure script Added simple script to autodetect if optional dependencies are installed and enable/disable them as needed. Note this solution uses the compiler directly instead of an external program like pkgconfig so it doesn't require any extra dependencies. It is intended to work with any arbitrary compiler; it has been tested with tcc and gcc. There are some "breaking" changes hidden here - HAVE_GIFLIB was renamed to HAVE_LIBGIF to match HAVE_LIBEXIF - Simply typing `make` will no longer try to build with optional dependencies * use implicit RM variable * General clean-up in configure - Use printf instead of echo - Format style * honor env PREFIX, use LDLIBS * Revert "General clean-up in configure" This reverts commit 8683c179dbf273a330f9a224a4d481a7bea42c5f. * honor env LDFLAGS if set * Don't set OPTIONAL_LIBS in configure * make OBJ all caps * follow suckless style build system - remove configure script. - HAVE_LIBGIF and HAVE_LIBEXIF defaults back to 1 - unload several varibales onto config.mk - make version all-caps - add -O2 optimization - use CPPFLAGS for includes and defines * Revert "follow suckless style build system" This reverts commit 8bf75b1f68d72df349edba8d998d4659dd956dd8. * Generate config.mk from make * Inlined configure in Makefile * update docs * cleanups - changes to config.mk should trigger a rebuild - remove potentially confusing variables form Makefile * Use install instead of mkdir/cp/chmod when sensible * fixup! Inlined configure in Makefile * Don't generate config.mk on rm -f *.o sxiv * update docs and cleanups - make config.mk silent - mention editing config.mk in README * fallback to 0 if user edits config.mk in unexpected way * add comment on config.mk * remove invalid comment configure script is removed * slight restructure - make version all caps - restructure variables that users may want to edit to top - use CPPFLAGS for defines - add some comments - remove needless echos since we have verbose output now * add echos back Co-authored-by: NRK <nrk@disroot.org> Co-authored-by: Berke Kocaoğlu <berke.kocaoglu@metu.edu.tr>
2021-09-14 16:42:57 +02:00
#if HAVE_LIBGIF
static bool img_load_gif(img_t *img, const fileinfo_t *file)
{
2011-08-17 00:56:18 +02:00
GifFileType *gif;
GifRowType *rows = NULL;
GifRecordType rec;
ColorMapObject *cmap;
2021-12-25 03:07:30 +01:00
DATA32 bgpixel = 0, *data, *ptr;
DATA32 *prev_frame = NULL;
Imlib_Image im;
int i, j, bg, r, g, b;
int x, y, w, h, sw, sh;
int px, py, pw, ph;
2011-08-17 00:56:18 +02:00
int intoffset[] = { 0, 4, 2, 1 };
int intjump[] = { 8, 8, 4, 2 };
2011-09-11 21:01:24 +02:00
int transp = -1;
unsigned int disposal = 0, prev_disposal = 0;
2011-08-18 16:05:32 +02:00
unsigned int delay = 0;
2011-09-11 21:01:24 +02:00
bool err = false;
2011-08-17 00:56:18 +02:00
if (img->multi.cap == 0) {
img->multi.cap = 8;
img->multi.frames = emalloc(img->multi.cap * sizeof(img_frame_t));
2011-08-17 00:56:18 +02:00
}
img->multi.cnt = img->multi.sel = 0;
img->multi.length = 0;
2011-08-17 00:56:18 +02:00
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
gif = DGifOpenFileName(file->path, NULL);
#else
2011-08-17 00:56:18 +02:00
gif = DGifOpenFileName(file->path);
#endif
2011-09-29 12:43:36 +02:00
if (gif == NULL) {
error(0, 0, "%s: Error opening gif image", file->name);
2011-09-11 21:01:24 +02:00
return false;
2011-08-17 00:56:18 +02:00
}
bg = gif->SBackGroundColor;
sw = gif->SWidth;
sh = gif->SHeight;
px = py = pw = ph = 0;
2011-08-17 00:56:18 +02:00
do {
if (DGifGetRecordType(gif, &rec) == GIF_ERROR) {
2011-09-11 21:01:24 +02:00
err = true;
2011-08-17 00:56:18 +02:00
break;
}
if (rec == EXTENSION_RECORD_TYPE) {
int ext_code;
GifByteType *ext = NULL;
DGifGetExtension(gif, &ext_code, &ext);
while (ext) {
2013-12-31 16:38:42 +01:00
if (ext_code == GRAPHICS_EXT_FUNC_CODE) {
if (ext[1] & 1)
transp = (int) ext[4];
else
transp = -1;
2011-08-18 16:05:32 +02:00
delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]);
disposal = (unsigned int) ext[1] >> 2 & 0x7;
}
2011-08-17 00:56:18 +02:00
ext = NULL;
DGifGetExtensionNext(gif, &ext);
}
} else if (rec == IMAGE_DESC_RECORD_TYPE) {
if (DGifGetImageDesc(gif) == GIF_ERROR) {
2011-09-11 21:01:24 +02:00
err = true;
2011-08-17 00:56:18 +02:00
break;
}
x = gif->Image.Left;
y = gif->Image.Top;
2011-08-17 00:56:18 +02:00
w = gif->Image.Width;
h = gif->Image.Height;
rows = emalloc(h * sizeof(GifRowType));
2011-08-17 00:56:18 +02:00
for (i = 0; i < h; i++)
rows[i] = emalloc(w * sizeof(GifPixelType));
2011-08-17 00:56:18 +02:00
if (gif->Image.Interlace) {
for (i = 0; i < 4; i++) {
for (j = intoffset[i]; j < h; j += intjump[i])
DGifGetLine(gif, rows[j], w);
}
} else {
for (i = 0; i < h; i++)
DGifGetLine(gif, rows[i], w);
}
ptr = data = emalloc(sw * sh * sizeof(DATA32));
2011-08-17 00:56:18 +02:00
cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap;
/* if bg > cmap->ColorCount, it is transparent black already */
if (cmap && bg >= 0 && bg < cmap->ColorCount) {
r = cmap->Colors[bg].Red;
g = cmap->Colors[bg].Green;
b = cmap->Colors[bg].Blue;
bgpixel = 0x00ffffff & (r << 16 | g << 8 | b);
}
for (i = 0; i < sh; i++) {
for (j = 0; j < sw; j++) {
2011-09-28 12:53:44 +02:00
if (i < y || i >= y + h || j < x || j >= x + w ||
rows[i-y][j-x] == transp)
{
if (prev_frame != NULL && (prev_disposal != 2 ||
i < py || i >= py + ph || j < px || j >= px + pw))
{
*ptr = prev_frame[i * sw + j];
} else {
*ptr = bgpixel;
}
2011-08-17 00:56:18 +02:00
} else {
r = cmap->Colors[rows[i-y][j-x]].Red;
g = cmap->Colors[rows[i-y][j-x]].Green;
b = cmap->Colors[rows[i-y][j-x]].Blue;
2015-12-26 10:14:35 +01:00
*ptr = 0xffu << 24 | r << 16 | g << 8 | b;
2011-08-17 00:56:18 +02:00
}
ptr++;
2011-08-17 00:56:18 +02:00
}
}
im = imlib_create_image_using_copied_data(sw, sh, data);
2011-08-17 00:56:18 +02:00
for (i = 0; i < h; i++)
free(rows[i]);
free(rows);
free(data);
2011-09-29 12:43:36 +02:00
if (im == NULL) {
2011-09-11 21:01:24 +02:00
err = true;
2011-08-17 00:56:18 +02:00
break;
}
imlib_context_set_image(im);
imlib_image_set_format("gif");
if (transp >= 0)
imlib_image_set_has_alpha(1);
if (disposal != 3)
prev_frame = imlib_image_get_data_for_reading_only();
prev_disposal = disposal;
px = x, py = y, pw = w, ph = h;
2011-08-17 00:56:18 +02:00
if (img->multi.cnt == img->multi.cap) {
img->multi.cap *= 2;
img->multi.frames = erealloc(img->multi.frames,
img->multi.cap * sizeof(img_frame_t));
2011-08-17 00:56:18 +02:00
}
2011-08-18 16:05:32 +02:00
img->multi.frames[img->multi.cnt].im = im;
delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay;
img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY;
img->multi.length += img->multi.frames[img->multi.cnt].delay;
2011-08-18 16:05:32 +02:00
img->multi.cnt++;
2011-08-17 00:56:18 +02:00
}
} while (rec != TERMINATE_RECORD_TYPE);
2014-05-27 11:46:06 +02:00
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
DGifCloseFile(gif, NULL);
#else
2011-08-17 00:56:18 +02:00
DGifCloseFile(gif);
2014-05-27 11:46:06 +02:00
#endif
2011-08-17 00:56:18 +02:00
if (err && (file->flags & FF_WARN))
error(0, 0, "%s: Corrupted gif file", file->name);
img_multiframe_context_set(img);
2011-08-17 00:56:18 +02:00
return !err;
}
Rework the build system (#19) * Added simple configure script Added simple script to autodetect if optional dependencies are installed and enable/disable them as needed. Note this solution uses the compiler directly instead of an external program like pkgconfig so it doesn't require any extra dependencies. It is intended to work with any arbitrary compiler; it has been tested with tcc and gcc. There are some "breaking" changes hidden here - HAVE_GIFLIB was renamed to HAVE_LIBGIF to match HAVE_LIBEXIF - Simply typing `make` will no longer try to build with optional dependencies * use implicit RM variable * General clean-up in configure - Use printf instead of echo - Format style * honor env PREFIX, use LDLIBS * Revert "General clean-up in configure" This reverts commit 8683c179dbf273a330f9a224a4d481a7bea42c5f. * honor env LDFLAGS if set * Don't set OPTIONAL_LIBS in configure * make OBJ all caps * follow suckless style build system - remove configure script. - HAVE_LIBGIF and HAVE_LIBEXIF defaults back to 1 - unload several varibales onto config.mk - make version all-caps - add -O2 optimization - use CPPFLAGS for includes and defines * Revert "follow suckless style build system" This reverts commit 8bf75b1f68d72df349edba8d998d4659dd956dd8. * Generate config.mk from make * Inlined configure in Makefile * update docs * cleanups - changes to config.mk should trigger a rebuild - remove potentially confusing variables form Makefile * Use install instead of mkdir/cp/chmod when sensible * fixup! Inlined configure in Makefile * Don't generate config.mk on rm -f *.o sxiv * update docs and cleanups - make config.mk silent - mention editing config.mk in README * fallback to 0 if user edits config.mk in unexpected way * add comment on config.mk * remove invalid comment configure script is removed * slight restructure - make version all caps - restructure variables that users may want to edit to top - use CPPFLAGS for defines - add some comments - remove needless echos since we have verbose output now * add echos back Co-authored-by: NRK <nrk@disroot.org> Co-authored-by: Berke Kocaoğlu <berke.kocaoglu@metu.edu.tr>
2021-09-14 16:42:57 +02:00
#endif /* HAVE_LIBGIF */
2011-08-17 00:56:18 +02:00
#if HAVE_LIBWEBP
static bool img_load_webp(img_t *img, const fileinfo_t *file)
{
FILE *webp_file;
WebPData data;
Imlib_Image im = NULL;
struct WebPAnimDecoderOptions opts;
WebPAnimDecoder *dec = NULL;
struct WebPAnimInfo info;
unsigned char *buf = NULL, *bytes = NULL;
int ts;
const WebPDemuxer *demux;
WebPIterator iter;
unsigned long flags;
unsigned int delay;
bool err = false;
if ((webp_file = fopen(file->path, "rb")) == NULL) {
error(0, errno, "%s: Error opening webp image", file->name);
return false;
}
fseek(webp_file, 0L, SEEK_END);
data.size = ftell(webp_file);
rewind(webp_file);
bytes = emalloc(data.size);
if ((err = fread(bytes, 1, data.size, webp_file) != data.size)) {
error(0, 0, "%s: Error reading webp image", file->name);
goto fail;
}
data.bytes = bytes;
/* Setup the WebP Animation Decoder */
if ((err = !WebPAnimDecoderOptionsInit(&opts))) {
error(0, 0, "%s: WebP library version mismatch", file->name);
goto fail;
}
opts.color_mode = MODE_BGRA;
/* NOTE: Multi-threaded decoding may cause problems on some system */
opts.use_threads = true;
dec = WebPAnimDecoderNew(&data, &opts);
if ((err = (dec == NULL) || !WebPAnimDecoderGetInfo(dec, &info))) {
error(0, 0, "%s: WebP parsing or memory error (file is corrupt?)", file->name);
goto fail;
}
demux = WebPAnimDecoderGetDemuxer(dec);
/* Get global information for the image */
flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS);
img->w = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
img->h = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);
if (info.frame_count > img->multi.cap) {
img->multi.cap = info.frame_count;
img->multi.frames = erealloc(img->multi.frames,
img->multi.cap * sizeof(img_frame_t));
}
/* Load and decode frames (also works on images with only 1 frame) */
img->multi.cnt = img->multi.sel = 0;
while (WebPAnimDecoderGetNext(dec, &buf, &ts)) {
im = imlib_create_image_using_copied_data(
info.canvas_width, info.canvas_height, (DATA32*)buf);
imlib_context_set_image(im);
imlib_image_set_format("webp");
/* Get an iterator of this frame - used for frame info (duration, etc.) */
WebPDemuxGetFrame(demux, img->multi.cnt+1, &iter);
imlib_image_set_has_alpha((flags & ALPHA_FLAG) == ALPHA_FLAG);
/* Store info for this frame */
img->multi.frames[img->multi.cnt].im = im;
delay = iter.duration > 0 ? iter.duration : DEF_WEBP_DELAY;
img->multi.frames[img->multi.cnt].delay = delay;
img->multi.length += img->multi.frames[img->multi.cnt].delay;
img->multi.cnt++;
}
WebPDemuxReleaseIterator(&iter);
img_multiframe_context_set(img);
fail:
if (dec != NULL)
WebPAnimDecoderDelete(dec);
free(bytes);
fclose(webp_file);
return !err;
}
#endif /* HAVE_LIBWEBP */
Imlib_Image img_open(const fileinfo_t *file)
{
struct stat st;
Imlib_Image im = NULL;
2011-08-17 00:56:18 +02:00
if (access(file->path, R_OK) == 0 &&
stat(file->path, &st) == 0 && S_ISREG(st.st_mode))
2011-09-29 12:43:36 +02:00
{
im = imlib_load_image(file->path);
if (im != NULL) {
imlib_context_set_image(im);
if (imlib_image_get_data_for_reading_only() == NULL) {
imlib_free_image();
im = NULL;
}
}
}
if (im == NULL && (file->flags & FF_WARN))
error(0, 0, "%s: Error opening image", file->name);
return im;
}
bool img_load(img_t *img, const fileinfo_t *file)
{
const char *fmt;
if ((img->im = img_open(file)) == NULL)
return false;
2011-01-18 20:11:28 +01:00
imlib_image_set_changes_on_disk();
/* since v1.7.5, Imlib2 can parse exif orientation from jpeg files.
* this version also happens to be the first one which defines the
* IMLIB2_VERSION macro.
*/
#if HAVE_LIBEXIF && !defined(IMLIB2_VERSION)
exif_auto_orientate(file);
#endif
2011-08-17 00:56:18 +02:00
if ((fmt = imlib_image_format()) != NULL) {
Rework the build system (#19) * Added simple configure script Added simple script to autodetect if optional dependencies are installed and enable/disable them as needed. Note this solution uses the compiler directly instead of an external program like pkgconfig so it doesn't require any extra dependencies. It is intended to work with any arbitrary compiler; it has been tested with tcc and gcc. There are some "breaking" changes hidden here - HAVE_GIFLIB was renamed to HAVE_LIBGIF to match HAVE_LIBEXIF - Simply typing `make` will no longer try to build with optional dependencies * use implicit RM variable * General clean-up in configure - Use printf instead of echo - Format style * honor env PREFIX, use LDLIBS * Revert "General clean-up in configure" This reverts commit 8683c179dbf273a330f9a224a4d481a7bea42c5f. * honor env LDFLAGS if set * Don't set OPTIONAL_LIBS in configure * make OBJ all caps * follow suckless style build system - remove configure script. - HAVE_LIBGIF and HAVE_LIBEXIF defaults back to 1 - unload several varibales onto config.mk - make version all-caps - add -O2 optimization - use CPPFLAGS for includes and defines * Revert "follow suckless style build system" This reverts commit 8bf75b1f68d72df349edba8d998d4659dd956dd8. * Generate config.mk from make * Inlined configure in Makefile * update docs * cleanups - changes to config.mk should trigger a rebuild - remove potentially confusing variables form Makefile * Use install instead of mkdir/cp/chmod when sensible * fixup! Inlined configure in Makefile * Don't generate config.mk on rm -f *.o sxiv * update docs and cleanups - make config.mk silent - mention editing config.mk in README * fallback to 0 if user edits config.mk in unexpected way * add comment on config.mk * remove invalid comment configure script is removed * slight restructure - make version all caps - restructure variables that users may want to edit to top - use CPPFLAGS for defines - add some comments - remove needless echos since we have verbose output now * add echos back Co-authored-by: NRK <nrk@disroot.org> Co-authored-by: Berke Kocaoğlu <berke.kocaoglu@metu.edu.tr>
2021-09-14 16:42:57 +02:00
#if HAVE_LIBGIF
if (STREQ(fmt, "gif"))
img_load_gif(img, file);
#endif
#if HAVE_LIBWEBP
if (STREQ(fmt, "webp"))
img_load_webp(img, file);
#endif
#if HAVE_LIBEXIF && defined(IMLIB2_VERSION)
if (!STREQ(fmt, "jpeg") && !STREQ(fmt, "jpg"))
exif_auto_orientate(file);
#endif
}
img->w = imlib_image_get_width();
img->h = imlib_image_get_height();
img->checkpan = true;
2011-09-11 21:01:24 +02:00
img->dirty = true;
2011-01-22 23:27:29 +01:00
2011-09-11 21:01:24 +02:00
return true;
2011-01-18 17:20:41 +01:00
}
2011-01-18 20:11:28 +01:00
CLEANUP void img_close(img_t *img, bool decache)
{
int i;
2011-09-29 12:43:36 +02:00
if (img->multi.cnt > 0) {
for (i = 0; i < img->multi.cnt; i++) {
2011-08-18 16:05:32 +02:00
imlib_context_set_image(img->multi.frames[i].im);
imlib_free_image();
}
img->multi.cnt = 0;
img->im = NULL;
2011-09-29 12:43:36 +02:00
} else if (img->im != NULL) {
2011-02-25 12:08:12 +01:00
imlib_context_set_image(img->im);
if (decache)
imlib_free_image_and_decache();
else
imlib_free_image();
2011-02-25 12:08:12 +01:00
img->im = NULL;
}
}
static void img_check_pan(img_t *img, bool moved)
{
win_t *win;
float w, h, ox, oy;
2011-09-11 01:13:45 +02:00
win = img->win;
w = img->w * img->zoom;
h = img->h * img->zoom;
2011-09-11 01:13:45 +02:00
ox = img->x;
oy = img->y;
if (w < win->w)
img->x = (win->w - w) / 2;
else if (img->x > 0)
img->x = 0;
else if (img->x + w < win->w)
img->x = win->w - w;
if (h < win->h)
img->y = (win->h - h) / 2;
else if (img->y > 0)
img->y = 0;
else if (img->y + h < win->h)
img->y = win->h - h;
2011-09-11 01:13:45 +02:00
if (!moved && (ox != img->x || oy != img->y))
2011-09-11 21:01:24 +02:00
img->dirty = true;
2011-01-20 23:22:00 +01:00
}
static bool img_fit(img_t *img)
{
float z, zw, zh;
if (img->scalemode == SCALE_ZOOM)
2011-09-11 21:01:24 +02:00
return false;
zw = (float) img->win->w / (float) img->w;
zh = (float) img->win->h / (float) img->h;
switch (img->scalemode) {
2021-04-14 19:10:14 +02:00
case SCALE_FILL:
z = MAX(zw, zh);
break;
case SCALE_WIDTH:
z = zw;
break;
case SCALE_HEIGHT:
z = zh;
break;
default:
z = MIN(zw, zh);
break;
}
z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : ZOOM_MAX);
if (ABS(img->zoom - z) > 1.0/MAX(img->w, img->h)) {
2011-09-11 01:13:45 +02:00
img->zoom = z;
img->dirty = title_dirty = true;
2011-09-11 21:01:24 +02:00
return true;
2011-09-11 01:13:45 +02:00
} else {
2011-09-11 21:01:24 +02:00
return false;
2011-09-11 01:13:45 +02:00
}
}
void img_render(img_t *img)
{
win_t *win;
2011-01-20 15:39:08 +01:00
int sx, sy, sw, sh;
int dx, dy, dw, dh;
Imlib_Image bg;
2011-01-20 15:39:08 +01:00
win = img->win;
img_fit(img);
2011-01-21 12:13:52 +01:00
2011-01-22 23:27:29 +01:00
if (img->checkpan) {
img_check_pan(img, false);
2011-09-11 21:01:24 +02:00
img->checkpan = false;
2011-01-21 12:13:52 +01:00
}
2011-01-20 23:22:00 +01:00
2011-09-11 01:13:45 +02:00
if (!img->dirty)
return;
/* calculate source and destination offsets:
* - part of image drawn on full window, or
* - full image drawn on part of window
*/
if (img->x <= 0) {
sx = -img->x / img->zoom + 0.5;
2011-01-20 21:44:34 +01:00
sw = win->w / img->zoom;
dx = 0;
dw = win->w;
2011-01-18 20:11:28 +01:00
} else {
sx = 0;
2011-01-20 21:44:34 +01:00
sw = img->w;
2011-01-18 20:11:28 +01:00
dx = img->x;
dw = MAX(img->w * img->zoom, 1);
2011-01-18 20:11:28 +01:00
}
if (img->y <= 0) {
sy = -img->y / img->zoom + 0.5;
2011-01-20 21:44:34 +01:00
sh = win->h / img->zoom;
dy = win->bar.top ? win->bar.h : 0;
2011-01-20 21:44:34 +01:00
dh = win->h;
2011-01-18 20:11:28 +01:00
} else {
sy = 0;
2011-01-20 21:44:34 +01:00
sh = img->h;
dy = img->y + (win->bar.top ? win->bar.h : 0);
dh = MAX(img->h * img->zoom, 1);
2011-01-18 20:11:28 +01:00
}
2011-01-20 21:44:34 +01:00
win_clear(win);
2011-01-18 20:11:28 +01:00
imlib_context_set_image(img->im);
imlib_context_set_anti_alias(img->aa);
imlib_context_set_drawable(win->buf.pm);
2011-01-20 21:44:34 +01:00
/* manual blending, for performance reasons.
* see https://phab.enlightenment.org/T8969#156167 for more details.
*/
if (imlib_image_has_alpha()) {
if ((bg = imlib_create_image(dw, dh)) == NULL) {
error(0, ENOMEM, "Failed to create image");
goto fallback;
}
imlib_context_set_image(bg);
imlib_image_set_has_alpha(0);
if (img->alpha) {
int i, c, r;
DATA32 col[2] = { 0xFF666666, 0xFF999999 };
DATA32 * data = imlib_image_get_data();
for (r = 0; r < dh; r++) {
i = r * dw;
if (r == 0 || r == 8) {
for (c = 0; c < dw; c++)
data[i++] = col[!(c & 8) ^ !r];
} else {
memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0]));
}
}
imlib_image_put_back_data(data);
} else {
XColor c = win->win_bg;
imlib_context_set_color(c.red >> 8, c.green >> 8, c.blue >> 8, 0xFF);
imlib_image_fill_rectangle(0, 0, dw, dh);
}
imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh);
imlib_context_set_color_modifier(NULL);
imlib_render_image_on_drawable(dx, dy);
imlib_free_image();
imlib_context_set_color_modifier(img->cmod);
} else {
fallback:
imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh);
}
2011-09-11 21:01:24 +02:00
img->dirty = false;
2011-01-18 20:11:28 +01:00
}
2011-01-20 23:22:00 +01:00
bool img_fit_win(img_t *img, scalemode_t sm)
{
float oz;
2011-02-03 14:32:02 +01:00
oz = img->zoom;
img->scalemode = sm;
if (img_fit(img)) {
img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz;
img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz;
img->checkpan = true;
2011-09-11 21:01:24 +02:00
return true;
2011-09-11 01:13:45 +02:00
} else {
2011-09-11 21:01:24 +02:00
return false;
2011-09-11 01:13:45 +02:00
}
2011-01-28 13:34:16 +01:00
}
bool img_zoom_to(img_t *img, float z)
{
int x, y;
if (ZOOM_MIN <= z && z <= ZOOM_MAX) {
2017-10-16 10:56:53 +02:00
win_cursor_pos(img->win, &x, &y);
if (x < 0 || x >= img->win->w || y < 0 || y >= img->win->h) {
x = img->win->w / 2;
y = img->win->h / 2;
}
img->x = x - (x - img->x) * z / img->zoom;
img->y = y - (y - img->y) * z / img->zoom;
2011-01-20 23:22:00 +01:00
img->zoom = z;
img->scalemode = SCALE_ZOOM;
img->dirty = img->checkpan = title_dirty = true;
2011-09-11 21:01:24 +02:00
return true;
2011-01-20 23:22:00 +01:00
} else {
2011-09-11 21:01:24 +02:00
return false;
2011-01-20 23:22:00 +01:00
}
}
bool img_zoom(img_t *img, int d)
{
int i = d > 0 ? 0 : ARRLEN(zoom_levels)-1;
while (i >= 0 && i < ARRLEN(zoom_levels) && (d > 0 ?
zoom_levels[i]/100 <= img->zoom : zoom_levels[i]/100 >= img->zoom))
{
i += d;
}
i = MIN(MAX(i, 0), ARRLEN(zoom_levels)-1);
return img_zoom_to(img, zoom_levels[i]/100);
2011-01-20 23:22:00 +01:00
}
2011-01-21 13:48:02 +01:00
bool img_pos(img_t *img, float x, float y)
{
float ox, oy;
2011-01-21 13:48:02 +01:00
ox = img->x;
oy = img->y;
img->x = x;
img->y = y;
2011-01-29 22:37:40 +01:00
img_check_pan(img, true);
2011-01-29 22:37:40 +01:00
2011-09-11 01:13:45 +02:00
if (ox != img->x || oy != img->y) {
2011-09-11 21:01:24 +02:00
img->dirty = true;
return true;
2011-09-11 01:13:45 +02:00
} else {
2011-09-11 21:01:24 +02:00
return false;
2011-09-11 01:13:45 +02:00
}
2011-01-29 22:37:40 +01:00
}
static bool img_move(img_t *img, float dx, float dy)
{
return img_pos(img, img->x + dx, img->y + dy);
}
bool img_pan(img_t *img, direction_t dir, int d)
{
/* d < 0: screen-wise
2017-07-23 02:15:47 +02:00
* d = 0: 1/PAN_FRACTION of screen
* d > 0: num of pixels
*/
float x, y;
if (d > 0) {
x = y = MAX(1, (float) d * img->zoom);
} else {
2017-07-23 02:15:47 +02:00
x = img->win->w / (d < 0 ? 1 : PAN_FRACTION);
y = img->win->h / (d < 0 ? 1 : PAN_FRACTION);
}
2011-01-21 13:48:02 +01:00
switch (dir) {
2011-07-22 14:49:06 +02:00
case DIR_LEFT:
return img_move(img, x, 0.0);
2011-07-22 14:49:06 +02:00
case DIR_RIGHT:
return img_move(img, -x, 0.0);
2011-07-22 14:49:06 +02:00
case DIR_UP:
return img_move(img, 0.0, y);
2011-07-22 14:49:06 +02:00
case DIR_DOWN:
return img_move(img, 0.0, -y);
2011-01-21 13:48:02 +01:00
}
2011-09-11 21:01:24 +02:00
return false;
2011-01-21 13:48:02 +01:00
}
2011-01-26 14:42:10 +01:00
bool img_pan_center(img_t *img)
{
float x, y;
x = (img->win->w - img->w * img->zoom) / 2.0;
y = (img->win->h - img->h * img->zoom) / 2.0;
return img_pos(img, x, y);
}
bool img_pan_edge(img_t *img, direction_t dir)
{
float ox, oy;
2011-05-16 15:54:09 +02:00
ox = img->x;
oy = img->y;
if (dir & DIR_LEFT)
img->x = 0;
if (dir & DIR_RIGHT)
img->x = img->win->w - img->w * img->zoom;
if (dir & DIR_UP)
img->y = 0;
if (dir & DIR_DOWN)
img->y = img->win->h - img->h * img->zoom;
2011-05-16 15:54:09 +02:00
img_check_pan(img, true);
2011-05-16 15:54:09 +02:00
2011-09-11 01:13:45 +02:00
if (ox != img->x || oy != img->y) {
2011-09-11 21:01:24 +02:00
img->dirty = true;
return true;
2011-09-11 01:13:45 +02:00
} else {
2011-09-11 21:01:24 +02:00
return false;
2011-09-11 01:13:45 +02:00
}
2011-05-16 15:54:09 +02:00
}
2013-08-10 15:55:18 +02:00
void img_rotate(img_t *img, degree_t d)
{
int i, tmp;
float ox, oy;
2011-01-26 14:42:10 +01:00
2011-02-25 12:08:12 +01:00
imlib_context_set_image(img->im);
2011-01-26 14:42:10 +01:00
imlib_image_orientate(d);
for (i = 0; i < img->multi.cnt; i++) {
if (i != img->multi.sel) {
imlib_context_set_image(img->multi.frames[i].im);
imlib_image_orientate(d);
}
}
2013-08-10 15:55:18 +02:00
if (d == DEGREE_90 || d == DEGREE_270) {
ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom;
oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom;
img->x = oy + (img->win->w - img->win->h) / 2;
img->y = ox + (img->win->h - img->win->w) / 2;
2011-01-26 14:42:10 +01:00
2013-06-23 16:02:26 +02:00
tmp = img->w;
img->w = img->h;
img->h = tmp;
img->checkpan = true;
}
2011-09-11 21:01:24 +02:00
img->dirty = true;
2011-01-26 14:42:10 +01:00
}
void img_flip(img_t *img, flipdir_t d)
{
int i;
void (*imlib_flip_op[3])(void) = {
imlib_image_flip_horizontal,
imlib_image_flip_vertical,
imlib_image_flip_diagonal
};
d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1;
2015-10-28 20:59:48 +01:00
if (d < 0 || d >= ARRLEN(imlib_flip_op))
return;
imlib_context_set_image(img->im);
imlib_flip_op[d]();
for (i = 0; i < img->multi.cnt; i++) {
if (i != img->multi.sel) {
imlib_context_set_image(img->multi.frames[i].im);
imlib_flip_op[d]();
}
}
2012-05-06 13:02:34 +02:00
img->dirty = true;
}
void img_toggle_antialias(img_t *img)
{
2011-09-11 21:01:24 +02:00
img->aa = !img->aa;
2011-09-11 01:13:45 +02:00
imlib_context_set_image(img->im);
imlib_context_set_anti_alias(img->aa);
2011-09-11 21:01:24 +02:00
img->dirty = true;
}
2011-08-18 16:05:32 +02:00
2013-11-14 17:06:20 +01:00
bool img_change_gamma(img_t *img, int d)
{
2013-11-14 17:06:20 +01:00
/* d < 0: decrease gamma
* d = 0: reset gamma
* d > 0: increase gamma
*/
int gamma;
double range;
2013-11-14 17:06:20 +01:00
if (d == 0)
gamma = 0;
else
gamma = MIN(MAX(img->gamma + d, -GAMMA_RANGE), GAMMA_RANGE);
2013-11-14 17:06:20 +01:00
if (img->gamma != gamma) {
imlib_reset_color_modifier();
if (gamma) {
range = gamma <= 0 ? 1.0 : GAMMA_MAX - 1.0;
imlib_modify_color_modifier_gamma(1.0 + gamma * (range / GAMMA_RANGE));
}
2013-11-14 17:06:20 +01:00
img->gamma = gamma;
img->dirty = true;
2013-11-14 17:06:20 +01:00
return true;
} else {
return false;
}
}
static bool img_frame_goto(img_t *img, int n)
{
if (n < 0 || n >= img->multi.cnt || n == img->multi.sel)
2011-09-11 21:01:24 +02:00
return false;
2011-08-18 16:05:32 +02:00
img->multi.sel = n;
img->im = img->multi.frames[n].im;
imlib_context_set_image(img->im);
img->w = imlib_image_get_width();
img->h = imlib_image_get_height();
2011-09-11 21:01:24 +02:00
img->checkpan = true;
img->dirty = true;
2011-08-18 16:05:32 +02:00
2011-09-11 21:01:24 +02:00
return true;
2011-08-18 16:05:32 +02:00
}
bool img_frame_navigate(img_t *img, int d)
{
2015-10-28 20:59:48 +01:00
if (img->multi.cnt == 0 || d == 0)
2011-09-11 21:01:24 +02:00
return false;
2011-08-18 16:05:32 +02:00
d += img->multi.sel;
d = MAX(0, MIN(d, img->multi.cnt - 1));
2011-08-18 16:05:32 +02:00
return img_frame_goto(img, d);
}
2014-09-01 20:41:27 +02:00
bool img_frame_animate(img_t *img)
{
if (img->multi.cnt > 0)
return img_frame_goto(img, (img->multi.sel + 1) % img->multi.cnt);
2014-09-01 20:41:27 +02:00
else
return false;
2011-08-18 16:05:32 +02:00
}