render.c 30.6 KB
Newer Older
Moritz Lipp's avatar
Moritz Lipp committed
1 2 3
/* See LICENSE file for license and copyright information */

#include <math.h>
4
#include <string.h>
5 6
#include <girara/datastructures.h>
#include <girara/utils.h>
7

Moritz Lipp's avatar
Moritz Lipp committed
8
#include "render.h"
9
#include "adjustment.h"
Moritz Lipp's avatar
Moritz Lipp committed
10
#include "zathura.h"
11
#include "document.h"
Moritz Lipp's avatar
Moritz Lipp committed
12
#include "page.h"
13
#include "page-widget.h"
14
#include "utils.h"
Moritz Lipp's avatar
Moritz Lipp committed
15

16 17
/* private data for ZathuraRenderer */
typedef struct private_s {
18
  GThreadPool* pool; /**< Pool of threads */
19
  GMutex mutex; /**< Render lock */
20 21
  volatile bool about_to_close; /**< Render thread is to be freed */

22 23 24
  /**
   * recolor information
   */
25 26 27
  struct {
    bool enabled;
    bool hue;
28
    bool reverse_video;
29

30 31
    GdkRGBA light;
    GdkRGBA dark;
32
  } recolor;
33 34 35 36 37 38 39 40 41 42 43 44

  /*
   * page cache
   */
  struct {
    int* cache;
    size_t size;
    size_t num_cached_pages;
  } page_cache;

  /* render requests */
  girara_list_t* requests;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
45
} ZathuraRendererPrivate;
46 47 48 49 50

/* private data for ZathuraRenderRequest */
typedef struct request_private_s {
  ZathuraRenderer* renderer;
  zathura_page_t* page;
51
  gint64 last_view_time;
52
  girara_list_t* active_jobs;
53
  GMutex jobs_mutex;
54
  bool render_plain;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
55
} ZathuraRenderRequestPrivate;
56

Sebastian Ramacher's avatar
Sebastian Ramacher committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
/* define the two types */
G_DEFINE_TYPE_WITH_CODE(ZathuraRenderer, zathura_renderer, G_TYPE_OBJECT, G_ADD_PRIVATE(ZathuraRenderer))
G_DEFINE_TYPE_WITH_CODE(ZathuraRenderRequest, zathura_render_request, G_TYPE_OBJECT, G_ADD_PRIVATE(ZathuraRenderRequest))

/* private methods for ZathuraRenderer  */
static void renderer_finalize(GObject* object);
/* private methods for ZathuraRenderRequest */
static void render_request_dispose(GObject* object);
static void render_request_finalize(GObject* object);

static void render_job(void* data, void* user_data);
static gint render_thread_sort(gconstpointer a, gconstpointer b, gpointer data);
static ssize_t page_cache_lru_invalidate(ZathuraRenderer* renderer);
static void page_cache_invalidate_all(ZathuraRenderer* renderer);
static bool page_cache_is_full(ZathuraRenderer* renderer, bool* result);
72

73 74 75 76 77 78
/* job descritption for render thread */
typedef struct render_job_s {
  ZathuraRenderRequest* request;
  volatile bool aborted;
} render_job_t;

79
/* init, new and free for ZathuraRenderer */
80 81

static void
82
zathura_renderer_class_init(ZathuraRendererClass* class)
83
{
84 85
  /* overwrite methods */
  GObjectClass* object_class = G_OBJECT_CLASS(class);
86
  object_class->finalize     = renderer_finalize;
87 88 89 90 91
}

static void
zathura_renderer_init(ZathuraRenderer* renderer)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
92
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
93 94 95
  priv->pool = g_thread_pool_new(render_job, renderer, 1, TRUE, NULL);
  priv->about_to_close = false;
  g_thread_pool_set_sort_function(priv->pool, render_thread_sort, NULL);
96
  g_mutex_init(&priv->mutex);
97

98
  /* recolor */
99 100
  priv->recolor.enabled = false;
  priv->recolor.hue = true;
101
  priv->recolor.reverse_video = false;
102

103 104 105 106 107
  /* page cache */
  priv->page_cache.size = 0;
  priv->page_cache.cache = NULL;
  priv->page_cache.num_cached_pages = 0;

108
  zathura_renderer_set_recolor_colors_str(renderer, "#000000", "#FFFFFF");
109 110 111 112

  priv->requests = girara_list_new();
}

113
static bool
114 115
page_cache_init(ZathuraRenderer* renderer, size_t cache_size)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
116
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
117

118 119
  priv->page_cache.size  = cache_size;
  priv->page_cache.cache = g_try_malloc0(cache_size * sizeof(int));
120 121 122 123
  if (priv->page_cache.cache == NULL) {
    return false;
  }

