A Simple X Image Viewer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

742 lines
16 KiB

  1. /* sxiv: image.c
  2. * Copyright (c) 2012 Bert Muennich <be.muennich at googlemail.com>
  3. *
  4. * This program is free software; you can redistribute it and/or modify it
  5. * under the terms of the GNU General Public License as published by the
  6. * Free Software Foundation; either version 2 of the License, or (at your
  7. * option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License along
  15. * with this program; if not, write to the Free Software Foundation, Inc.,
  16. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. */
  18. #define _POSIX_C_SOURCE 200112L
  19. #define _IMAGE_CONFIG
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <sys/types.h>
  23. #include <unistd.h>
  24. #include <gif_lib.h>
  25. #include "exif.h"
  26. #include "image.h"
  27. #include "options.h"
  28. #include "util.h"
  29. #include "config.h"
  30. enum { MIN_GIF_DELAY = 50 };
  31. float zoom_min;
  32. float zoom_max;
  33. int zoomdiff(float z1, float z2) {
  34. float d = z1 - z2;
  35. const float mindelta = 0.001;
  36. if (ABS(d) < mindelta)
  37. return 0;
  38. else
  39. return d < 0 ? -1 : 1;
  40. }
  41. void img_init(img_t *img, win_t *win) {
  42. zoom_min = zoom_levels[0] / 100.0;
  43. zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0;
  44. if (img == NULL || win == NULL)
  45. return;
  46. imlib_context_set_display(win->env.dpy);
  47. imlib_context_set_visual(win->env.vis);
  48. imlib_context_set_colormap(win->env.cmap);
  49. img->im = NULL;
  50. img->win = win;
  51. img->zoom = options->zoom;
  52. img->zoom = MAX(img->zoom, zoom_min);
  53. img->zoom = MIN(img->zoom, zoom_max);
  54. img->checkpan = false;
  55. img->dirty = false;
  56. img->aa = options->aa;
  57. img->alpha = true;
  58. img->multi.cap = img->multi.cnt = 0;
  59. img->multi.animate = false;
  60. }
  61. void exif_auto_orientate(const fileinfo_t *file) {
  62. switch (exif_orientation(file)) {
  63. case 5:
  64. imlib_image_orientate(1);
  65. case 2:
  66. imlib_image_flip_vertical();
  67. break;
  68. case 3:
  69. imlib_image_orientate(2);
  70. break;
  71. case 7:
  72. imlib_image_orientate(1);
  73. case 4:
  74. imlib_image_flip_horizontal();
  75. break;
  76. case 6:
  77. imlib_image_orientate(1);
  78. break;
  79. case 8:
  80. imlib_image_orientate(3);
  81. break;
  82. }
  83. }
  84. bool img_load_gif(img_t *img, const fileinfo_t *file) {
  85. GifFileType *gif;
  86. GifRowType *rows = NULL;
  87. GifRecordType rec;
  88. ColorMapObject *cmap;
  89. DATA32 bgpixel, *data, *ptr;
  90. DATA32 *prev_frame = NULL;
  91. Imlib_Image *im;
  92. int i, j, bg, r, g, b;
  93. int x, y, w, h, sw, sh;
  94. int px, py, pw, ph;
  95. int intoffset[] = { 0, 4, 2, 1 };
  96. int intjump[] = { 8, 8, 4, 2 };
  97. int transp = -1;
  98. unsigned int disposal = 0, prev_disposal = 0;
  99. unsigned int delay = 0;
  100. bool err = false;
  101. if (img->multi.cap == 0) {
  102. img->multi.cap = 8;
  103. img->multi.frames = (img_frame_t*)
  104. s_malloc(sizeof(img_frame_t) * img->multi.cap);
  105. }
  106. img->multi.cnt = 0;
  107. img->multi.sel = 0;
  108. gif = DGifOpenFileName(file->path);
  109. if (gif == NULL) {
  110. warn("could not open gif file: %s", file->name);
  111. return false;
  112. }
  113. bg = gif->SBackGroundColor;
  114. sw = gif->SWidth;
  115. sh = gif->SHeight;
  116. px = py = pw = ph = 0;
  117. do {
  118. if (DGifGetRecordType(gif, &rec) == GIF_ERROR) {
  119. err = true;
  120. break;
  121. }
  122. if (rec == EXTENSION_RECORD_TYPE) {
  123. int ext_code;
  124. GifByteType *ext = NULL;
  125. DGifGetExtension(gif, &ext_code, &ext);
  126. while (ext) {
  127. if (ext_code == 0xf9) {
  128. if (ext[1] & 1)
  129. transp = (int) ext[4];
  130. else
  131. transp = -1;
  132. delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]);
  133. if (delay)
  134. delay = MAX(delay, MIN_GIF_DELAY);
  135. disposal = (unsigned int) ext[1] >> 2 & 0x7;
  136. }
  137. ext = NULL;
  138. DGifGetExtensionNext(gif, &ext);
  139. }
  140. } else if (rec == IMAGE_DESC_RECORD_TYPE) {
  141. if (DGifGetImageDesc(gif) == GIF_ERROR) {
  142. err = true;
  143. break;
  144. }
  145. x = gif->Image.Left;
  146. y = gif->Image.Top;
  147. w = gif->Image.Width;
  148. h = gif->Image.Height;
  149. rows = (GifRowType*) s_malloc(h * sizeof(GifRowType));
  150. for (i = 0; i < h; i++)
  151. rows[i] = (GifRowType) s_malloc(w * sizeof(GifPixelType));
  152. if (gif->Image.Interlace) {
  153. for (i = 0; i < 4; i++) {
  154. for (j = intoffset[i]; j < h; j += intjump[i])
  155. DGifGetLine(gif, rows[j], w);
  156. }
  157. } else {
  158. for (i = 0; i < h; i++)
  159. DGifGetLine(gif, rows[i], w);
  160. }
  161. ptr = data = (DATA32*) s_malloc(sizeof(DATA32) * sw * sh);
  162. cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap;
  163. r = cmap->Colors[bg].Red;
  164. g = cmap->Colors[bg].Green;
  165. b = cmap->Colors[bg].Blue;
  166. bgpixel = 0x00ffffff & (r << 16 | g << 8 | b);
  167. for (i = 0; i < sh; i++) {
  168. for (j = 0; j < sw; j++) {
  169. if (i < y || i >= y + h || j < x || j >= x + w ||
  170. rows[i-y][j-x] == transp)
  171. {
  172. if (prev_frame != NULL && (prev_disposal != 2 ||
  173. i < py || i >= py + ph || j < px || j >= px + pw))
  174. {
  175. *ptr = prev_frame[i * sw + j];
  176. } else {
  177. *ptr = bgpixel;
  178. }
  179. } else {
  180. r = cmap->Colors[rows[i-y][j-x]].Red;
  181. g = cmap->Colors[rows[i-y][j-x]].Green;
  182. b = cmap->Colors[rows[i-y][j-x]].Blue;
  183. *ptr = 0xff << 24 | r << 16 | g << 8 | b;
  184. }
  185. ptr++;
  186. }
  187. }
  188. im = imlib_create_image_using_copied_data(sw, sh, data);
  189. for (i = 0; i < h; i++)
  190. free(rows[i]);
  191. free(rows);
  192. free(data);
  193. if (im == NULL) {
  194. err = true;
  195. break;
  196. }
  197. imlib_context_set_image(im);
  198. imlib_image_set_format("gif");
  199. if (transp >= 0)
  200. imlib_image_set_has_alpha(1);
  201. if (disposal != 3)
  202. prev_frame = imlib_image_get_data_for_reading_only();
  203. prev_disposal = disposal;
  204. px = x, py = y, pw = w, ph = h;
  205. if (img->multi.cnt == img->multi.cap) {
  206. img->multi.cap *= 2;
  207. img->multi.frames = (img_frame_t*)
  208. s_realloc(img->multi.frames,
  209. img->multi.cap * sizeof(img_frame_t));
  210. }
  211. img->multi.frames[img->multi.cnt].im = im;
  212. img->multi.frames[img->multi.cnt].delay = delay ? delay : GIF_DELAY;
  213. img->multi.cnt++;
  214. }
  215. } while (rec != TERMINATE_RECORD_TYPE);
  216. DGifCloseFile(gif);
  217. if (err && !file->loaded)
  218. warn("corrupted gif file: %s", file->name);
  219. if (img->multi.cnt > 1) {
  220. imlib_context_set_image(img->im);
  221. imlib_free_image();
  222. img->im = img->multi.frames[0].im;
  223. img->multi.animate = GIF_AUTOPLAY;
  224. } else if (img->multi.cnt == 1) {
  225. imlib_context_set_image(img->multi.frames[0].im);
  226. imlib_free_image();
  227. img->multi.cnt = 0;
  228. img->multi.animate = false;
  229. }
  230. imlib_context_set_image(img->im);
  231. return !err;
  232. }
  233. bool img_load(img_t *img, const fileinfo_t *file) {
  234. const char *fmt;
  235. if (img == NULL || file == NULL || file->name == NULL || file->path == NULL)
  236. return false;
  237. if (access(file->path, R_OK) < 0 ||
  238. (img->im = imlib_load_image(file->path)) == NULL)
  239. {
  240. warn("could not open image: %s", file->name);
  241. return false;
  242. }
  243. imlib_context_set_image(img->im);
  244. imlib_image_set_changes_on_disk();
  245. if ((fmt = imlib_image_format()) == NULL) {
  246. warn("could not open image: %s", file->name);
  247. return false;
  248. }
  249. if (STREQ(fmt, "jpeg"))
  250. exif_auto_orientate(file);
  251. if (STREQ(fmt, "gif"))
  252. img_load_gif(img, file);
  253. img->w = imlib_image_get_width();
  254. img->h = imlib_image_get_height();
  255. img->scalemode = options->scalemode;
  256. img->re = false;
  257. img->checkpan = false;
  258. img->dirty = true;
  259. return true;
  260. }
  261. void img_close(img_t *img, bool decache) {
  262. int i;
  263. if (img == NULL)
  264. return;
  265. if (img->multi.cnt > 0) {
  266. for (i = 0; i < img->multi.cnt; i++) {
  267. imlib_context_set_image(img->multi.frames[i].im);
  268. imlib_free_image();
  269. }
  270. img->multi.cnt = 0;
  271. img->im = NULL;
  272. } else if (img->im != NULL) {
  273. imlib_context_set_image(img->im);
  274. if (decache)
  275. imlib_free_image_and_decache();
  276. else
  277. imlib_free_image();
  278. img->im = NULL;
  279. }
  280. }
  281. void img_check_pan(img_t *img, bool moved) {
  282. win_t *win;
  283. int ox, oy;
  284. if (img == NULL || img->im == NULL || img->win == NULL)
  285. return;
  286. win = img->win;
  287. ox = img->x;
  288. oy = img->y;
  289. if (img->w * img->zoom > win->w) {
  290. if (img->x > 0 && img->x + img->w * img->zoom > win->w)
  291. img->x = 0;
  292. if (img->x < 0 && img->x + img->w * img->zoom < win->w)
  293. img->x = win->w - img->w * img->zoom;
  294. } else {
  295. img->x = (win->w - img->w * img->zoom) / 2;
  296. }
  297. if (img->h * img->zoom > win->h) {
  298. if (img->y > 0 && img->y + img->h * img->zoom > win->h)
  299. img->y = 0;
  300. if (img->y < 0 && img->y + img->h * img->zoom < win->h)
  301. img->y = win->h - img->h * img->zoom;
  302. } else {
  303. img->y = (win->h - img->h * img->zoom) / 2;
  304. }
  305. if (!moved && (ox != img->x || oy != img->y))
  306. img->dirty = true;
  307. }
  308. bool img_fit(img_t *img) {
  309. float z, zmax, zw, zh;
  310. if (img == NULL || img->im == NULL || img->win == NULL)
  311. return false;
  312. if (img->scalemode == SCALE_ZOOM)
  313. return false;
  314. zmax = img->scalemode == SCALE_DOWN ? 1.0 : zoom_max;
  315. zw = (float) img->win->w / (float) img->w;
  316. zh = (float) img->win->h / (float) img->h;
  317. switch (img->scalemode) {
  318. case SCALE_WIDTH:
  319. z = zw;
  320. break;
  321. case SCALE_HEIGHT:
  322. z = zh;
  323. break;
  324. default:
  325. z = MIN(zw, zh);
  326. break;
  327. }
  328. z = MAX(z, zoom_min);
  329. z = MIN(z, zmax);
  330. if (zoomdiff(z, img->zoom) != 0) {
  331. img->zoom = z;
  332. img->dirty = true;
  333. return true;
  334. } else {
  335. return false;
  336. }
  337. }
  338. void img_render(img_t *img) {
  339. win_t *win;
  340. int sx, sy, sw, sh;
  341. int dx, dy, dw, dh;
  342. if (img == NULL || img->im == NULL || img->win == NULL)
  343. return;
  344. win = img->win;
  345. img_fit(img);
  346. if (!img->re) {
  347. /* rendered for the first time */
  348. img->re = true;
  349. if (img->zoom * img->w <= win->w)
  350. img->x = (win->w - img->w * img->zoom) / 2;
  351. else
  352. img->x = 0;
  353. if (img->zoom * img->h <= win->h)
  354. img->y = (win->h - img->h * img->zoom) / 2;
  355. else
  356. img->y = 0;
  357. }
  358. if (img->checkpan) {
  359. img_check_pan(img, false);
  360. img->checkpan = false;
  361. }
  362. if (!img->dirty)
  363. return;
  364. /* calculate source and destination offsets */
  365. if (img->x < 0) {
  366. sx = -img->x / img->zoom;
  367. sw = win->w / img->zoom;
  368. dx = 0;
  369. dw = win->w;
  370. } else {
  371. sx = 0;
  372. sw = img->w;
  373. dx = img->x;
  374. dw = img->w * img->zoom;
  375. }
  376. if (img->y < 0) {
  377. sy = -img->y / img->zoom;
  378. sh = win->h / img->zoom;
  379. dy = 0;
  380. dh = win->h;
  381. } else {
  382. sy = 0;
  383. sh = img->h;
  384. dy = img->y;
  385. dh = img->h * img->zoom;
  386. }
  387. win_clear(win);
  388. imlib_context_set_image(img->im);
  389. imlib_context_set_anti_alias(img->aa);
  390. if (!img->alpha && imlib_image_has_alpha())
  391. win_draw_rect(win, win->pm, dx, dy, dw, dh, True, 0, win->white);
  392. imlib_context_set_drawable(win->pm);
  393. imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh);
  394. img->dirty = false;
  395. }
  396. bool img_fit_win(img_t *img, scalemode_t sm) {
  397. if (img == NULL || img->im == NULL)
  398. return false;
  399. img->scalemode = sm;
  400. return img_fit(img);
  401. }
  402. bool img_center(img_t *img) {
  403. int ox, oy;
  404. if (img == NULL || img->im == NULL || img->win == NULL)
  405. return false;
  406. ox = img->x;
  407. oy = img->y;
  408. img->x = (img->win->w - img->w * img->zoom) / 2;
  409. img->y = (img->win->h - img->h * img->zoom) / 2;
  410. if (ox != img->x || oy != img->y) {
  411. img->dirty = true;
  412. return true;
  413. } else {
  414. return false;
  415. }
  416. }
  417. bool img_zoom(img_t *img, float z) {
  418. if (img == NULL || img->im == NULL || img->win == NULL)
  419. return false;
  420. z = MAX(z, zoom_min);
  421. z = MIN(z, zoom_max);
  422. img->scalemode = SCALE_ZOOM;
  423. if (zoomdiff(z, img->zoom) != 0) {
  424. img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * z / img->zoom;
  425. img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * z / img->zoom;
  426. img->zoom = z;
  427. img->checkpan = true;
  428. img->dirty = true;
  429. return true;
  430. } else {
  431. return false;
  432. }
  433. }
  434. bool img_zoom_in(img_t *img) {
  435. int i;
  436. float z;
  437. if (img == NULL || img->im == NULL)
  438. return false;
  439. for (i = 1; i < ARRLEN(zoom_levels); i++) {
  440. z = zoom_levels[i] / 100.0;
  441. if (zoomdiff(z, img->zoom) > 0)
  442. return img_zoom(img, z);
  443. }
  444. return false;
  445. }
  446. bool img_zoom_out(img_t *img) {
  447. int i;
  448. float z;
  449. if (img == NULL || img->im == NULL)
  450. return false;
  451. for (i = ARRLEN(zoom_levels) - 2; i >= 0; i--) {
  452. z = zoom_levels[i] / 100.0;
  453. if (zoomdiff(z, img->zoom) < 0)
  454. return img_zoom(img, z);
  455. }
  456. return false;
  457. }
  458. bool img_move(img_t *img, float dx, float dy) {
  459. float ox, oy;
  460. if (img == NULL || img->im == NULL)
  461. return false;
  462. ox = img->x;
  463. oy = img->y;
  464. img->x += dx;
  465. img->y += dy;
  466. img_check_pan(img, true);
  467. if (ox != img->x || oy != img->y) {
  468. img->dirty = true;
  469. return true;
  470. } else {
  471. return false;
  472. }
  473. }
  474. bool img_pan(img_t *img, direction_t dir, int d) {
  475. /* d < 0: screen-wise
  476. * d = 0: 1/5 of screen
  477. * d > 0: num of pixels
  478. */
  479. float x, y;
  480. if (img == NULL || img->im == NULL || img->win == NULL)
  481. return false;
  482. if (d > 0) {
  483. x = y = MAX(1, (float) d * img->zoom);
  484. } else {
  485. x = img->win->w / (d < 0 ? 1 : 5);
  486. y = img->win->h / (d < 0 ? 1 : 5);
  487. }
  488. switch (dir) {
  489. case DIR_LEFT:
  490. return img_move(img, x, 0.0);
  491. case DIR_RIGHT:
  492. return img_move(img, -x, 0.0);
  493. case DIR_UP:
  494. return img_move(img, 0.0, y);
  495. case DIR_DOWN:
  496. return img_move(img, 0.0, -y);
  497. }
  498. return false;
  499. }
  500. bool img_pan_edge(img_t *img, direction_t dir) {
  501. int ox, oy;
  502. if (img == NULL || img->im == NULL || img->win == NULL)
  503. return false;
  504. ox = img->x;
  505. oy = img->y;
  506. switch (dir) {
  507. case DIR_LEFT:
  508. img->x = 0;
  509. break;
  510. case DIR_RIGHT:
  511. img->x = img->win->w - img->w * img->zoom;
  512. break;
  513. case DIR_UP:
  514. img->y = 0;
  515. break;
  516. case DIR_DOWN:
  517. img->y = img->win->h - img->h * img->zoom;
  518. break;
  519. }
  520. img_check_pan(img, true);
  521. if (ox != img->x || oy != img->y) {
  522. img->dirty = true;
  523. return true;
  524. } else {
  525. return false;
  526. }
  527. }
  528. void img_rotate(img_t *img, int d) {
  529. win_t *win;
  530. int ox, oy, tmp;
  531. if (img == NULL || img->im == NULL || img->win == NULL)
  532. return;
  533. win = img->win;
  534. ox = d == 1 ? img->x : win->w - img->x - img->w * img->zoom;
  535. oy = d == 3 ? img->y : win->h - img->y - img->h * img->zoom;
  536. imlib_context_set_image(img->im);
  537. imlib_image_orientate(d);
  538. img->x = oy + (win->w - win->h) / 2;
  539. img->y = ox + (win->h - win->w) / 2;
  540. tmp = img->w;
  541. img->w = img->h;
  542. img->h = tmp;
  543. img->checkpan = true;
  544. img->dirty = true;
  545. }
  546. void img_rotate_left(img_t *img) {
  547. img_rotate(img, 3);
  548. }
  549. void img_rotate_right(img_t *img) {
  550. img_rotate(img, 1);
  551. }
  552. void img_flip(img_t *img, flipdir_t d) {
  553. if (img == NULL || img->im == NULL)
  554. return;
  555. imlib_context_set_image(img->im);
  556. switch (d) {
  557. case FLIP_HORIZONTAL:
  558. imlib_image_flip_horizontal();
  559. break;
  560. case FLIP_VERTICAL:
  561. imlib_image_flip_vertical();
  562. break;
  563. }
  564. img->dirty = true;
  565. }
  566. void img_toggle_antialias(img_t *img) {
  567. if (img == NULL || img->im == NULL)
  568. return;
  569. img->aa = !img->aa;
  570. imlib_context_set_image(img->im);
  571. imlib_context_set_anti_alias(img->aa);
  572. img->dirty = true;
  573. }
  574. bool img_frame_goto(img_t *img, int n) {
  575. if (img == NULL || img->im == NULL)
  576. return false;
  577. if (n < 0 || n >= img->multi.cnt || n == img->multi.sel)
  578. return false;
  579. img->multi.sel = n;
  580. img->im = img->multi.frames[n].im;
  581. imlib_context_set_image(img->im);
  582. img->w = imlib_image_get_width();
  583. img->h = imlib_image_get_height();
  584. img->checkpan = true;
  585. img->dirty = true;
  586. return true;
  587. }
  588. bool img_frame_navigate(img_t *img, int d) {
  589. if (img == NULL|| img->im == NULL || img->multi.cnt == 0 || d == 0)
  590. return false;
  591. d += img->multi.sel;
  592. if (d < 0)
  593. d = 0;
  594. else if (d >= img->multi.cnt)
  595. d = img->multi.cnt - 1;
  596. return img_frame_goto(img, d);
  597. }
  598. bool img_frame_animate(img_t *img, bool restart) {
  599. if (img == NULL || img->im == NULL || img->multi.cnt == 0)
  600. return false;
  601. if (img->multi.sel + 1 >= img->multi.cnt) {
  602. if (restart || GIF_LOOP) {
  603. img_frame_goto(img, 0);
  604. } else {
  605. img->multi.animate = false;
  606. return false;
  607. }
  608. } else if (!restart) {
  609. img_frame_goto(img, img->multi.sel + 1);
  610. }
  611. img->multi.animate = true;
  612. img->dirty = true;
  613. return true;
  614. }