三自绘图运动

静态题图
静态题图

释题略。题图见上。

缘起

因为知乎文章有「题图」功能,截止到为止在知乎发表的文章,最后一篇是《〈范版数学恶补记〉流水账〔四十〕》,都自制了题图上传。

当时在各种千奇百怪软件和钦定红客种下的木马均极大充沛的旧台式机上,为了省事起见,使用了「Micosoft™©® Visio™©®」拼凑图形和文本,然后截图。

题图Visio版
题图Visio

为了保证每次截图大小相同,还得调整缩放比例保持一致,先按照坐标网格节点精确到像素级别截一遍,然后在Visio中设置不显示网格,再「重复上一次截图」获得白色背景图片。

为了能做到「精确到像素级别截图」,一直使用「HyperSnap」软件,除了能用鼠标一圈之外,还可以用键盘控制起点终点,方便得很。虽然说当代有许多免费甚至开源的截图工具,但是还没找到能提供以键盘控制截图范围之功能的替代品。

后来嘛,集中精力转向以Markdown格式码字,而简书不支持题图功能,作业部落也一样。并且又换了新机,所以中间杂七杂八的随笔都没做题图,一直空缺到现在。

于是乎,在新机已经磨合完毕(安装必要软件和调整各项设置)正常使用效率接近旧机的现在,重建题图就提上了码字日程。虽然简书不支持「上传题图」,但是可以把题图贴在文章开头起到同样的效果,还不局限于静态图片。

目标很简单,就是使用开源至少是免费的工具,在当前环境下,炮制出不逊色于甚至远胜于之前版本题图品质的图片。这不是软件之间的优劣比较,而是老迈年高的「精神病仆街写手不入流码农数学渣」突破过去的自己。

过程

前两篇「三自运动」随笔当中提到的内容略。

图片选择SVG格式,不仅仅是因为其「矢量」特性,还关乎以不入流码农身份客串图农的工作便利。

众所周知,小学语文课本当中有这么一句话勒令学生背诵:「人有两个宝,双手和大脑」。最近整理旧日记的同时记忆沉渣泛起,忽然想起了这句话。

然后开始反思,「三省吾身」「有则改之无则加勉」:大脑想必是有的,因为「我思故我在」;双手也是有的,看得见摸得着。那么我这老迈年高的历史唯物主义の尘埃和科技昌明民智大开的当代那些党国栋梁青年才俊或子承父业童年才俊钦定接班人之间有什么区别呢?

初步结论大概是这样的。我的「双手」上面十根手指头都在,而青年才俊和童年才俊虽然也有双手,但恐怕只有一根手指头。青年才俊是食指,用于操作鼠标;童年才俊则是大拇指,用于操作手机。

因此,对于SVG这种格式来说,除了可以用「可视化工具」处理之外,还能在「编辑器」内作为「源代码」修改,并且修改结果还可以提供实时预览。

我虽然老迈年高,但基本功还在,看五颜六色「纯文本」不眼晕,并且还知道每个标签当中涉及的元素或属性都对应图形界面底层的什么操作。

所以,虽然试用了号称可替代「Adobe Illustrator」的包括但不限于「Inkscape」在内的基于SVG的免费甚至开源之矢量绘图工具,但是看到生成的文件当中标签注释极大充沛,确实眼晕。对于我当前面对的简单需求来说,还不如手写极简主义纯文本更方便。

准备工作

题图布局简单,其中的文本比较容易,不解释。而作为标志的「希帕索斯十字」就不好办了。数学上很简单,就是两个极坐标方程「ρ = cos2θ」「ρ = sin2θ」,但是绘图比较麻烦。

同样是为了省事,当初是在Mathematica上面画的,并以图片方式插入Visio当中。其实其它任何一定品质的数学软件都可以绘图,但是是否提供充沛设置以调整图像显示效果使得普通用户可以直接保存,那就不一定了。

比方说,用SageMath也可以绘图,详见知乎的这个回答简书备份已被「简书(jianshu.com)」屏蔽。(附本站链接),但是颜色,以及附加的坐标轴、网格和标签就不那么容易处理了。用来对比,最后列出了Mathematica中用于绘图的代码,画出来直接截图即可。

还有一个问题。在旧台式机上,因为连不上GitHub所以不能获得SageMathWindows安装包,使用的是虚拟机映像的8.0版,绘图正常。而换了新机之后,下载了8.2Windows安装包,装上之后反而不正常了。

SageMath绘图错误
SageMath绘图错误

看错误信息当中出现「ascii」字眼,很明显是因为Windows特有的「代码页」的缘故。而SageMath使用的一些组件,仍然依赖「Python 2.7」,于是无法处理。