124
  page_cache_invalidate_all(renderer);
125
  return true;
126 127 128
}

ZathuraRenderer*
129
zathura_renderer_new(size_t cache_size)
130
{
131 132
  g_return_val_if_fail(cache_size > 0, NULL);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
133
  GObject* obj = g_object_new(ZATHURA_TYPE_RENDERER, NULL);
134
  ZathuraRenderer* ret = ZATHURA_RENDERER(obj);
135 136

  if (page_cache_init(ret, cache_size) == false) {
137
    g_object_unref(obj);
138 139
    return NULL;
  }
140 141

  return ret;
142 143 144
}

static void
145
renderer_finalize(GObject* object)
146 147
{
  ZathuraRenderer* renderer = ZATHURA_RENDERER(object);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
148
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
149 150

  zathura_renderer_stop(renderer);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
151
  if (priv->pool != NULL) {
152
    g_thread_pool_free(priv->pool, TRUE, TRUE);
Moritz Lipp's avatar
Moritz Lipp committed
153
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
154
  g_mutex_clear(&(priv->mutex));
155

156
  g_free(priv->page_cache.cache);
157 158 159 160 161 162 163 164 165
  girara_list_free(priv->requests);
}

/* (un)register requests at the renderer */

static void
renderer_unregister_request(ZathuraRenderer* renderer,
    ZathuraRenderRequest* request)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
166
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
167 168 169 170 171 172 173
  girara_list_remove(priv->requests, request);
}

static void
renderer_register_request(ZathuraRenderer* renderer,
    ZathuraRenderRequest* request)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
174
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
175 176 177
  if (girara_list_contains(priv->requests, request) == false) {
    girara_list_append(priv->requests, request);
  }
178 179 180 181 182 183
}

/* init, new and free for ZathuraRenderRequest */

enum {
  REQUEST_COMPLETED,
184 185
  REQUEST_CACHE_ADDED,
  REQUEST_CACHE_INVALIDATED,
186 187
  REQUEST_LAST_SIGNAL
};
Moritz Lipp's avatar
Moritz Lipp committed
188

189 190 191 192 193 194 195
static guint request_signals[REQUEST_LAST_SIGNAL] = { 0 };

static void
zathura_render_request_class_init(ZathuraRenderRequestClass* class)
{
  /* overwrite methods */
  GObjectClass* object_class = G_OBJECT_CLASS(class);
196
  object_class->dispose      = render_request_dispose;
197
  object_class->finalize     = render_request_finalize;
198 199 200 201 202 203 204 205 206 207 208

  request_signals[REQUEST_COMPLETED] = g_signal_new("completed",
      ZATHURA_TYPE_RENDER_REQUEST,
      G_SIGNAL_RUN_LAST,
      0,
      NULL,
      NULL,
      g_cclosure_marshal_generic,
      G_TYPE_NONE,
      1,
      G_TYPE_POINTER);
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228

  request_signals[REQUEST_CACHE_ADDED] = g_signal_new("cache-added",
      ZATHURA_TYPE_RENDER_REQUEST,
      G_SIGNAL_RUN_LAST,
      0,
      NULL,
      NULL,
      g_cclosure_marshal_generic,
      G_TYPE_NONE,
      0);

  request_signals[REQUEST_CACHE_INVALIDATED] = g_signal_new("cache-invalidated",
      ZATHURA_TYPE_RENDER_REQUEST,
      G_SIGNAL_RUN_LAST,
      0,
      NULL,
      NULL,
      g_cclosure_marshal_generic,
      G_TYPE_NONE,
      0);
229 230 231 232 233
}

static void
zathura_render_request_init(ZathuraRenderRequest* request)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
234
  ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
235 236 237 238 239 240 241 242 243 244 245 246
  priv->renderer = NULL;
  priv->page = NULL;
}

ZathuraRenderRequest*
zathura_render_request_new(ZathuraRenderer* renderer, zathura_page_t* page)
{
  g_return_val_if_fail(renderer != NULL && page != NULL, NULL);

  GObject* obj = g_object_new(ZATHURA_TYPE_RENDER_REQUEST, NULL);
  if (obj == NULL) {
    return NULL;
247
  }
248 249

  ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(obj);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
250
  ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
251 252 253
  /* we want to make sure that renderer lives long enough */
  priv->renderer = g_object_ref(renderer);
  priv->page = page;
254
  priv->active_jobs = girara_list_new();
255
  g_mutex_init(&priv->jobs_mutex);
256
  priv->render_plain = false;
257

258 259 260
  /* register the request with the renderer */
  renderer_register_request(renderer, request);

261
  return request;
Moritz Lipp's avatar
Moritz Lipp committed
262 263
}

