數學公式轉圖片:純Python實現,可設置字體、字號、顏色和分辨率
寫數學公式,功能最強大的當然是LaTex了。不過,強大不代表易用,駕馭LaTex絕不是一件容易的事兒。這也不難理解:畢竟數學公式不是孤立存在的,必然要作為文檔、網頁或者程序輸出的元素,如何無縫地讓LaTex關聯到文檔、網頁或程序,的確是個棘手的難題。
既然直接使用LaTex有難度,那就退而求其次:借助于工具將數學公式轉為圖片,然后就可以方便地應用到文檔、網頁或者程序中了。這樣的工具,除了在線式的,基本上都是重量級的,安裝和使用極不方便。我曾經花了11個C幣從CSDN買過一個數學公式轉圖片的工具,下載之后發現,竟然只是封裝了一個http請求,圖片仍然是在線生成的!一氣之下,自己寫了個離線的,只是功能比較簡單,不能方便地設置輸出圖像的大小和顏色。
最近工作中又用到了LaTex,幾經嘗試,終于在matplotlib源碼中發現了一個處理LaTex數學公式的好東西。稍加改造,增加了字體、字號、顏色和分辨率的設置,最終完美解決了LaTex數學公式轉圖片的問題。
1. 核心代碼
先從最簡單的開始吧。matplotlib有個mathtext子模塊,提供了math_to_image函數可以直接將LaTex數學公式生成圖片。下面的代碼,僅僅兩行,就將質能方程轉成了圖片。請注意,LaTex數學公式一定要包含在兩個$符號之間。
- >>> from matplotlib import mathtext
- >>> mathtext.math_to_image(r'$E=mc^2$', r'd:\demo_1.png')
生成的圖片寬度55個像素,高度15個像素,分辨率為100dpi。這么迷你,看上去可憐巴巴的。
雖然指定了png格式,也確實存在透明通道,其背景卻是不透明的。除了png格式外,math_to_image函數還支持eps, pdf, pgf, png, ps, raw, rgba, svg, svgz等格式,但不支持jpg格式。
2. 設置字體、字號、分辨率
要設置字體字號,就得先導入matplotlib的font_manager字體管理模塊。該模塊的FontProperties類可以實例化一個字體對象傳給math_to_image函數,用來設置family(字體)、size(字號)和weight(筆畫輕重)等。
math_to_image函數的dpi參數用于設置分辨率(每英寸像素數)。如果應用于網頁的話,建議分辨率設置為72dpi就可以了,如果用于印刷,請將dpi設置為300。
- >>> import matplotlib.font_manager as mfm
- >>> prop = mfm.FontProperties(family='sans-serif', size=64, weight='normal')
- >>> mathtext.math_to_image(r'$E=mc^2$', r'd:\demo_2.png', prop=prop, dpi=72)
輸出結果如下。這次生成的圖片終于不那么可憐了,寬度250個像素,高度59個像素。
如果不知道有哪些字體可用怎么辦?不要擔心,下面這一行代碼就可以列出當前系統中全部的可用字體。在我的電腦上運行之后,找到了幾百種可用的字體(重名的字體表示該字體有多個字體文件)。
- >>> [item.name for item in mfm.fontManager.ttflist]
3. 設置顏色
要想對圖片文件做顏色處理,最好的方式是先將math_to_image的輸出暫存到類文件對象中,借助于PIL和NumPy完成顏色設置后,再保存為文件。為此,要先導入io模塊、pillow模塊和numpy模塊。
在開始寫代碼前,先約定使用浮點型的三元組表示顏色,比如,(0.17, 0.63, 0.17)表示亮度稍暗的綠色。如果喜歡使用其他方式表示顏色,請自行轉換。下面的例子換了一個復雜的數學公式(虛構的,并無實際意義)來演示如何設置顏色。
- >>> from io import BytesIO
- >>> from PIL import Image
- >>> import numpy as np
- >>> text = r'$s(t) = \mathcal{A}\mathrm{sin}(2 \omega \sum_{i=0}^\infty t_i)$'
- >>> color = (0.17, 0.63, 0.17) # 要使用的顏色
- >>> bfo = BytesIO() # 創建二進制的類文件對象
- >>> prop = mfm.FontProperties(family='Palatino Linotype', size=256, weight='normal')
- >>> mathtext.math_to_image(text, bfo, prop=prop, dpi=72)
- 209.0
- >>> im = Image.open(bfo) # 打開二進制的類文件對象,返回一個PIL圖像對象
- >>> r, g, b, a = im.split() # 分離出RGBA四個通道
- >>> r, g, b = 255-np.array(r), 255-np.array(g), 255-np.array(b) # RGB通道反白
- >>> a = r/3 + g/3 + b/3 # 生成新的alpha通道
- >>> r, g, b = r*color[0], g*color[1], b*color[2] # RGB通道設置為目標顏色
- >>> im = np.dstack((r,g,b,a)).astype(np.uint8) # RGBA四個通道合并為三維的numpy數組
- >>> im = Image.fromarray(im) # numpy數組轉PIL圖像對象
- >>> im.save(r'd:\demo_3.png') # PIL圖像對象保存為文件
來看看最終的輸出結果是什么樣的呢?最終生成了2451x653的一張大圖,公式內容、字體、字號、顏色等,正如期望的那樣。大功告成!
4. 封裝成函數
為了方便使用,將上面的代碼封裝成一個函數,完整代碼如下。
- # -*- coding: utf-8 -*-
- import os
- from io import BytesIO
- from PIL import Image
- import numpy as np
- import matplotlib.font_manager as mfm
- from matplotlib import mathtext
- def latex2img(text, size=32, color=(0.1,0.1,0.1), out=None, **kwds):
- """LaTex數學公式轉圖片
- text - 文本字符串,其中數學公式須包含在兩個$符號之間
- size - 字號,整型,默認32
- color - 顏色,浮點型三元組,值域范圍[0,1],默認深黑色
- out - 文件名,僅支持后綴名為.png的文件名。若為None,則返回PIL圖像對象
- kwds - 關鍵字參數
- dpi - 輸出分辨率(每英寸像素數),默認72
- family - 系統支持的字體,None表示當前默認的字體
- weight - 筆畫輕重,可選項包括:normal(默認)、light和bold
- """
- assert out is None or os.path.splitext(out)[1].lower() == '.png', '僅支持后綴名為.png的文件名'
- for key in kwds:
- if key not in ['dpi', 'family', 'weight']:
- raise KeyError('不支持的關鍵字參數:%s'%key)
- dpi = kwds.get('dpi', 72)
- family = kwds.get('family', None)
- weight = kwds.get('weight', 'normal')
- bfo = BytesIO() # 創建二進制的類文件對象
- prop = mfm.FontProperties(family=family, size=size, weight=weight)
- mathtext.math_to_image(text, bfo, prop=prop, dpi=dpi)
- im = Image.open(bfo)
- r, g, b, a = im.split()
- r, g, b = 255-np.array(r), 255-np.array(g), 255-np.array(b)
- a = r/3 + g/3 + b/3
- r, g, b = r*color[0], g*color[1], b*color[2]
- im = np.dstack((r,g,b,a)).astype(np.uint8)
- im = Image.fromarray(im)
- if out is None:
- return im
- else:
- im.save(out)
- print('生成的圖片已保存為%s'%out)
- if __name__ == '__main__':
- text = r'$\sum_{i=0}^\infty x_i$'
- latex2img(text, size=48, color=(0.1,0.8,0.8), out=r'd:\demo.png')
- text = r'$\sum_{n=1}^\infty\frac{-e^{i\pi}}{2^n}$'
- im = latex2img(text, size=48, color=(0.9,0.1,0.1))
- im.show()