文档章节

使用PyGtk Pixbuf及freetype-py 显示文本

WolfCS
 WolfCS
发布于 2013/06/01 17:07
字数 2064
阅读 1062
收藏 2

使用PyGtk Pixbuf及freetype-py 显示文本

计算机上显示文本的过程大体上是,先将文本转换成一个一个的bitmap,然后再用图形系统将这些bitmap显示出来。freetype是一个open source的字体引擎,它完成的工作即是将字符转换成bitmap。freetype-py是一个freetype的python绑定,为我们在Python code中使用Freetype接口提供便利。

我们可以结合PyGtk所提供的Pixbuf对象,一些操作像素的方法,PyGtk所提供的窗口管理系统,及freetype转换字符到bitmap的方法,来显示字符。这个过程的code可以像下面这样:

#!/usr/bin/python
'''
author: Wolf-CS
website: http://my.oschina.net/wolfcs/blog
last edited: May 2013
'''

import gtk, gtk.gdk
import cairo
import freetype
import ctypes.util
from ctypes import *

class ColorMap:
    ''' A colormap is used to map scalar values to colors. It is build by
        adding couples of (value,color) where value must be between 0 and 1.
        The 'scale' method allows to specify the range of the colormap and
        the 'color' method then returns a color for any value. '''

    def __init__ (self, colors):
        self.colors = colors
        self.min    = 0
        self.max    = 1

    def scale (self, min, max):
        self.min, self.max = min,max

    def color (self, value):
        ''' Return the color corresponding to value. '''
        if not len(self.colors):
            return (0,0,0)
        elif len(self.colors) == 1:
            return self.colors[0][1]
        elif value < self.min:
            return self.colors[0][1]
        elif value > self.max:
            return self.colors[-1][1]
        value = (value-self.min)/(self.max-self.min)
        sup_color = self.colors[0]
        inf_color = self.colors[-1]
        for i in range (len(self.colors)-1):
            if value < self.colors[i+1][0]:
                inf_color = self.colors[i]
                sup_color = self.colors[i+1]
                break
        r = (value-inf_color[0]) / (sup_color[0] - inf_color[0])
        if r < 0: r = -r
        color = [sup_color[1][0]*r + inf_color[1][0]*(1-r),
                 sup_color[1][1]*r + inf_color[1][1]*(1-r),
                 sup_color[1][2]*r + inf_color[1][2]*(1-r)]
        return color

# Some colormaps
CM_IceAndFire = ColorMap([(0.00, (0.0, 0.0, 1.0)),
                         (0.25, (0.0, 0.5, 1.0)),
                         (0.50, (1.0, 1.0, 1.0)),
                         (0.75, (1.0, 1.0, 0.0)),
                         (1.00, (1.0, 0.0, 0.0))])
CM_Ice = ColorMap([(0.00, (0.0, 0.0, 1.0)),
                   (0.50, (0.5, 0.5, 1.0)),
                   (1.00, (1.0, 1.0, 1.0))])
CM_Fire = ColorMap([(0.00, (1.0, 1.0, 1.0)),
                    (0.50, (1.0, 1.0, 0.0)),
                    (1.00, (1.0, 0.0, 0.0))])
CM_Hot = ColorMap([(0.00, (0.0, 0.0, 0.0)),
                   (0.33, (1.0, 0.0, 0.0)),
                   (0.66, (1.0, 1.0, 0.0)),
                   (1.00, (1.0, 1.0, 1.0))])
CM_Grey = ColorMap([(0.00, (0.0, 0.0, 0.0)),
                    (1.00, (1.0, 1.0, 1.0))])

CM_MAPS = [CM_IceAndFire, CM_Ice, CM_Fire, CM_Hot, CM_Grey]

gcolor_map = CM_IceAndFire

def set_gcolor_map(color_map_index):
    index = color_map_index % len(CM_MAPS)
    global gcolor_map
    gcolor_map = CM_MAPS[index]

class MainWindow(gtk.Window):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.init_ui()
        self.create_pixbuf()

    def init_ui(self):
        self.darea = gtk.DrawingArea()
        self.darea.connect("expose_event", self.expose)
        self.add(self.darea)

        self.set_title("JpegImage")
        self.resize(960, 480)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("delete-event", gtk.main_quit)
        self.show_all()

    def create_pixbuf(self):
        width = 960
        height = 480
        self.datapb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, width, height)
        self.clear_pixbuf(self.datapb, 0, 128, 255, 255)

    def expose(self, widget, event):
        self.context = widget.window.cairo_create()
        self.on_draw(300, self.context)

    def on_draw(self, wdith, cr):
        text = "A Quick Brown Fox Jumps Over The Lazy Dog 0123456789"
        face = freetype.Face("./Arial.ttf")
        text_size = 32
        face.set_char_size(text_size * 64)
        
        metrics = face.size
        self.ascender  = metrics.ascender/64.0
        self.descender = metrics.descender/64.0
        self.height    = metrics.height/64.0
        self.linegap   = self.height - self.ascender + self.descender