264
static void
265
render_request_dispose(GObject* object)
Moritz Lipp's avatar
Moritz Lipp committed
266
{
267
  ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(object);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
268
  ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
Moritz Lipp's avatar
Moritz Lipp committed
269

270
  if (priv->renderer != NULL) {
271 272 273
    /* unregister the request */
    renderer_unregister_request(priv->renderer, request);
    /* release our private reference to the renderer */
274
    g_clear_object(&priv->renderer);
275
  }
276 277 278 279 280 281 282 283

  G_OBJECT_CLASS(zathura_render_request_parent_class)->dispose(object);
}

static void
render_request_finalize(GObject* object)
{
  ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(object);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
284
  ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
285

286 287 288 289
  if (girara_list_size(priv->active_jobs) != 0) {
    girara_error("This should not happen!");
  }
  girara_list_free(priv->active_jobs);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
290
  g_mutex_clear(&priv->jobs_mutex);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
291

292
  G_OBJECT_CLASS(zathura_render_request_parent_class)->finalize(object);
293 294 295
}

/* renderer methods */
296

297 298 299 300
bool
zathura_renderer_recolor_enabled(ZathuraRenderer* renderer)
{
  g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);
301

Sebastian Ramacher's avatar
Sebastian Ramacher committed
302 303
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
  return priv->recolor.enabled;
304
}
Moritz Lipp's avatar
Moritz Lipp committed
305

306 307 308 309
void
zathura_renderer_enable_recolor(ZathuraRenderer* renderer, bool enable)
{
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
Moritz Lipp's avatar
Moritz Lipp committed
310

Sebastian Ramacher's avatar
Sebastian Ramacher committed
311 312
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
  priv->recolor.enabled = enable;
313 314 315 316 317 318 319
}

bool
zathura_renderer_recolor_hue_enabled(ZathuraRenderer* renderer)
{
  g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
320 321
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
  return priv->recolor.hue;
Moritz Lipp's avatar
Moritz Lipp committed
322 323 324
}

void
325
zathura_renderer_enable_recolor_hue(ZathuraRenderer* renderer, bool enable)
Moritz Lipp's avatar
Moritz Lipp committed
326
{
327 328
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
329 330
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
  priv->recolor.hue = enable;
331 332
}

333 334 335 336 337
bool
zathura_renderer_recolor_reverse_video_enabled(ZathuraRenderer* renderer)
{
  g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
338 339
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
  return priv->recolor.reverse_video;
340 341 342 343 344 345 346
}

void
zathura_renderer_enable_recolor_reverse_video(ZathuraRenderer* renderer, bool enable)
{
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
347 348
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
  priv->recolor.reverse_video = enable;
349
}
350 351
void
zathura_renderer_set_recolor_colors(ZathuraRenderer* renderer,
352
    const GdkRGBA* light, const GdkRGBA* dark)
353 354 355
{
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
356
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
357
  if (light != NULL) {
358
    memcpy(&priv->recolor.light, light, sizeof(GdkRGBA));
359 360
  }
  if (dark != NULL) {
361
    memcpy(&priv->recolor.dark, dark, sizeof(GdkRGBA));
362 363 364 365 366 367 368 369 370 371
  }
}

void
zathura_renderer_set_recolor_colors_str(ZathuraRenderer* renderer,
    const char* light, const char* dark)
{
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));

  if (dark != NULL) {
372
    GdkRGBA color;
373 374 375
    if (parse_color(&color, dark) == true) {
      zathura_renderer_set_recolor_colors(renderer, NULL, &color);
    }
376 377
  }
  if (light != NULL) {
378
    GdkRGBA color;
379 380 381
    if (parse_color(&color, light) == true) {
      zathura_renderer_set_recolor_colors(renderer, &color, NULL);
    }
382 383 384 385 386
  }
}

void
zathura_renderer_get_recolor_colors(ZathuraRenderer* renderer,
387
    GdkRGBA* light, GdkRGBA* dark)
388 389 390
{
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
391
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
392
  if (light != NULL) {
393
    memcpy(light, &priv->recolor.light, sizeof(GdkRGBA));
394 395
  }
  if (dark != NULL) {
396
    memcpy(dark, &priv->recolor.dark, sizeof(GdkRGBA));
397
  }
398 399 400 401 402 403 404
}

void
zathura_renderer_lock(ZathuraRenderer* renderer)
{
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
405
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
406
  g_mutex_lock(&priv->mutex);
407
}
Moritz Lipp's avatar
Moritz Lipp committed
408

409 410 411 412 413
void
zathura_renderer_unlock(ZathuraRenderer* renderer)
{
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
414
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
415
  g_mutex_unlock(&priv->mutex);
416 417 418 419 420 421
}

