1 /* SPDX-License-Identifier: Zlib */
2
3 #include <math.h>
4 #include <string.h>
5 #include <girara/datastructures.h>
6 #include <girara/utils.h>
7
8 #include "render.h"
9 #include "adjustment.h"
10 #include "zathura.h"
11 #include "document.h"
12 #include "page.h"
13 #include "page-widget.h"
14 #include "utils.h"
15
16 /* private data for ZathuraRenderer */
17 typedef struct private_s {
18 GThreadPool* pool; /**< Pool of threads */
19 GMutex mutex; /**< Render lock */
20 volatile bool about_to_close; /**< Render thread is to be freed */
21
22 /**
23 * recolor information
24 */
25 struct {
26 bool enabled;
27 bool hue;
28 bool reverse_video;
29
30 GdkRGBA light;
31 GdkRGBA dark;
32 } recolor;
33
34 /*
35 * page cache
36 */
37 struct {
38 int* cache;
39 size_t size;
40 size_t num_cached_pages;
41 } page_cache;
42
43 /* render requests */
44 girara_list_t* requests;
45 } ZathuraRendererPrivate;
46
47 /* private data for ZathuraRenderRequest */
48 typedef struct request_private_s {
49 ZathuraRenderer* renderer;
50 zathura_page_t* page;
51 gint64 last_view_time;
52 girara_list_t* active_jobs;
53 GMutex jobs_mutex;
54 bool render_plain;
55 } ZathuraRenderRequestPrivate;
56
57 /* define the two types */
58 G_DEFINE_TYPE_WITH_CODE(ZathuraRenderer, zathura_renderer, G_TYPE_OBJECT, G_ADD_PRIVATE(ZathuraRenderer))
59 G_DEFINE_TYPE_WITH_CODE(ZathuraRenderRequest, zathura_render_request, G_TYPE_OBJECT, G_ADD_PRIVATE(ZathuraRenderRequest))
60
61 /* private methods for ZathuraRenderer */
62 static void renderer_finalize(GObject* object);
63 /* private methods for ZathuraRenderRequest */
64 static void render_request_dispose(GObject* object);
65 static void render_request_finalize(GObject* object);
66
67 static void render_job(void* data, void* user_data);
68 static gint render_thread_sort(gconstpointer a, gconstpointer b, gpointer data);
69 static ssize_t page_cache_lru_invalidate(ZathuraRenderer* renderer);
70 static void page_cache_invalidate_all(ZathuraRenderer* renderer);
71 static bool page_cache_is_full(ZathuraRenderer* renderer, bool* result);
72
73 /* job descritption for render thread */
74 typedef struct render_job_s {
75 ZathuraRenderRequest* request;
76 volatile bool aborted;
77 } render_job_t;
78
79 /* init, new and free for ZathuraRenderer */
80
81 static void
zathura_renderer_class_init(ZathuraRendererClass * class)82 zathura_renderer_class_init(ZathuraRendererClass* class)
83 {
84 /* overwrite methods */
85 GObjectClass* object_class = G_OBJECT_CLASS(class);
86 object_class->finalize = renderer_finalize;
87 }
88
89 static void
zathura_renderer_init(ZathuraRenderer * renderer)90 zathura_renderer_init(ZathuraRenderer* renderer)
91 {
92 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
93 priv->pool = g_thread_pool_new(render_job, renderer, 1, TRUE, NULL);
94 priv->about_to_close = false;
95 g_thread_pool_set_sort_function(priv->pool, render_thread_sort, NULL);
96 g_mutex_init(&priv->mutex);
97
98 /* recolor */
99 priv->recolor.enabled = false;
100 priv->recolor.hue = true;
101 priv->recolor.reverse_video = false;
102
103 /* page cache */
104 priv->page_cache.size = 0;
105 priv->page_cache.cache = NULL;
106 priv->page_cache.num_cached_pages = 0;
107
108 zathura_renderer_set_recolor_colors_str(renderer, "#000000", "#FFFFFF");
109
110 priv->requests = girara_list_new();
111 }
112
113 static bool
page_cache_init(ZathuraRenderer * renderer,size_t cache_size)114 page_cache_init(ZathuraRenderer* renderer, size_t cache_size)
115 {
116 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
117
118 priv->page_cache.size = cache_size;
119 priv->page_cache.cache = g_try_malloc0(cache_size * sizeof(int));
120 if (priv->page_cache.cache == NULL) {
121 return false;
122 }
123
124 page_cache_invalidate_all(renderer);
125 return true;
126 }
127
128 ZathuraRenderer*
zathura_renderer_new(size_t cache_size)129 zathura_renderer_new(size_t cache_size)
130 {
131 g_return_val_if_fail(cache_size > 0, NULL);
132
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 return NULL;
139 }
140
141 return ret;
142 }
143
144 static void
renderer_finalize(GObject * object)145 renderer_finalize(GObject* object)
146 {
147 ZathuraRenderer* renderer = ZATHURA_RENDERER(object);
148 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
149
150 zathura_renderer_stop(renderer);
151 if (priv->pool != NULL) {
152 g_thread_pool_free(priv->pool, TRUE, TRUE);
153 }
154 g_mutex_clear(&(priv->mutex));
155
156 g_free(priv->page_cache.cache);
157 girara_list_free(priv->requests);
158 }
159
160 /* (un)register requests at the renderer */
161
162 static void
renderer_unregister_request(ZathuraRenderer * renderer,ZathuraRenderRequest * request)163 renderer_unregister_request(ZathuraRenderer* renderer,
164 ZathuraRenderRequest* request)
165 {
166 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
167 girara_list_remove(priv->requests, request);
168 }
169
170 static void
renderer_register_request(ZathuraRenderer * renderer,ZathuraRenderRequest * request)171 renderer_register_request(ZathuraRenderer* renderer,
172 ZathuraRenderRequest* request)
173 {
174 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
175 if (girara_list_contains(priv->requests, request) == false) {
176 girara_list_append(priv->requests, request);
177 }
178 }
179
180 /* init, new and free for ZathuraRenderRequest */
181
182 enum {
183 REQUEST_COMPLETED,
184 REQUEST_CACHE_ADDED,
185 REQUEST_CACHE_INVALIDATED,
186 REQUEST_LAST_SIGNAL
187 };
188
189 static guint request_signals[REQUEST_LAST_SIGNAL] = { 0 };
190
191 static void
zathura_render_request_class_init(ZathuraRenderRequestClass * class)192 zathura_render_request_class_init(ZathuraRenderRequestClass* class)
193 {
194 /* overwrite methods */
195 GObjectClass* object_class = G_OBJECT_CLASS(class);
196 object_class->dispose = render_request_dispose;
197 object_class->finalize = render_request_finalize;
198
199 request_signals[REQUEST_COMPLETED] = g_signal_new("completed",
200 ZATHURA_TYPE_RENDER_REQUEST,
201 G_SIGNAL_RUN_LAST,
202 0,
203 NULL,
204 NULL,
205 g_cclosure_marshal_generic,
206 G_TYPE_NONE,
207 1,
208 G_TYPE_POINTER);
209
210 request_signals[REQUEST_CACHE_ADDED] = g_signal_new("cache-added",
211 ZATHURA_TYPE_RENDER_REQUEST,
212 G_SIGNAL_RUN_LAST,
213 0,
214 NULL,
215 NULL,
216 g_cclosure_marshal_generic,
217 G_TYPE_NONE,
218 0);
219
220 request_signals[REQUEST_CACHE_INVALIDATED] = g_signal_new("cache-invalidated",
221 ZATHURA_TYPE_RENDER_REQUEST,
222 G_SIGNAL_RUN_LAST,
223 0,
224 NULL,
225 NULL,
226 g_cclosure_marshal_generic,
227 G_TYPE_NONE,
228 0);
229 }
230
231 static void
zathura_render_request_init(ZathuraRenderRequest * request)232 zathura_render_request_init(ZathuraRenderRequest* request)
233 {
234 ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
235 priv->renderer = NULL;
236 priv->page = NULL;
237 }
238
239 ZathuraRenderRequest*
zathura_render_request_new(ZathuraRenderer * renderer,zathura_page_t * page)240 zathura_render_request_new(ZathuraRenderer* renderer, zathura_page_t* page)
241 {
242 g_return_val_if_fail(renderer != NULL && page != NULL, NULL);
243
244 GObject* obj = g_object_new(ZATHURA_TYPE_RENDER_REQUEST, NULL);
245 if (obj == NULL) {
246 return NULL;
247 }
248
249 ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(obj);
250 ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
251 /* we want to make sure that renderer lives long enough */
252 priv->renderer = g_object_ref(renderer);
253 priv->page = page;
254 priv->active_jobs = girara_list_new();
255 g_mutex_init(&priv->jobs_mutex);
256 priv->render_plain = false;
257
258 /* register the request with the renderer */
259 renderer_register_request(renderer, request);
260
261 return request;
262 }
263
264 static void
render_request_dispose(GObject * object)265 render_request_dispose(GObject* object)
266 {
267 ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(object);
268 ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
269
270 if (priv->renderer != NULL) {
271 /* unregister the request */
272 renderer_unregister_request(priv->renderer, request);
273 /* release our private reference to the renderer */
274 g_clear_object(&priv->renderer);
275 }
276
277 G_OBJECT_CLASS(zathura_render_request_parent_class)->dispose(object);
278 }
279
280 static void
render_request_finalize(GObject * object)281 render_request_finalize(GObject* object)
282 {
283 ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(object);
284 ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
285
286 if (girara_list_size(priv->active_jobs) != 0) {
287 girara_error("This should not happen!");
288 }
289 girara_list_free(priv->active_jobs);
290 g_mutex_clear(&priv->jobs_mutex);
291
292 G_OBJECT_CLASS(zathura_render_request_parent_class)->finalize(object);
293 }
294
295 /* renderer methods */
296
297 bool
zathura_renderer_recolor_enabled(ZathuraRenderer * renderer)298 zathura_renderer_recolor_enabled(ZathuraRenderer* renderer)
299 {
300 g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);
301
302 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
303 return priv->recolor.enabled;
304 }
305
306 void
zathura_renderer_enable_recolor(ZathuraRenderer * renderer,bool enable)307 zathura_renderer_enable_recolor(ZathuraRenderer* renderer, bool enable)
308 {
309 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
310
311 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
312 priv->recolor.enabled = enable;
313 }
314
315 bool
zathura_renderer_recolor_hue_enabled(ZathuraRenderer * renderer)316 zathura_renderer_recolor_hue_enabled(ZathuraRenderer* renderer)
317 {
318 g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);
319
320 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
321 return priv->recolor.hue;
322 }
323
324 void
zathura_renderer_enable_recolor_hue(ZathuraRenderer * renderer,bool enable)325 zathura_renderer_enable_recolor_hue(ZathuraRenderer* renderer, bool enable)
326 {
327 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
328
329 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
330 priv->recolor.hue = enable;
331 }
332
333 bool
zathura_renderer_recolor_reverse_video_enabled(ZathuraRenderer * renderer)334 zathura_renderer_recolor_reverse_video_enabled(ZathuraRenderer* renderer)
335 {
336 g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);
337
338 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
339 return priv->recolor.reverse_video;
340 }
341
342 void
zathura_renderer_enable_recolor_reverse_video(ZathuraRenderer * renderer,bool enable)343 zathura_renderer_enable_recolor_reverse_video(ZathuraRenderer* renderer, bool enable)
344 {
345 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
346
347 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
348 priv->recolor.reverse_video = enable;
349 }
350 void
zathura_renderer_set_recolor_colors(ZathuraRenderer * renderer,const GdkRGBA * light,const GdkRGBA * dark)351 zathura_renderer_set_recolor_colors(ZathuraRenderer* renderer,
352 const GdkRGBA* light, const GdkRGBA* dark)
353 {
354 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
355
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 void
zathura_renderer_set_recolor_colors_str(ZathuraRenderer * renderer,const char * light,const char * dark)366 zathura_renderer_set_recolor_colors_str(ZathuraRenderer* renderer,
367 const char* light, const char* dark)
368 {
369 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
370
371 if (dark != NULL) {
372 GdkRGBA color;
373 if (parse_color(&color, dark) == true) {
374 zathura_renderer_set_recolor_colors(renderer, NULL, &color);
375 }
376 }
377 if (light != NULL) {
378 GdkRGBA color;
379 if (parse_color(&color, light) == true) {
380 zathura_renderer_set_recolor_colors(renderer, &color, NULL);
381 }
382 }
383 }
384
385 void
zathura_renderer_get_recolor_colors(ZathuraRenderer * renderer,GdkRGBA * light,GdkRGBA * dark)386 zathura_renderer_get_recolor_colors(ZathuraRenderer* renderer,
387 GdkRGBA* light, GdkRGBA* dark)
388 {
389 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
390
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 void
zathura_renderer_lock(ZathuraRenderer * renderer)401 zathura_renderer_lock(ZathuraRenderer* renderer)
402 {
403 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
404
405 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
406 g_mutex_lock(&priv->mutex);
407 }
408
409 void
zathura_renderer_unlock(ZathuraRenderer * renderer)410 zathura_renderer_unlock(ZathuraRenderer* renderer)
411 {
412 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
413
414 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
415 g_mutex_unlock(&priv->mutex);
416 }
417
418 void
zathura_renderer_stop(ZathuraRenderer * renderer)419 zathura_renderer_stop(ZathuraRenderer* renderer)
420 {
421 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
422 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
423 priv->about_to_close = true;
424 }
425
426
427 /* ZathuraRenderRequest methods */
428
429 void
zathura_render_request(ZathuraRenderRequest * request,gint64 last_view_time)430 zathura_render_request(ZathuraRenderRequest* request, gint64 last_view_time)
431 {
432 g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
433
434 ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
435 g_mutex_lock(&request_priv->jobs_mutex);
436
437 bool unfinished_jobs = false;
438 /* check if there are any active jobs left */
439 GIRARA_LIST_FOREACH_BODY(request_priv->active_jobs, render_job_t*, job,
440 if (job->aborted == false) {
441 unfinished_jobs = true;
442 break;
443 }
444 );
445
446 /* only add a new job if there are no active ones left */
447 if (unfinished_jobs == false) {
448 request_priv->last_view_time = last_view_time;
449
450 render_job_t* job = g_try_malloc0(sizeof(render_job_t));
451 if (job == NULL) {
452 return;
453 }
454
455 job->request = g_object_ref(request);
456 job->aborted = false;
457 girara_list_append(request_priv->active_jobs, job);
458
459 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(request_priv->renderer);
460 g_thread_pool_push(priv->pool, job, NULL);
461 }
462
463 g_mutex_unlock(&request_priv->jobs_mutex);
464 }
465
466 void
zathura_render_request_abort(ZathuraRenderRequest * request)467 zathura_render_request_abort(ZathuraRenderRequest* request)
468 {
469 g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
470
471 ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
472 g_mutex_lock(&request_priv->jobs_mutex);
473 GIRARA_LIST_FOREACH_BODY(request_priv->active_jobs, render_job_t*, job,
474 job->aborted = true;
475 );
476 g_mutex_unlock(&request_priv->jobs_mutex);
477 }
478
479 void
zathura_render_request_update_view_time(ZathuraRenderRequest * request)480 zathura_render_request_update_view_time(ZathuraRenderRequest* request)
481 {
482 g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
483
484 ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
485 request_priv->last_view_time = g_get_real_time();
486 }
487
488 /* render job */
489
490 static void
remove_job_and_free(render_job_t * job)491 remove_job_and_free(render_job_t* job)
492 {
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 g_object_unref(job->request);
500 g_free(job);
501 }
502
503 typedef struct emit_completed_signal_s
504 {
505 render_job_t* job;
506 cairo_surface_t* surface;
507 } emit_completed_signal_t;
508
509 static gboolean
emit_completed_signal(void * data)510 emit_completed_signal(void* data)
511 {
512 emit_completed_signal_t* ecs = data;
513 render_job_t* job = ecs->job;
514 ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(job->request);
515 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(request_priv->renderer);
516
517 if (priv->about_to_close == false && job->aborted == false) {
518 /* emit the signal */
519 girara_debug("Emitting signal for page %d",
520 zathura_page_get_index(request_priv->page) + 1);
521 g_signal_emit(job->request, request_signals[REQUEST_COMPLETED], 0, ecs->surface);
522 } else {
523 girara_debug("Rendering of page %d aborted",
524 zathura_page_get_index(request_priv->page) + 1);
525 }
526 /* mark the request as done */
527 remove_job_and_free(job);
528
529 /* clean up the data */
530 cairo_surface_destroy(ecs->surface);
531 g_free(ecs);
532
533 return FALSE;
534 }
535
536 /* Returns the maximum possible saturation for given h and l.
537 Assumes that l is in the interval l1, l2 and corrects the value to
538 force u=0 on l1 and l2 */
539 static double
colorumax(const double h[3],double l,double l1,double l2)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 return 0;
544 }
545
546 const double lv = (l - l1) / (l2 - l1); /* Remap l to the whole interval [0,1] */
547 double u = DBL_MAX;
548 double v = DBL_MAX;
549 for (unsigned int k = 0; k < 3; ++k) {
550 if (h[k] > DBL_EPSILON) {
551 u = fmin(fabs((1-l)/h[k]), u);
552 v = fmin(fabs((1-lv)/h[k]), v);
553 } else if (h[k] < -DBL_EPSILON) {
554 u = fmin(fabs(l/h[k]), u);
555 v = fmin(fabs(lv/h[k]), v);
556 }
557 }
558
559 /* rescale v according to the length of the interval [l1, l2] */
560 v = fabs(l2 - l1) * v;
561
562 /* forces the returned value to be 0 on l1 and l2, trying not to distort colors too much */
563 return fmin(u, v);
564 }
565
566 static void
recolor(ZathuraRendererPrivate * priv,zathura_page_t * page,unsigned int page_width,unsigned int page_height,cairo_surface_t * surface)567 recolor(ZathuraRendererPrivate* priv, zathura_page_t* page, unsigned int page_width,
568 unsigned int page_height, cairo_surface_t* surface)
569 {
570 /* uses a representation of a rgb color as follows:
571 - a lightness scalar (between 0,1), which is a weighted average of r, g, b,
572 - a hue vector, which indicates a radian direction from the grey axis,
573 inside the equal lightness plane.
574 - a saturation scalar between 0,1. It is 0 when grey, 1 when the color is
575 in the boundary of the rgb cube.
576 */
577
578 /* TODO: split handling of image handling off
579 * Ideally we would create a mask surface for the location of the images and
580 * we would blit the recolored and unmodified surfaces together to get the
581 * same effect.
582 */
583
584 cairo_surface_flush(surface);
585
586 const int rowstride = cairo_image_surface_get_stride(surface);
587 unsigned char* image = cairo_image_surface_get_data(surface);
588
589 /* RGB weights for computing lightness. Must sum to one */
590 static const double a[] = {0.30, 0.59, 0.11};
591
592 const GdkRGBA rgb1 = priv->recolor.dark;
593 const GdkRGBA rgb2 = priv->recolor.light;
594
595 const double l1 = a[0]*rgb1.red + a[1]*rgb1.green + a[2]*rgb1.blue;
596 const double l2 = a[0]*rgb2.red + a[1]*rgb2.green + a[2]*rgb2.blue;
597
598 const double rgb_diff[] = {
599 rgb2.red - rgb1.red,
600 rgb2.green - rgb1.green,
601 rgb2.blue - rgb1.blue
602 };
603
604 girara_list_t* images = NULL;
605 girara_list_t* rectangles = NULL;
606 bool found_images = false;
607
608 /* If in reverse video mode retrieve images */
609 if (priv->recolor.reverse_video == true) {
610 images = zathura_page_images_get(page, NULL);
611 found_images = (images != NULL);
612
613 rectangles = girara_list_new();
614 if (rectangles == NULL) {
615 found_images = false;
616 girara_warning("Failed to retrieve images.");
617 }
618
619 if (found_images == true) {
620 /* Get images bounding boxes */
621 GIRARA_LIST_FOREACH_BODY(images, zathura_image_t*, image_it,
622 zathura_rectangle_t* rect = g_try_malloc(sizeof(zathura_rectangle_t));
623 if (rect == NULL) {
624 break;
625 }
626 *rect = recalc_rectangle(page, image_it->position);
627 girara_list_append(rectangles, rect);
628 );
629 }
630 }
631
632 for (unsigned int y = 0; y < page_height; y++) {
633 unsigned char* data = image + y * rowstride;
634
635 for (unsigned int x = 0; x < page_width; x++, data += 4) {
636 /* Check if the pixel belongs to an image when in reverse video mode*/
637 if (priv->recolor.reverse_video == true && found_images == true){
638 bool inside_image = false;
639 GIRARA_LIST_FOREACH_BODY(rectangles, zathura_rectangle_t*, rect_it,
640 if (rect_it->x1 <= x && rect_it->x2 >= x &&
641 rect_it->y1 <= y && rect_it->y2 >= y) {
642 inside_image = true;
643 break;
644 }
645 );
646 /* If it's inside and image don't recolor */
647 if (inside_image == true) {
648 continue;
649 }
650 }
651
652 /* Careful. data color components blue, green, red. */
653 const double rgb[3] = {
654 data[2] / 255.,
655 data[1] / 255.,
656 data[0] / 255.
657 };
658
659 /* compute h, s, l data */
660 double l = a[0]*rgb[0] + a[1]*rgb[1] + a[2]*rgb[2];
661
662 if (priv->recolor.hue == true) {
663 /* adjusting lightness keeping hue of current color. white and black
664 * go to grays of same ligtness as light and dark colors. */
665 const double h[3] = {
666 rgb[0] - l,
667 rgb[1] - l,
668 rgb[2] - l
669 };
670
671 /* u is the maximum possible saturation for given h and l. s is a
672 * rescaled saturation between 0 and 1 */
673 const double u = colorumax(h, l, 0, 1);
674 const double s = fabs(u) > DBL_EPSILON ? 1.0 / u : 0.0;
675
676 /* Interpolates lightness between light and dark colors. white goes to
677 * light, and black goes to dark. */
678 l = l * (l2 - l1) + l1;
679
680 const double su = s * colorumax(h, l, l1, l2);
681 data[2] = (unsigned char)round(255.*(l + su * h[0]));
682 data[1] = (unsigned char)round(255.*(l + su * h[1]));
683 data[0] = (unsigned char)round(255.*(l + su * h[2]));
684 } else {
685 /* linear interpolation between dark and light with color ligtness as
686 * a parameter */
687 data[2] = (unsigned char)round(255.*(l * rgb_diff[0] + rgb1.red));
688 data[1] = (unsigned char)round(255.*(l * rgb_diff[1] + rgb1.green));
689 data[0] = (unsigned char)round(255.*(l * rgb_diff[2] + rgb1.blue));
690 }
691 }
692 }
693
694 if (images != NULL) {
695 girara_list_free(images);
696 }
697 if (rectangles != NULL) {
698 girara_list_free(rectangles);
699 }
700
701 cairo_surface_mark_dirty(surface);
702 }
703
704 static bool
invoke_completed_signal(render_job_t * job,cairo_surface_t * surface)705 invoke_completed_signal(render_job_t* job, cairo_surface_t* surface)
706 {
707 emit_completed_signal_t* ecs = g_try_malloc0(sizeof(emit_completed_signal_t));
708 if (ecs == NULL) {
709 return false;
710 }
711
712 ecs->job = job;
713 ecs->surface = cairo_surface_reference(surface);
714
715 /* emit signal from the main context, i.e. the main thread */
716 g_main_context_invoke(NULL, emit_completed_signal, ecs);
717 return true;
718 }
719
720 static bool
render_to_cairo_surface(cairo_surface_t * surface,zathura_page_t * page,ZathuraRenderer * renderer,double real_scale)721 render_to_cairo_surface(cairo_surface_t* surface, zathura_page_t* page, ZathuraRenderer* renderer, double real_scale)
722 {
723 cairo_t* cairo = cairo_create(surface);
724 if (cairo == NULL) {
725 return false;
726 }
727
728 cairo_save(cairo);
729 cairo_set_source_rgb(cairo, 1, 1, 1);
730 cairo_paint(cairo);
731 cairo_restore(cairo);
732 cairo_save(cairo);
733
734 /* apply scale (used by e.g. Poppler as pixels per point) */
735 if (fabs(real_scale - 1.0f) > FLT_EPSILON) {
736 cairo_scale(cairo, real_scale, real_scale);
737 }
738
739 zathura_renderer_lock(renderer);
740 const int err = zathura_page_render(page, cairo, false);
741 zathura_renderer_unlock(renderer);
742 cairo_restore(cairo);
743 cairo_destroy(cairo);
744
745 return err == ZATHURA_ERROR_OK;
746 }
747
748 static bool
render(render_job_t * job,ZathuraRenderRequest * request,ZathuraRenderer * renderer)749 render(render_job_t* job, ZathuraRenderRequest* request, ZathuraRenderer* renderer)
750 {
751 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
752 ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
753 zathura_page_t* page = request_priv->page;
754
755 /* create cairo surface */
756 unsigned int page_width = 0;
757 unsigned int page_height = 0;
758
759 /* page size in points */
760 zathura_document_t* document = zathura_page_get_document(page);
761 const double height = zathura_page_get_height(page);
762 const double width = zathura_page_get_width(page);
763
764 zathura_device_factors_t device_factors = { 0 };
765 double real_scale = 1;
766 if (request_priv->render_plain == false) {
767 /* page size in user pixels based on document zoom: if PPI information is
768 * correct, 100% zoom will result in 72 documents points per inch of screen
769 * (i.e. document size on screen matching the physical paper size). */
770 real_scale = page_calc_height_width(document, height, width,
771 &page_height, &page_width, false);
772
773 device_factors = zathura_document_get_device_factors(document);
774 page_width *= device_factors.x;
775 page_height *= device_factors.y;
776 } else {
777 page_width = width;
778 page_height = height;
779 }
780
781 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
782 page_width, page_height);
783 if (surface == NULL) {
784 return false;
785 }
786
787 if (request_priv->render_plain == false) {
788 cairo_surface_set_device_scale(surface, device_factors.x, device_factors.y);
789 }
790
791 if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
792 cairo_surface_destroy(surface);
793 return false;
794 }
795
796 /* actually render to the surface */
797 if (!render_to_cairo_surface(surface, page, renderer, real_scale)) {
798 cairo_surface_destroy(surface);
799 return false;
800 }
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 cairo_surface_destroy(surface);
808 return true;
809 }
810
811 /* recolor */
812 if (request_priv->render_plain == false && priv->recolor.enabled == true) {
813 recolor(priv, page, page_width, page_height, surface);
814 }
815
816 if (!invoke_completed_signal(job, surface)) {
817 cairo_surface_destroy(surface);
818 return false;
819 }
820
821 cairo_surface_destroy(surface);
822
823 return true;
824 }
825
826 static void
render_job(void * data,void * user_data)827 render_job(void* data, void* user_data)
828 {
829 render_job_t* job = data;
830 ZathuraRenderRequest* request = job->request;
831 ZathuraRenderer* renderer = user_data;
832 g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
833 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
834
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 return;
840 }
841
842 ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
843 girara_debug("Rendering page %d ...",
844 zathura_page_get_index(request_priv->page) + 1);
845 if (render(job, request, renderer) != true) {
846 girara_error("Rendering failed (page %d)\n",
847 zathura_page_get_index(request_priv->page) + 1);
848 remove_job_and_free(job);
849 }
850 }
851
852
853 void
render_all(zathura_t * zathura)854 render_all(zathura_t* zathura)
855 {
856 if (zathura == NULL || zathura->document == NULL) {
857 return;
858 }
859
860 /* unmark all pages */
861 const unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
862 for (unsigned int page_id = 0; page_id < number_of_pages; ++page_id) {
863 zathura_page_t* page = zathura_document_get_page(zathura->document,
864 page_id);
865 unsigned int page_height = 0, page_width = 0;
866 const double height = zathura_page_get_height(page);
867 const double width = zathura_page_get_width(page);
868 page_calc_height_width(zathura->document, height, width, &page_height, &page_width, true);
869
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 if (widget != NULL) {
873 gtk_widget_set_size_request(widget, page_width, page_height);
874 gtk_widget_queue_resize(widget);
875 }
876 }
877 }
878
879 static gint
render_thread_sort(gconstpointer a,gconstpointer b,gpointer UNUSED (data))880 render_thread_sort(gconstpointer a, gconstpointer b, gpointer UNUSED(data))
881 {
882 if (a == NULL || b == NULL) {
883 return 0;
884 }
885
886 const render_job_t* job_a = a;
887 const render_job_t* job_b = b;
888 if (job_a->aborted == job_b->aborted) {
889 ZathuraRenderRequestPrivate* priv_a = zathura_render_request_get_instance_private(job_a->request);
890 ZathuraRenderRequestPrivate* priv_b = zathura_render_request_get_instance_private(job_b->request);
891
892 return priv_a->last_view_time < priv_b->last_view_time ? -1 :
893 (priv_a->last_view_time > priv_b->last_view_time ? 1 : 0);
894 }
895
896 /* sort aborted entries earlier so that they are thrown out of the queue */
897 return job_a->aborted ? 1 : -1;
898 }
899
900 /* cache functions */
901
902 static bool
page_cache_is_cached(ZathuraRenderer * renderer,unsigned int page_index)903 page_cache_is_cached(ZathuraRenderer* renderer, unsigned int page_index)
904 {
905 g_return_val_if_fail(renderer != NULL, false);
906 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
907
908 if (priv->page_cache.num_cached_pages != 0) {
909 for (size_t i = 0; i < priv->page_cache.size; ++i) {
910 if (priv->page_cache.cache[i] >= 0 &&
911 page_index == (unsigned int)priv->page_cache.cache[i]) {
912 girara_debug("Page %d is a cache hit", page_index + 1);
913 return true;
914 }
915 }
916 }
917
918 girara_debug("Page %d is a cache miss", page_index + 1);
919 return false;
920 }
921
922 static int
find_request_by_page_index(const void * req,const void * data)923 find_request_by_page_index(const void* req, const void* data)
924 {
925 ZathuraRenderRequest* request = (void*) req;
926 const unsigned int page_index = *((const int*)data);
927
928 ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
929 if (zathura_page_get_index(priv->page) == page_index) {
930 return 0;
931 }
932 return 1;
933 }
934
935 static ssize_t
page_cache_lru_invalidate(ZathuraRenderer * renderer)936 page_cache_lru_invalidate(ZathuraRenderer* renderer)
937 {
938 g_return_val_if_fail(renderer != NULL, -1);
939 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
940 g_return_val_if_fail(priv->page_cache.size != 0, -1);
941
942 ssize_t lru_index = 0;
943 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);
949 ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(tmp_request);
950
951 if (request_priv->last_view_time < lru_view_time) {
952 lru_view_time = request_priv->last_view_time;
953 lru_index = i;
954 request = tmp_request;
955 }
956 }
957
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 girara_debug("Invalidated page %d at cache index %zd",
963 zathura_page_get_index(request_priv->page) + 1, lru_index);
964 priv->page_cache.cache[lru_index] = -1;
965 --priv->page_cache.num_cached_pages;
966
967 return lru_index;
968 }
969
970 static bool
page_cache_is_full(ZathuraRenderer * renderer,bool * result)971 page_cache_is_full(ZathuraRenderer* renderer, bool* result)
972 {
973 g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer) && result != NULL, false);
974
975 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
976 *result = priv->page_cache.num_cached_pages == priv->page_cache.size;
977
978 return true;
979 }
980
981 static void
page_cache_invalidate_all(ZathuraRenderer * renderer)982 page_cache_invalidate_all(ZathuraRenderer* renderer)
983 {
984 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
985
986 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
987 for (size_t i = 0; i < priv->page_cache.size; ++i) {
988 priv->page_cache.cache[i] = -1;
989 }
990 priv->page_cache.num_cached_pages = 0;
991 }
992
993 void
zathura_renderer_page_cache_add(ZathuraRenderer * renderer,unsigned int page_index)994 zathura_renderer_page_cache_add(ZathuraRenderer* renderer,
995 unsigned int page_index)
996 {
997 g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
998 if (page_cache_is_cached(renderer, page_index) == true) {
999 return;
1000 }
1001
1002 ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
1003 bool full = false;
1004 if (page_cache_is_full(renderer, &full) == false) {
1005 return;
1006 } else if (full == true) {
1007 const ssize_t idx = page_cache_lru_invalidate(renderer);
1008 if (idx == -1) {
1009 return;
1010 }
1011
1012 priv->page_cache.cache[idx] = page_index;
1013 ++priv->page_cache.num_cached_pages;
1014 girara_debug("Page %d is cached at cache index %zd", page_index + 1, idx);
1015 } else {
1016 priv->page_cache.cache[priv->page_cache.num_cached_pages++] = page_index;
1017 girara_debug("Page %d is cached at cache index %zu", page_index + 1,
1018 priv->page_cache.num_cached_pages - 1);
1019 }
1020
1021 ZathuraRenderRequest* request = girara_list_find(priv->requests,
1022 find_request_by_page_index, &page_index);
1023 g_return_if_fail(request != NULL);
1024 g_signal_emit(request, request_signals[REQUEST_CACHE_ADDED], 0);
1025 }
1026
zathura_render_request_set_render_plain(ZathuraRenderRequest * request,bool render_plain)1027 void zathura_render_request_set_render_plain(ZathuraRenderRequest* request,
1028 bool render_plain)
1029 {
1030 g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
1031
1032 ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
1033 priv->render_plain =render_plain;
1034 }
1035
1036 bool
zathura_render_request_get_render_plain(ZathuraRenderRequest * request)1037 zathura_render_request_get_render_plain(ZathuraRenderRequest* request)
1038 {
1039 g_return_val_if_fail(ZATHURA_IS_RENDER_REQUEST(request), false);
1040
1041 ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
1042 return priv->render_plain;
1043 }
1044
1045