#        print "ascender = %d, descender = %d, height = %d" % (self.ascender, self.descender, self.height)
#        self.draw_char(self.datapb, 20, 20, 'S', face)
        ypos = int(self.ascender)
        color_map_index = 0
        while ypos + int(self.height) < 480:
            set_gcolor_map(color_map_index)
            self.draw_string(self.datapb, 5, int(ypos), text, face)
            color_map_index += 1
            ypos += int(self.ascender - self.descender)
        
        gtk.gdk.CairoContext.set_source_pixbuf(cr, self.datapb, 0, 0)
        cr.paint()

    def draw_ft_bitmap(self, pixbuf, bitmap, pen):
        x_pos = pen.x >> 6
        y_pos = pen.y >> 6
        width = bitmap.width
        rows = bitmap.rows

        pixbuf_width = pixbuf.get_width()
        pixbuf_height = pixbuf.get_height()
#        print "y_pos = %d, pixbuf_height = %d" % (y_pos, pixbuf_height)
        assert ((y_pos > 0) and (y_pos + rows < pixbuf_height))
        assert ((x_pos > 0) and (x_pos + width < pixbuf_width))

        glyph_pixels = bitmap.buffer

        for line in range(rows):
            for column in range(width):
                if glyph_pixels[line * width + column] != 0:
                    colors = gcolor_map.color(glyph_pixels[line * width + column] / 255)
                    self.put_pixel(pixbuf, y_pos + line, x_pos + column, 
                               colors[0] * 255, 
                               colors[1] * 255,
                               colors[2] * 255,
                               255)

    def draw_string(self, pixbuf, x_pos, y_pos, str, face):
        prev_char = 0;
        pen = freetype.Vector()
        pen.x = x_pos << 6
        pen.y = y_pos << 6

        ascender = face.ascender
        descender = face.descender
        height = face.height
#        print "ascender = %d, descender = %d, height = %d" % (ascender, descender, height)

        hscale = 1.0
        matrix = freetype.Matrix(int((hscale) * 0x10000L), int((0.2) * 0x10000L),
                         int((0.0) * 0x10000L), int((1.1) * 0x10000L))

        cur_pen = freetype.Vector()
        pen_translate = freetype.Vector()
        for cur_char in str:
            face.set_transform(matrix, pen_translate)

            face.load_char(cur_char)
            kerning = face.get_kerning(prev_char, cur_char)
            pen.x += kerning.x
            slot = face.glyph
            bitmap = slot.bitmap

            cur_pen.x = pen.x
            cur_pen.y = pen.y - slot.bitmap_top * 64
            self.draw_ft_bitmap(pixbuf, bitmap, cur_pen)
            
            pen.x += slot.advance.x
            prev_char = cur_char

    def draw_char(self, pixbuf, x_pos, y_pos, char, face):
        face.load_char(char)
        slot = face.glyph
        bitmap = slot.bitmap

        pen = freetype.Vector()
        pen.x = x_pos << 6
        pen.y = y_pos << 6
        self.draw_ft_bitmap(pixbuf, bitmap, pen)

    def put_pixel(self, pixbuf, y_pos, x_pos, red, green, blue, alpha):
        n_channels = pixbuf.get_n_channels()
        width = pixbuf.get_width()
        height = pixbuf.get_height()
        assert (n_channels == 4)
        assert (y_pos >= 0 and y_pos < height)
        assert (x_pos >= 0 and x_pos < width)

        pixels = pixbuf.get_pixels_array()
        pixels[y_pos][x_pos][0] = red
        pixels[y_pos][x_pos][1] = green
        pixels[y_pos][x_pos][2] = blue
        pixels[y_pos][x_pos][3] = alpha

    def clear_pixbuf(self, pixbuf, red, green, blue, alpha):
        n_channels = pixbuf.get_n_channels()
        assert (n_channels == 4)

        width = pixbuf.get_width()
        height = pixbuf.get_height()

        pixels = pixbuf.get_pixels_array()
        for row in range(height):
            for column in range(width):
                pixels[row][column][0] = red
                pixels[row][column][1] = green
                pixels[row][column][2] = blue
                pixels[row][column][3] = alpha

def main():
    window = MainWindow()
    gtk.main()

if __name__ == "__main__":
    main()

首先,可以先来看一下上面那段code运行的结果。

上面那段code的整体思路为,

  1. 创建一个Pixbuf,
  2. 向Pixbuf中写入文本的像素数据,由于文本的像素数据是单色的,即描述文本的bitmap的一个像素点,会是一个字节,因而,可以依据需要,将这种单色的数据映射到适当的颜色,如上面的图所示。
  3. 使用PyGtk的窗口系统来显示这个Pixbuf。