void
zathura_renderer_stop(ZathuraRenderer* renderer)
{
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
422 423
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
  priv->about_to_close = true;
424 425 426 427 428 429
}


/* ZathuraRenderRequest methods */

void
430
zathura_render_request(ZathuraRenderRequest* request, gint64 last_view_time)
431 432 433
{
  g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
434
  ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
435
  g_mutex_lock(&request_priv->jobs_mutex);
436 437 438

  bool unfinished_jobs = false;
  /* check if there are any active jobs left */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
439
  GIRARA_LIST_FOREACH_BODY(request_priv->active_jobs, render_job_t*, job,
440
    if (job->aborted == false) {
441
      unfinished_jobs = true;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
442
      break;
443
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
444
  );
445 446 447

  /* only add a new job if there are no active ones left */
  if (unfinished_jobs == false) {
448
    request_priv->last_view_time = last_view_time;
449

450 451 452 453 454
    render_job_t* job = g_try_malloc0(sizeof(render_job_t));
    if (job == NULL) {
      return;
    }

455 456 457 458
    job->request = g_object_ref(request);
    job->aborted = false;
    girara_list_append(request_priv->active_jobs, job);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
459
    ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(request_priv->renderer);
460
    g_thread_pool_push(priv->pool, job, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
461
  }
462

463
  g_mutex_unlock(&request_priv->jobs_mutex);
464
}
465

466 467 468 469 470
void
zathura_render_request_abort(ZathuraRenderRequest* request)
{
  g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
471
  ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
472
  g_mutex_lock(&request_priv->jobs_mutex);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
473
  GIRARA_LIST_FOREACH_BODY(request_priv->active_jobs, render_job_t*, job,
474
    job->aborted = true;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
475
  );
476
  g_mutex_unlock(&request_priv->jobs_mutex);
Moritz Lipp's avatar
Moritz Lipp committed
477 478
}

479 480 481 482 483
void
zathura_render_request_update_view_time(ZathuraRenderRequest* request)
{
  g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
484
  ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
485 486
  request_priv->last_view_time = g_get_real_time();
}
487

Sebastian Ramacher's avatar
Sebastian Ramacher committed
488 489
/* render job */

490 491 492
static void
remove_job_and_free(render_job_t* job)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
493
  ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(job->request);
494

495
  g_mutex_lock(&request_priv->jobs_mutex);
496
  girara_list_remove(request_priv->active_jobs, job);
497
  g_mutex_unlock(&request_priv->jobs_mutex);
498 499 500 501 502

  g_object_unref(job->request);
  g_free(job);
}

503
typedef struct emit_completed_signal_s
Moritz Lipp's avatar
Moritz Lipp committed
504
{
505
  render_job_t* job;
506 507
  cairo_surface_t* surface;
} emit_completed_signal_t;
508

509 510 511 512
static gboolean
emit_completed_signal(void* data)
{
  emit_completed_signal_t* ecs = data;
513
  render_job_t* job = ecs->job;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
514 515
  ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(job->request);
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(request_priv->renderer);
Moritz Lipp's avatar
Moritz Lipp committed
516

517
  if (priv->about_to_close == false && job->aborted == false) {
518
    /* emit the signal */
519 520
    girara_debug("Emitting signal for page %d",
        zathura_page_get_index(request_priv->page) + 1);
521
    g_signal_emit(job->request, request_signals[REQUEST_COMPLETED], 0, ecs->surface);
522 523 524
  } else {
    girara_debug("Rendering of page %d aborted",
        zathura_page_get_index(request_priv->page) + 1);
525
  }
526
  /* mark the request as done */
527
  remove_job_and_free(job);
528 529 530 531 532 533

  /* clean up the data */
  cairo_surface_destroy(ecs->surface);
  g_free(ecs);

  return FALSE;
Moritz Lipp's avatar
Moritz Lipp committed
534 535
}

536 537 538
/* Returns the maximum possible saturation for given h and l.
   Assumes that l is in the interval l1, l2 and corrects the value to
   force u=0 on l1 and l2 */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
539
static double
540
colorumax(const double h[3], double l, double l1, double l2)
541
{
542
  if (fabs(h[0]) <= DBL_EPSILON && fabs(h[1]) <= DBL_EPSILON && fabs(h[2]) <= DBL_EPSILON) {
543 544 545
    return 0;
  }

546
  const double lv = (l - l1) / (l2 - l1);    /* Remap l to the whole interval [0,1] */
547 548
  double u = DBL_MAX;
  double v = DBL_MAX;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
549
  for (unsigned int k = 0; k < 3; ++k) {
550
    if (h[k] > DBL_EPSILON) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
551 552
      u = fmin(fabs((1-l)/h[k]), u);
      v = fmin(fabs((1-lv)/h[k]), v);
553
    } else if (h[k] < -DBL_EPSILON) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
554 555
      u = fmin(fabs(l/h[k]), u);
      v = fmin(fabs(lv/h[k]), v);
556 557 558 559
    }
  }

  /* rescale v according to the length of the interval [l1, l2] */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