网上搜到的信息显示,「境外用户」纷纷表示情绪稳定,说SageMathWindows安装包装上就能用,比虚拟机映像版更方便。不过呢,估计SageMath团队测试用环境,以及主要用户的使用环境,应该都是「英文Windows」吧,也是「Python 2.7」的开发及测试环境。

总之,既然用不了,还得想其它办法画十字。经过一番艰苦卓绝的搜索工作之后,在繁荣的互联网上找到了可免费使用的站点「FooPlot|线上数学函数绘图器」,还提供SVG格式下载。

希帕索斯十字

首先,在FooPlot上分别绘制两个极坐标方程,并「导出成」SVG格式「下载」,参数设定如下图:

使用FooPlot绘图
使用FooPlot绘图

SVG的处理过程略,都是前两篇提到的内容。

«ρ=cos(2θ).svg»

<svg xmlns="http://www.w3.org/2000/svg"
    width="650" height="650" viewBox="0 0 650 650"
    style="background-color: blue">
    <path d="M …… Z"
        fill="none" stroke="white" stroke-width="30"/>
</svg>

«ρ=sin(2θ).svg»

<svg xmlns="http://www.w3.org/2000/svg"
    width="650" height="650" viewBox="0 0 650 650"
    style="background-color:white">
    <path d="M …… Z"
        fill="none" stroke="blue" stroke-width="30"/>
</svg>

代码中路径参数均已省略。但是要提醒,在「<path>」元素的「d」属性值的最后,要手动加上一个「Z」命令,确保路径闭合。毕竟在线绘图的区间是(0, 2π),最终图片放大后会在「θ = 0」处显示出一个小小的缝隙。

组合图形和文本

既然素材有了,那么接下来就需要「一张白纸好作画」了也。除了「上传头像」之类场合可以直接使用素材,其它时候都需要加工嘛。

磨刀不误砍柴工,最终题图之前,还得重新熟悉安排各元素的布局,以及试验准备附加的各种效果。所以,先纪念伟大的数学先驱者希帕索斯同志。

希帕索斯永垂不朽
希帕索斯永垂不朽
«HippasusCross.svg»

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
    width="640" height="480" viewBox="0 0 320 240">