def create_pixbuf(self):
        width = 700
        height = 480
        self.datapb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, width, height)
        self.clear_pixbuf(self.datapb, 0, 128, 255, 255)

创建一个Pixbuf。参数为色彩空间,是否需要alpha通道,每一个原色占用的位数,以及图片的宽和高,这些参数将决定着Pixbuf的像素缓冲区的大小。然后就是用一种蓝色来清空这个缓冲区。

def clear_pixbuf(self, pixbuf, red, green, blue, alpha):
        n_channels = pixbuf.get_n_channels()
        assert (n_channels == 4)

        width = pixbuf.get_width()
        height = pixbuf.get_height()

        pixels = pixbuf.get_pixels_array()
        for row in range(height):
            for column in range(width):
                pixels[row][column][0] = red
                pixels[row][column][1] = green
                pixels[row][column][2] = blue
                pixels[row][column][3] = alpha

由这个函数,有两点值得我们关注。其一,是写入像素数据的方法。我们需要调到Pixbuf的get_pixels_array()函数,它会返回一个可供我们修改的像素缓冲区的引用。其二,在Pixbuf中,是用什么样的结构来存储像素数据的。尽管我们能看到array的字样,但它并不是用一个一维数组来存的,可以看到,它是用一个元组的二维数组来存储像素数据的。每一个元组描述一个像素点的色彩数据,有4个元素,分别表示R、G、B、A值。每一个行的像素数据存入一个元组数组中,所有的像素数据再组成一个元组数组的数组。这样可以让我们很方便的依据像素点的位置来定位某个像素点的像素数据。

def on_draw(self, wdith, cr):
        text = "A Quick Brown Fox Jumps Over The Lazy Dog"
        face = freetype.Face("./Arial.ttf")
        text_size = 32
        face.set_char_size(text_size * 64)
        
        metrics = face.size
        self.ascender  = metrics.ascender/64.0
        self.descender = metrics.descender/64.0
        self.height    = metrics.height/64.0
        self.linegap   = self.height - self.ascender + self.descender
#        print "ascender = %d, descender = %d, height = %d" % (self.ascender, self.descender, self.height)
#        self.draw_char(self.datapb, 20, 20, 'S', face)
        ypos = int(self.ascender)
        color_map_index = 0
        while ypos + int(self.height) < 480:
            set_gcolor_map(color_map_index)
            self.draw_string(self.datapb, 5, int(ypos), text, face)
            color_map_index += 1
            ypos += int(self.ascender - self.descender)
        
        gtk.gdk.CairoContext.set_source_pixbuf(cr, self.datapb, 0, 0)
        cr.paint()

freetype-py不仅仅是导出了freetype的C API,它还对其中的一些结构进行了封装,以方便我们的操作。Face即是对于FT_Face的一种封装,使得我们的操作可以更加的简便。我们可以简单的将字库文件的路径传给Face的构造函数,来创建一个Face对象,然后为它设置字体大小。由于freetype内部对字体大小的格式的要求,我们需要将pixel的单位乘上一个64。

换行时,要求得绘制下一行的纵坐标,我们可以Face中提供的一些信息来完成。ascender为一个正数,descender为一个负数,两者相减,可以求得字的高度,也就可以依据绘制当前行的纵坐标,来求得下一行绘制的纵坐标。

可以将子串内容,face,及绘制的位置传给draw_string()函数来进行绘制。

def draw_string(self, pixbuf, x_pos, y_pos, str, face):
        prev_char = 0;
        pen = freetype.Vector()
        pen.x = x_pos << 6
        pen.y = y_pos << 6

        ascender = face.ascender
        descender = face.descender
        height = face.height
#        print "ascender = %d, descender = %d, height = %d" % (ascender, descender, height)

        hscale = 1.0
        matrix = freetype.Matrix(int((hscale) * 0x10000L), int((0.2) * 0x10000L),
                         int((0.0) * 0x10000L), int((1.1) * 0x10000L))

        cur_pen = freetype.Vector()
        pen_translate = freetype.Vector()
        for cur_char in str:
            face.set_transform(matrix, pen_translate)

            face.load_char(cur_char)
            kerning = face.get_kerning(prev_char, cur_char)
            pen.x += kerning.x
            slot = face.glyph
            bitmap = slot.bitmap

            cur_pen.x = pen.x
            cur_pen.y = pen.y - slot.bitmap_top * 64
            self.draw_ft_bitmap(pixbuf, bitmap, cur_pen)
            
            pen.x += slot.advance.x
            prev_char = cur_char

这个是draw_string()函数的实现。比较基本的freetype API的应用,设置transformation,load glyph,获取到kerning值以修正绘制字符的横坐标,然后便是抓取的glyph的bitmap,并绘制。