560
  v = fabs(l2 - l1) * v;
561 562 563 564 565

  /* forces the returned value to be 0 on l1 and l2, trying not to distort colors too much */
  return fmin(u, v);
}

566
static void
Sebastian Ramacher's avatar
Sebastian Ramacher committed
567
recolor(ZathuraRendererPrivate* priv, zathura_page_t* page, unsigned int page_width, 
568
        unsigned int page_height, cairo_surface_t* surface)
569 570 571
{
  /* uses a representation of a rgb color as follows:
     - a lightness scalar (between 0,1), which is a weighted average of r, g, b,
572 573 574 575
     - a hue vector, which indicates a radian direction from the grey axis,
       inside the equal lightness plane.
     - a saturation scalar between 0,1. It is 0 when grey, 1 when the color is
       in the boundary of the rgb cube.
576 577
  */

578 579
  /* TODO: split handling of image handling off
   * Ideally we would create a mask surface for the location of the images and
Sebastian Ramacher's avatar
Sebastian Ramacher committed
580
   * we would blit the recolored and unmodified surfaces together to get the
581 582 583
   * same effect.
   */

584 585
  cairo_surface_flush(surface);

586 587 588 589 590 591
  const int rowstride  = cairo_image_surface_get_stride(surface);
  unsigned char* image = cairo_image_surface_get_data(surface);

  /* RGB weights for computing lightness. Must sum to one */
  static const double a[] = {0.30, 0.59, 0.11};

592 593 594
  const GdkRGBA rgb1 = priv->recolor.dark;
  const GdkRGBA rgb2 = priv->recolor.light;

595 596
  const double l1 = a[0]*rgb1.red + a[1]*rgb1.green + a[2]*rgb1.blue;
  const double l2 = a[0]*rgb2.red + a[1]*rgb2.green + a[2]*rgb2.blue;
597 598

  const double rgb_diff[] = {
599 600 601
    rgb2.red - rgb1.red,
    rgb2.green - rgb1.green,
    rgb2.blue - rgb1.blue
602 603
  };

604 605 606 607 608 609 610 611 612 613 614 615
  girara_list_t* images     = NULL;
  girara_list_t* rectangles = NULL;
  bool found_images         = false;

  /* If in reverse video mode retrieve images */
  if (priv->recolor.reverse_video == true) {
    images = zathura_page_images_get(page, NULL);
    found_images = (images != NULL);

    rectangles = girara_list_new();
    if (rectangles == NULL) {
      found_images = false;
616
      girara_warning("Failed to retrieve images.");
617 618 619 620
    }

    if (found_images == true) {
      /* Get images bounding boxes */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
621
      GIRARA_LIST_FOREACH_BODY(images, zathura_image_t*, image_it,
622 623 624 625 626 627
        zathura_rectangle_t* rect = g_try_malloc(sizeof(zathura_rectangle_t));
        if (rect == NULL) {
          break;
        }
        *rect = recalc_rectangle(page, image_it->position);
        girara_list_append(rectangles, rect);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
628
      );
629 630 631
    }
  }

632 633 634 635
  for (unsigned int y = 0; y < page_height; y++) {
    unsigned char* data = image + y * rowstride;

    for (unsigned int x = 0; x < page_width; x++, data += 4) {
636 637 638
      /* Check if the pixel belongs to an image when in reverse video mode*/
      if (priv->recolor.reverse_video == true && found_images == true){
        bool inside_image = false;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
639
        GIRARA_LIST_FOREACH_BODY(rectangles, zathura_rectangle_t*, rect_it,
640 641 642 643 644
          if (rect_it->x1 <= x && rect_it->x2 >= x &&
              rect_it->y1 <= y && rect_it->y2 >= y) {
            inside_image = true;
            break;
          }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
645
        );
646 647 648 649 650 651
        /* If it's inside and image don't recolor */
        if (inside_image == true) {
          continue;
        }
      }

652 653
      /* Careful. data color components blue, green, red. */
      const double rgb[3] = {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
654 655 656
        data[2] / 255.,
        data[1] / 255.,
        data[0] / 255.
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
      };

      /* compute h, s, l data   */
      double l = a[0]*rgb[0] + a[1]*rgb[1] + a[2]*rgb[2];

      if (priv->recolor.hue == true) {
        /* adjusting lightness keeping hue of current color. white and black
         * go to grays of same ligtness as light and dark colors. */
        const double h[3] = {
          rgb[0] - l,
          rgb[1] - l,
          rgb[2] - l
        };

        /* u is the maximum possible saturation for given h and l. s is a
         * rescaled saturation between 0 and 1 */
673 674
        const double u = colorumax(h, l, 0, 1);
        const double s = fabs(u) > DBL_EPSILON ? 1.0 / u : 0.0;
675 676 677 678 679

        /* Interpolates lightness between light and dark colors. white goes to
         * light, and black goes to dark. */
        l = l * (l2 - l1) + l1;

680 681 682 683
        const double su = s * colorumax(h, l, l1, l2);
        data[2] = (unsigned char)round(255.*(l + su * h[0]));
        data[1] = (unsigned char)round(255.*(l + su * h[1]));
        data[0] = (unsigned char)round(255.*(l + su * h[2]));
684 685 686
      } else {
        /* linear interpolation between dark and light with color ligtness as
         * a parameter */
687 688 689
        data[2] = (unsigned char)round(255.*(l * rgb_diff[0] + rgb1.red));
        data[1] = (unsigned char)round(255.*(l * rgb_diff[1] + rgb1.green));
        data[0] = (unsigned char)round(255.*(l * rgb_diff[2] + rgb1.blue));
690 691 692 693
      }
    }
  }

694 695 696 697 698 699 700
  if (images != NULL) {
    girara_list_free(images);
  }
  if (rectangles != NULL) {
    girara_list_free(rectangles);
  }

701
  cairo_surface_mark_dirty(surface);
702 703
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747
static bool
invoke_completed_signal(render_job_t* job, cairo_surface_t* surface)
{
  emit_completed_signal_t* ecs = g_try_malloc0(sizeof(emit_completed_signal_t));
  if (ecs == NULL) {
    return false;
  }

  ecs->job     = job;
  ecs->surface = cairo_surface_reference(surface);

  /* emit signal from the main context, i.e. the main thread */
  g_main_context_invoke(NULL, emit_completed_signal, ecs);
  return true;
}

static bool
render_to_cairo_surface(cairo_surface_t* surface, zathura_page_t* page, ZathuraRenderer* renderer, double real_scale)
{
  cairo_t* cairo = cairo_create(surface);
  if (cairo == NULL) {
    return false;
  }

  cairo_save(cairo);
  cairo_set_source_rgb(cairo, 1, 1, 1);
  cairo_paint(cairo);
  cairo_restore(cairo);
  cairo_save(cairo);

  /* apply scale (used by e.g. Poppler as pixels per point) */
  if (fabs(real_scale - 1.0f) > FLT_EPSILON) {
    cairo_scale(cairo, real_scale, real_scale);
  }

  zathura_renderer_lock(renderer);
  const int err = zathura_page_render(page, cairo, false);
  zathura_renderer_unlock(renderer);
  cairo_restore(cairo);
  cairo_destroy(cairo);

  return err == ZATHURA_ERROR_OK;
}

748
static bool
749
render(render_job_t* job, ZathuraRenderRequest* request, ZathuraRenderer* renderer)
Moritz Lipp's avatar
Moritz Lipp committed
750
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
751 752
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
  ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
753
  zathura_page_t* page = request_priv->page;
Moritz Lipp's avatar
Moritz Lipp committed
754

Moritz Lipp's avatar
Moritz Lipp committed
755 756 757
  /* create cairo surface */
  unsigned int page_width  = 0;
  unsigned int page_height = 0;
758

759
  /* page size in points */
760
  zathura_document_t* document = zathura_page_get_document(page);
761 762
  const double height = zathura_page_get_height(page);
  const double width = zathura_page_get_width(page);
763

764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
  zathura_device_factors_t device_factors = { 0 };
  double real_scale = 1;
  if (request_priv->render_plain == false) {
    /* page size in user pixels based on document zoom: if PPI information is
     * correct, 100% zoom will result in 72 documents points per inch of screen
     * (i.e. document size on screen matching the physical paper size). */
    real_scale = page_calc_height_width(document, height, width,
                                        &page_height, &page_width, false);

    device_factors = zathura_document_get_device_factors(document);
    page_width *= device_factors.x;
    page_height *= device_factors.y;
  } else {
    page_width = width;
    page_height = height;
  }
Moritz Lipp's avatar
Moritz Lipp committed
780

781 782
  cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
      page_width, page_height);
