为Evince-PDF添加灰色背景

Ubuntu系统的默认PDF阅读软件

ubuntu操作系统自带了Evince-PDF阅读器,其优点是简单便捷,不过与其他常用的PDF阅读器相比,缺少很多定制化的选项,例如为避免眼睛疲劳的替换阅读界面背景颜色的功能。使用GNU/Linux操作系统的一个优势是,可以获取从内核至应用各个层面的软件源码,并加以修改后重新编译,可以方便地实现所需的新功能。笔者在该文章中记录了为Evince阅读器添加灰色背景的操作过程。作为对比,在添加该功能之前,笔者打开SystemTap教程文档的背景如下:
在这里插入图片描述
修改之后,在Evince的选项中使能Night Mode,阅读的背景会被修改为:
在这里插入图片描述

获取Evince的源代码

笔者使用的Ubuntu系统版本为20.04。获取Evince源码并能够编译,需要做一些准备工作。首先,将/etc/apt/sources.list中的# deb-src前面的#号删除:

sudo sed -e 's/# deb-src/deb-src/g' -i /etc/apt/sources.list
sudo apt-get update

之后,安装重新编译Evince所需的依赖和系统软件:

sudo apt-get install build-essential libtool autoconf make pbuilder
sudo apt-get build-dep evince

最后,下载Evince的源码包:

mkdir ~/evince-rebuild
cd ~/evince-rebuild
apt-get source evince

修改Evince源码以加载动态库

Evince有一个Night Mode的选项,可以将界面的亮度反转。其实现比较简单,是使用到了“差异算子”,将界面与最大亮度值求差:

/* libdocument/ev-document-misc.c */
void ev_document_misc_invert_surface (cairo_surface_t *surface) {    
    cairo_t *cr;    
    cr = cairo_create (surface);    
    /* white + DIFFERENCE -> invert */    
    cairo_set_operator (cr, CAIRO_OPERATOR_DIFFERENCE);    
    cairo_set_source_rgb (cr, 1., 1., 1.);    
    cairo_paint(cr);    
    cairo_destroy (cr);    
} 

笔者在实现该功能时,考虑到替换该函数的实现,就可以修改阅读界面的背景。不过,笔者没有在该函数中直接操作cairo_surface_t对象,而是通过加载动态库实现的,这样Evince只需被重新编译一次。笔者对该代码的修改如下:

diff --git a/libdocument/ev-document-misc.c b/libdocument/ev-document-misc.c
index 1d0acc5..da6c6ad 100644
--- a/libdocument/ev-document-misc.c
+++ b/libdocument/ev-document-misc.c
@@ -27,6 +27,9 @@
 
 #include "ev-document-misc.h"
 
+#include <sys/stat.h>
+#include <dlfcn.h>
+
 /* Returns a new GdkPixbuf that is suitable for placing in the thumbnail view.
  * It is four pixels wider and taller than the source.  If source_pixbuf is not
  * NULL, then it will fill the return pixbuf with the contents of
@@ -463,10 +466,32 @@ ev_document_misc_surface_rotate_and_scale (cairo_surface_t *surface,
        return new_surface;
 }
 
+typedef void (* ext_invert_func)(cairo_surface_t *);
+
 void
 ev_document_misc_invert_surface (cairo_surface_t *surface) {
        cairo_t *cr;
-
+       void * invhdl;
+       struct stat invst;
+       ext_invert_func extfunc;
+       const char * invlib = "/usr/lib/evince_invert.so";
+
+       if (stat(invlib, &invst) == -1)
+               goto next;
+       if (S_ISREG(invst.st_mode) == 0 || invst.st_size == 0)
+               goto next;
+       invhdl = dlopen(invlib, RTLD_NODELETE | RTLD_NOW);
+       if (invhdl == NULL)
+               goto next;
+       extfunc = (ext_invert_func) dlsym(invhdl, "external_evince_invert");
+       if (extfunc == NULL) {
+               dlclose(invhdl);
+               goto next;
+       }
+       extfunc(surface);
+       dlclose(invhdl);
+       return;
+next:
        cr = cairo_create (surface);
 
        /* white + DIFFERENCE -> invert */

重新编译Evince之前,需要提交以上修改:

dpkg-source --commit

dpkg-source --commit需要交互操作(可带一些命令行参数,非交互),过程如下:

~/evince-rebuild/evince-3.36.10$ dpkg-source --commit
dpkg-source: info: using patch list from debian/patches/series
dpkg-source: info: local changes detected, the modified files are:
 evince-3.36.10/libdocument/ev-document-misc.c
Enter the desired patch name: load-external-invert-library
dpkg-source: info: local changes have been recorded in a new patch: evince-3.36.10/debian/patches/load-external-invert-library

接下来,执行编译操作:

debuild -S # 请忽略 debsign相关的错误:debsign: gpg error occurred
debuild # 编译链接过程会出错

上面的debuild会链接出错,原因是我们加入了dlopen相关的调用:

./libdocument/ev-document-misc.c:483: undefined reference to `dlopen'
/usr/bin/ld: ./libdocument/ev-document-misc.c:486: undefined reference to `dlsym'
/usr/bin/ld: ./libdocument/ev-document-misc.c:492: undefined reference to `dlclose'
/usr/bin/ld: ./libdocument/ev-document-misc.c:488: undefined reference to `dlclose'
collect2: error: ld returned 1 exit status
make[4]: *** [Makefile:787: libevdocument3.la] Error 1

修改libdocument/Makefile的第787行,加入链接选项-ldl,之后执行make继续编译,将编译得到的动态库libevdocument3.so.4.0.0替换到系统路径下:

sed -e '787s/$/ -ldl/' -i libdocument/Makefile
make
sudo cp -v ./libdocument/.libs/libevdocument3.so.4.0.0 /usr/lib/x86_64-linux-gnu/

编写evince_invert.so动态库修改界面背景

笔者编写了一个简单的动态库,用于将Evince阅读界面的白色背景替换为灰色。笔者实现的灰度转换曲线如下:

在这里插入图片描述

其默认最大亮度为208,不过可以通过环境变量EVINCE_MAXVAL指定:

/* evince_invert.c
 * gcc -shared -o evince_invert.so evince_invert.c -Wall -O2 -fPIC -D_GNU_SOURCE -std=c99 -lcairo -lm
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <cairo/cairo.h>

extern void external_evince_invert(cairo_surface_t * surf);
extern void evince_invert_init(void) __attribute__((constructor));

#define EVINCE_GRAY_NUM 256
static unsigned char invert_map[EVINCE_GRAY_NUM];
static unsigned char rgb_map[EVINCE_GRAY_NUM][EVINCE_GRAY_NUM];

void external_evince_invert(cairo_surface_t * surf)
{
	int pixelsize;
	cairo_format_t cfmt;
	unsigned char * pdat;
	int iwid, ihei, istr, idx, jdx;

	cfmt = cairo_image_surface_get_format(surf);
	if (cfmt == CAIRO_FORMAT_ARGB32)
		pixelsize = 0x4;
	else if (cfmt == CAIRO_FORMAT_RGB24)
		pixelsize = 0x3;
	else {
		fprintf(stderr, "Unknown cairo surface format: %#x\n",
			(unsigned int) cfmt);
		fflush(stderr);
		return;
	}

	pdat = cairo_image_surface_get_data(surf);
	if (pdat == NULL)
		return;

	iwid = cairo_image_surface_get_width(surf);
	if (iwid <= 0)
		return;
	ihei = cairo_image_surface_get_height(surf);
	if (ihei <= 0)
		return;
	istr = cairo_image_surface_get_stride(surf);
	if (istr < (pixelsize * iwid))
		return;

	for (jdx = 0; jdx < ihei; ++jdx) {
		unsigned char * pd = pdat;
		for (idx = 0; idx < iwid; ++idx) {
			unsigned int newgray, gray;
			unsigned int red, blue, green;

			blue  = (unsigned int) pd[0];
			green = (unsigned int) pd[1];
			red   = (unsigned int) pd[2];

			/*
			 * Y = 0.2126 * R + 0.7152 * G + 0.0722 * B
			 *
			 * 0.2126 * 65536 = 13932.9536 
			 * 0.7152 * 65536 = 46871.3472
			 * 0.0722 * 65536 = 4731.6992
			 */
			gray  = (red * 13933 + green * 46871 + blue * 4732) >> 16;
#if 0 /* impossible, as 13933 + 46871 + 4732 = 65536 */
			if (gray >= 0x100)
				gray = 0xff;
#endif
			newgray = (unsigned int) invert_map[gray];
			if (newgray >= gray) {
				pd += pixelsize;
				continue;
			}

			/* important: `newgray is less than `gray: */
#if 1
			const unsigned char * rgbmap;
			rgbmap = rgb_map[gray];
			pd[0] = rgbmap[blue];
			pd[1] = rgbmap[green];
			pd[2] = rgbmap[red];
#else
			pd[0] = (unsigned char) (blue * newgray / gray);
			pd[1] = (unsigned char) (green * newgray / gray);
			pd[2] = (unsigned char) (red * newgray / gray);
#endif
			pd += pixelsize;
		}
		pdat += istr;
	}
}

#define EVINCE_MAXVAL_DEFAULT 208
void evince_invert_init(void)
{
	const char * evmax;
	int idx, jdx, maxval, step;
	const int half = EVINCE_GRAY_NUM / 2;

	maxval = EVINCE_MAXVAL_DEFAULT;
	evmax = getenv("EVINCE_MAXVAL");
	if (evmax != NULL) {
		maxval = (int) strtol(evmax, NULL, 0);
		if (maxval <= half || maxval >= EVINCE_GRAY_NUM)
			maxval = EVINCE_MAXVAL_DEFAULT;
	}
	for (idx = 0; idx < EVINCE_GRAY_NUM; ++idx)
		invert_map[idx] = (unsigned char) idx;

	/* NOTE: step is a positive integer: */
	step = maxval - half;
	for (idx = half; idx < EVINCE_GRAY_NUM; ++idx) {
		double dval = (double) half;
		double didx = (double) (idx - half);
		dval += step * log(1.0 + didx * (M_E - 1.0) / half);
		invert_map[idx] = (unsigned char) ((long) (dval + 0.5));
	}

	for (jdx = 0; jdx < EVINCE_GRAY_NUM; ++jdx) {
		int ngray;
		unsigned char * pgray;

		pgray = rgb_map[jdx];
		ngray = (int) invert_map[jdx];
		ngray &= 0xff;
		if (ngray >= jdx) {
			for (idx = 0; idx < EVINCE_GRAY_NUM; ++idx)
				*pgray++ = (unsigned char) idx;
			continue;
		}

		for (idx = 0; idx < EVINCE_GRAY_NUM; ++idx)
			*pgray++ = (unsigned char) (idx * ngray / jdx);
	}
}

按照注释的命令编译完成后,将evince_invert.so复制到/usr/lib/目录下,就可以通过EvinceNight Mode转换PDF的阅读界面为灰度背景了;该方法不会改变颜色的显示。不过该功能也有其缺陷,不建议反复多次设置Night Mode选项,否则界面会越来越暗。若有需要而不要重新编译Evince,可发邮件给笔者提供这两个动态库文件:xiaoqzye@qq.com。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值