def draw_ft_bitmap(self, pixbuf, bitmap, pen):
        x_pos = pen.x >> 6
        y_pos = pen.y >> 6
        width = bitmap.width
        rows = bitmap.rows

        pixbuf_width = pixbuf.get_width()
        pixbuf_height = pixbuf.get_height()
#        print "y_pos = %d, pixbuf_height = %d" % (y_pos, pixbuf_height)
        assert ((y_pos > 0) and (y_pos + rows < pixbuf_height))
        assert ((x_pos > 0) and (x_pos + width < pixbuf_width))

        glyph_pixels = bitmap.buffer

        for line in range(rows):
            for column in range(width):
                if glyph_pixels[line * width + column] != 0:
                    colors = gcolor_map.color(glyph_pixels[line * width + column] / 255)
                    self.put_pixel(pixbuf, y_pos + line, x_pos + column, 
                               colors[0] * 255, 
                               colors[1] * 255,
                               colors[2] * 255,
                               255)

这个函数就是将freetype的glyph bitmap,做色彩映射之后,依据Pixbuf中存储像素数据的格式,将像素数据存储进Pixbuf中。

关于freetype API更详细的使用方法,可以参考freetype的官方文档,那里介绍的比较详细,比较权威,也比较清晰。

http://www.freetype.org/freetype2/documentation.html

Done。

© 著作权归作者所有

WolfCS
粉丝 81
博文 147
码字总数 505184
作品 4
杭州
高级程序员
私信 提问
解决Onethink验证码不显示问题

常见的GD未安装及BOM问题办法就不细说了,度娘上答案较多。 可是我遇到的验证码不显示不是这类原因导致的,反复检查最后才确定了是FreeType字体引擎未安装的原因。 首先,打开TP调试模式,右...

麦拂沙
2015/07/27
1K
0
Magento 添加验证码,请求503,系统日志报错:Image CAPTCHA requires FT fonts support

今天为ETS网站增加了注册时需要验证码的功能.在本地测试正常,上传到线上之后发现无法正常显示验证码图片检查发现验证码刷新的动作请求了 这个功能我再其他网站也添加了都没有问题。本地也没...

alt_tab_jj
2018/10/16
31
0
FreeType的网友评论

FreeType 2被设计为一种占用空间小的、高效的、高度可定制的、并且可以产生可移植的高品质输出(符号图像)。可以被用在诸如图像库、展出服务器、字体转换工具、图像文字产生工具等多种其它产...

红薯
2009/12/11
670
1
快被php 的 gd 搞疯了

服务器 ubuntu server,php 5.2.17 检测 gd,已经全部支持,可输出jpeg/png的时候,全部无法显示,现在网站验证码都显示不了,用gif生成倒是正常。求救啊,已经照着网上安装gd的文章,重新安...

jyh149129
2013/03/26
1K
12
Microsoft Office 使用了 FreeType 项目

微软说它“爱开源”,它确实是言行一致。在Microsoft Office 2011 for Mac(beta 5)中,微软使用了开源项目FreeType。 FreeType是一个用C语言实 现的字型栅格化引擎制作的的一个函式库。最新...

红薯
2010/08/31
1K
5

没有更多内容

加载失败,请刷新页面

加载更多

会用python把linux命令写一遍的人,进大厂有多容易?

看过这篇《2000字谏言,给那些想学Python的人,建议收藏后细看!》的读者应该都对一个命令有点印象吧?没错,就是 linux 中经常会用到的 ls 命令。 文章中我就提到如何提升自己的 python 能力...

上海小胖
11分钟前
1
0
HashMap的特性

一、hashmap数据结构:哈希表结构:数组+链表 hashmap调用默认构造方法会产生一个默认底层是长度为16的Entry数组,首先调用key的hasCode()方法来得到一个整数, int hash = hash(key.hashCode...

GGbird
12分钟前
2
0
第五章 spring-connet之Imports注解来龙去脉

前言 imports是一个在spring体系里非常重要的注解,基本每个Enable开头的注解必然有一个import注解。接下来我们深入研究下import的作用。看小节的同学建议先取看PostProcessorRegistrationDe...

鸟菜啊
15分钟前
1
0
CentOS部署Harbor镜像仓库

关于Harbor Harbor是用于存储和分发Docker镜像的镜像仓库服务,相比Docker Registry,Harbor在安全、标识、管理等方面做了增强,更适合企业使用; 官方网站:https://goharbor.io/ 官方开源:...

程序员欣宸
20分钟前
1
0
JavaScript调试必会的8个console方法

每个JavaScript开发者都用过console.log()来调试程序,但实际上Console对象还提供了很多其他方法可以提高调试效率。本文将介绍8个有趣的Console方法,即使JavaScript老手也不一定知道! 1、c...

汇智网教程
41分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部