Moritz Lipp's avatar
Moritz Lipp committed
783
  if (surface == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
784 785
    return false;
  }
786

787 788 789
  if (request_priv->render_plain == false) {
    cairo_surface_set_device_scale(surface, device_factors.x, device_factors.y);
  }
790

791 792 793 794
  if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
    cairo_surface_destroy(surface);
    return false;
  }
Moritz Lipp's avatar
Moritz Lipp committed
795

Sebastian Ramacher's avatar
Sebastian Ramacher committed
796 797
  /* actually render to the surface */
  if (!render_to_cairo_surface(surface, page, renderer, real_scale)) {
798 799 800
    cairo_surface_destroy(surface);
    return false;
  }
Moritz Lipp's avatar
Moritz Lipp committed
801

802
  /* before recoloring, check if we've been aborted */
803
  if (priv->about_to_close == true || job->aborted == true) {
804
    girara_debug("Rendering of page %d aborted",
805
                 zathura_page_get_index(request_priv->page) + 1);
806
    remove_job_and_free(job);
807 808 809 810
    cairo_surface_destroy(surface);
    return true;
  }

Moritz Lipp's avatar
Moritz Lipp committed
811
  /* recolor */
812
  if (request_priv->render_plain == false && priv->recolor.enabled == true) {
813
    recolor(priv, page, page_width, page_height, surface);
Moritz Lipp's avatar
Moritz Lipp committed
814 815
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
816
  if (!invoke_completed_signal(job, surface)) {
817
    cairo_surface_destroy(surface);
818 819 820
    return false;
  }

821 822
  cairo_surface_destroy(surface);

Moritz Lipp's avatar
Moritz Lipp committed
823 824
  return true;
}
825