<style>
    svg { background-color: gray; }
    rect { stroke: none; }
    rect.icon { fill: url(#IconGradient); }
    rect.logo { fill: url(#LogoGradient); }
    path { fill: none; stroke-width: 30; }
    path.icon { stroke: white; }
    path.logo { stroke: blue; }
    text { text-anchor: middle; }
    .label { alignment-baseline: hanging;
        font: 24px 'Latin Modern Math'; fill: gold; stroke: gold; }
    .caption { font: 32px '隶书'; fill: black; stroke: black; }
</style>

<defs>
    <radialGradient id="IconGradient">
        <stop offset="0%" stop-color="blue"/>
        <stop offset="100%" stop-color="gray"/>
    </radialGradient>
    <radialGradient id="LogoGradient">
        <stop offset="0%" stop-color="white"/>
        <stop offset="100%" stop-color="gray"/>
    </radialGradient>
</defs>

……

</svg>

上述代码中同样省略了所有实际内容元素,只剩下样式以及渐变效果的部分。全贴出来太长了,又没有地方可供上传附件并提供下载链接。实际工作情况「边改边预览」的截图(展开了内容元素)是这样的:

边改边预览
边改边预览

也就是说,为了重复使用内容,需要在「<symbol>」标签内定义元素(不渲染),然后在需要使用的地方采用「<use>」标签引用即可。

而「<symbol>」标签的优点,是可以定义视口「viewBox」,也就是独立的坐标系,于是「素材」可以作为单独的模块不加修改的插入,矢量图形就是可以无损缩放,于是不影响实际渲染效果。

与此对比,「<defs>」标签内部定义的不是实际内容元素,而是「效果」,也就是两个放射型色彩渐变。在前面的样式定义中,作为希帕索斯十字之背景的「<rect>」元素的填充效果,就引用了这里定义的内容。

文本的处理比较麻烦,因为页面坐标系的原点在左上角,而文本的「锚点」通常在左下角,是按照「基线」对齐的。

所以在样式中指定了「text-anchor: middle;」以便指定文本「中点」,这样操作简单,只要把「预定文本区域」的水平中点算出来就可以了也。

而垂直对齐就要具体情况具体分析,简便起见,作为图片注释的两个极坐标方程的文本的「基线」设定为「alignment-baseline: hanging;」,也就是「悬挂」在图片下方。

磨刀之后砍柴

接下来正式开工,准备做题图了。先是决定图片尺寸,以及计算各个元素的位置,略。

文本安插方式略,但是为了增加「作者」名,选择了「竖排」方式。对应到样式当中,是「writing-mode: tb;」这条。因为文本是汉字,还指定了「alignment-baseline: ideographic;」以按照「方块字」对齐。为了实现各种具体效果,试验了许多并不熟悉的属性值,是为「面向巧合编程」者也。

而对于标识的颜色,使用了线性渐变效果。不解释,代码如下:

<linearGradient id="LogoGradient" x1="1" x2="0" y1="1" y2="0">
    <stop offset="0%" stop-color="cyan"/>
    <stop offset="25%" stop-color="blue"/>
    <stop offset="50%" stop-color="aqua"/>
    <stop offset="75%" stop-color="blue"/>
    <stop offset="100%" stop-color="cyan"/>
</linearGradient>

其它内容之前都解说过了,这里略。接下来是画蛇添足或狗尾续貂,追加动态效果。

首先在头像上使用旋转,运用前一篇提到的「<animateTransform>」元素,略。

其次在标题上使用颜色渐变动画效果,但是由于「<animateColor>」元素已经被废弃,所以直接使用更通用的「<animate>」元素。

注意动画是追加在线性渐变渲染方式之上,而不是内容元素之上:

<linearGradient id="ArticleGradient">
    <stop offset="0%" stop-color="black">
    </stop>
    <stop offset="0%" stop-color="gold">
        <animate attributeName="offset" dur="6s" repeatCount="indefinite"
            values="0;.25;.5;.75;1;.75;.5;.25;" />
    </stop>
    <stop offset="100%" stop-color="black">
    </stop>
</linearGradient>

就是说,选择渐变当中的某个「定位点」,动态调整其(比例)位置,实现渲染的变化。而常见的操作,除了调整位置之外,还可以调整颜色。

比方说以背景色循环变化实现「昼夜更替」的错觉,其实已经上网乱搜照葫芦画瓢做出来了,但是感觉太花哨看着乱,就删掉了也。

再然后就是那个小头像绕着转的「轨道」了,使用「<animateMotion>」元素。

<symbol id="Logo" viewBox="0 0 650 650">
    <path id="Orbit" d="M …… Z" />
    <use xlink:href="#Icon" x="0" y="0" width="65" height="65">
        <animateMotion dur="8s" repeatCount="indefinite" rotate="auto-reverse">
            <mpath xlink:href="#Orbit" />
        </animateMotion>
    </use>
</symbol>

这就是「重用」模块的好处,在「Logo」定义中使用了之前定义的「Icon」并指定自身为「(外部引用)路径」追加动态移动效果。

差不多先这些吧。成果仍然无法直接转换,还是以屏幕录像方式生成的GIF「动态题图」:注:在GitHub上传时已换成原SVG文件

动态题图
动态题图

使用的开源免费工具唤作「LICEcap」,支持「Microsoft™©® Windows™©®」和「Apple™©® macOS™©®」,推荐。

后记

其实也没啥好写的了。把遇到的问题罗列一下吧。

首先是竖排文本,对齐方式仍然是个迷,尤其是各个浏览器对标准的支持程度以及实现的程度都不一样。而基于「Electron」框架的「VS Code」所使用的「预览」是基于「Chromium」的,属于「谷歌那一套」。

所以,「面向巧合编程」很长时间也没找到能如同「可视化工具」当中鼠标一点就能居中的设置方式。时间有限暂且放下,实践中是通过硬编码坐标实现的。

其次是动图转换。前一篇说过了,目前主流软件,无论是免费开源还是收费商业软件,似乎都不支持SVG动画,也很少有支持GIFAPNG的。

底层的原理很简单,就是一个「定时器」(Timer),把每次渲染的内容按顺序保存为帧,并以相应格式写进二进制文件形成「.gif」「.png」而已。

那么,就看源码也知道,各个动画效果指定的「dur」是不一样的,比较随意。因此难以自动决定持续时间和帧率,也是可以理解的嘛。

也就是说,如果要自己实现,就按照单独一个SVG文件当中出现的各个持续时间数值计算最小公倍数,作为总时长,然后……发现渲染的问题又出现了。

因为目前的浏览器环境提供的功能已经很强大了,自行解决渲染SVG的工作,需要依赖第三方库,包括但不限于「Apache Batik」(Java™版),不知道.Net阵营有没有对应的工作。

但是,为了避免豪门贵种走兽派歪曲我的言行举止吃喝拉撒睡诈骗不明真相的人民群众引领政治和意识形态斗争新动向,我最近在极力避免写具体代码。无论是什么编程语言,能使用既存功能通过「设置」或修改「数据」实现的效果,绝不编程。

那么,老吾老以及人之幼,老迈年高的我使用十根手指头吃力不讨好事倍功半之余,换位思考将心比心,也能体会到仅使用食指的党国栋梁青年才俊和仅使用大拇指的与时俱进童年才俊的野望:能写「大数据」就绝不写代码,能用「脚本」就绝不用生产语言。

毕竟,「码农最优秀的品质,就是懒惰啊」(Perl作者Larry Wall语)。

2018-06-11