826 827 828
static void
render_job(void* data, void* user_data)
{
829 830
  render_job_t* job = data;
  ZathuraRenderRequest* request = job->request;
831 832 833 834
  ZathuraRenderer* renderer = user_data;
  g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
835
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
836
  if (priv->about_to_close == true || job->aborted == true) {
837
    /* back out early */
838
    remove_job_and_free(job);
839 840 841
    return;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
842
  ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
843 844
  girara_debug("Rendering page %d ...",
      zathura_page_get_index(request_priv->page) + 1);
845
  if (render(job, request, renderer) != true) {
846 847
    girara_error("Rendering failed (page %d)\n",
        zathura_page_get_index(request_priv->page) + 1);
848
    remove_job_and_free(job);
849 850 851 852
  }
}


853
void
Moritz Lipp's avatar
Moritz Lipp committed
854
render_all(zathura_t* zathura)
855
{
856
  if (zathura == NULL || zathura->document == NULL) {
857 858 859 860
    return;
  }

  /* unmark all pages */
861
  const unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
862
  for (unsigned int page_id = 0; page_id < number_of_pages; ++page_id) {
863 864
    zathura_page_t* page = zathura_document_get_page(zathura->document,
        page_id);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
865
    unsigned int page_height = 0, page_width = 0;
866 867
    const double height = zathura_page_get_height(page);
    const double width = zathura_page_get_width(page);
868
    page_calc_height_width(zathura->document, height, width, &page_height, &page_width, true);
869

Sebastian Ramacher's avatar
Sebastian Ramacher committed
870
    girara_debug("Queuing resize for page %u to %u x %u (%0.2f x %0.2f).", page_id, page_width, page_height, width, height);
871
    GtkWidget* widget = zathura_page_get_widget(zathura, page);
872 873 874 875
    if (widget != NULL) {
      gtk_widget_set_size_request(widget, page_width, page_height);
      gtk_widget_queue_resize(widget);
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
876
  }
Moritz Lipp's avatar
Moritz Lipp committed
877
}
878 879

static gint
880
render_thread_sort(gconstpointer a, gconstpointer b, gpointer UNUSED(data))
881
{
882
  if (a == NULL || b == NULL) {
883 884 885
    return 0;
  }

886 887 888
  const render_job_t* job_a = a;
  const render_job_t* job_b = b;
  if (job_a->aborted == job_b->aborted) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
889 890
    ZathuraRenderRequestPrivate* priv_a = zathura_render_request_get_instance_private(job_a->request);
    ZathuraRenderRequestPrivate* priv_b = zathura_render_request_get_instance_private(job_b->request);
891

892 893
    return priv_a->last_view_time < priv_b->last_view_time ? -1 :
        (priv_a->last_view_time > priv_b->last_view_time ? 1 : 0);
894 895
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
896
  /* sort aborted entries earlier so that they are thrown out of the queue */
897
  return job_a->aborted ? 1 : -1;
898
}
899 900 901

/* cache functions */

902 903
static bool
page_cache_is_cached(ZathuraRenderer* renderer, unsigned int page_index)
904 905
{
  g_return_val_if_fail(renderer != NULL, false);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
906
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924

  if (priv->page_cache.num_cached_pages != 0) {
    for (size_t i = 0; i < priv->page_cache.size; ++i) {
      if (priv->page_cache.cache[i] >= 0 &&
          page_index == (unsigned int)priv->page_cache.cache[i]) {
        girara_debug("Page %d is a cache hit", page_index + 1);
        return true;
      }
    }
  }

  girara_debug("Page %d is a cache miss", page_index + 1);
  return false;
}

static int
find_request_by_page_index(const void* req, const void* data)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
925
  ZathuraRenderRequest* request = (void*) req;
926 927
  const unsigned int page_index = *((const int*)data);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
928
  ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
929 930 931 932 933 934 935 936 937 938
  if (zathura_page_get_index(priv->page) == page_index) {
    return 0;
  }
  return 1;
}

static ssize_t
page_cache_lru_invalidate(ZathuraRenderer* renderer)
{
  g_return_val_if_fail(renderer != NULL, -1);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
939
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
940 941 942 943
  g_return_val_if_fail(priv->page_cache.size != 0, -1);

  ssize_t lru_index = 0;
  gint64 lru_view_time = G_MAXINT64;
944
  ZathuraRenderRequest* request = NULL;
945
  for (size_t i = 0; i < priv->page_cache.size; ++i) {
946
    ZathuraRenderRequest* tmp_request = girara_list_find(priv->requests,
947
        find_request_by_page_index, &priv->page_cache.cache[i]);
948
    g_return_val_if_fail(tmp_request != NULL, -1);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
949
    ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(tmp_request);
950 951 952 953

    if (request_priv->last_view_time < lru_view_time) {
      lru_view_time = request_priv->last_view_time;
      lru_index = i;
954
      request = tmp_request;
955 956 957
    }
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
958
  ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
959 960

  /* emit the signal */
961
  g_signal_emit(request, request_signals[REQUEST_CACHE_INVALIDATED], 0);
962 963 964 965 966 967 968 969 970 971 972
  girara_debug("Invalidated page %d at cache index %zd",
      zathura_page_get_index(request_priv->page) + 1, lru_index);
  priv->page_cache.cache[lru_index] = -1;
  --priv->page_cache.num_cached_pages;

  return lru_index;
}

static bool
page_cache_is_full(ZathuraRenderer* renderer, bool* result)
{
973
  g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer) && result != NULL, false);
974

Sebastian Ramacher's avatar
Sebastian Ramacher committed
975
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
976 977 978 979 980
  *result = priv->page_cache.num_cached_pages == priv->page_cache.size;

  return true;
}

981
static void
982 983
page_cache_invalidate_all(ZathuraRenderer* renderer)
{
984
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
985

Sebastian Ramacher's avatar
Sebastian Ramacher committed
986
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
987 988 989 990 991 992 993
  for (size_t i = 0; i < priv->page_cache.size; ++i) {
    priv->page_cache.cache[i] = -1;
  }
  priv->page_cache.num_cached_pages = 0;
}

void
994 995
zathura_renderer_page_cache_add(ZathuraRenderer* renderer,
    unsigned int page_index)
996
{
997 998
  g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
  if (page_cache_is_cached(renderer, page_index) == true) {
999 1000 1001
    return;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
1002
  ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
1003 1004 1005 1006
  bool full = false;
  if (page_cache_is_full(renderer, &full) == false) {
    return;
  } else if (full == true) {
1007
    const ssize_t idx = page_cache_lru_invalidate(renderer);
1008 1009 1010 1011 1012 1013 1014
    if (idx == -1) {
      return;
    }

    priv->page_cache.cache[idx] = page_index;
    ++priv->page_cache.num_cached_pages;
    girara_debug("Page %d is cached at cache index %zd", page_index + 1, idx);
1015 1016 1017 1018
  } else {
    priv->page_cache.cache[priv->page_cache.num_cached_pages++] = page_index;
    girara_debug("Page %d is cached at cache index %zu", page_index + 1,
        priv->page_cache.num_cached_pages - 1);
1019 1020
  }

1021 1022 1023 1024
  ZathuraRenderRequest* request = girara_list_find(priv->requests,
        find_request_by_page_index, &page_index);
  g_return_if_fail(request != NULL);
  g_signal_emit(request, request_signals[REQUEST_CACHE_ADDED], 0);
1025
}
1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044

void zathura_render_request_set_render_plain(ZathuraRenderRequest* request,
    bool render_plain)
{
  g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));

  ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
  priv->render_plain =render_plain;
}

bool
zathura_render_request_get_render_plain(ZathuraRenderRequest* request)
{
  g_return_val_if_fail(ZATHURA_IS_RENDER_REQUEST(request), false);

  ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
  return priv->render_plain;
}