Pillow不支持color emoji font!

我想在MAC下面用pillow把一些文本转换成PNG图片,在转普通文字的时候都没问题,但在遇到emoji字符的时候就搞不定了,代码如下:

import    loggingimport    PIL.Image
import    PIL.ImageDraw
import    PIL.ImageFont
        
class    Main(object):def TestEmoji(self):
        text = u''
        fontName = '/System/Library/Fonts/Apple Color Emoji.ttf'
        pngPath = 'test.png'
        fontSize = 16

        font = PIL.ImageFont.truetype('/System/Library/Fonts/Apple Color Emoji.ttf', 16, encoding='unic')
        width, height = font.getsize(text)
        logging.debug('(width, height) = (%d, %d)' % (width, height))
        image = PIL.Image.new('RGBA', (width, height), (0, 0, 0, 0))  # 设置透明背景
        draw = PIL.ImageDraw.Draw(image)
        draw.text((0, 0), text, font = font, fill = '#000000')
        image.save(pngPath)
        image.show()

得到如下错误:

  File "test.py", line 37, in TestEmoji
    font = PIL.ImageFont.truetype('/System/Library/Fonts/Apple Color Emoji.ttf', 16, encoding='unic')
  File "/Users/palance/Downloads/Pillow-master/PIL/ImageFont.py", line 291, in truetype
    return FreeTypeFont(fontpath, size, index, encoding)
  File "/Users/palance/Downloads/Pillow-master/PIL/ImageFont.py", line 140, in __init__
    self.font = core.getfont(font, size, index, encoding)
IOError: invalid pixel size

根据提示,追到ImageFont.py:

……
try:
    from PIL import _imagingft as core
except ImportError:
    core = _imagingft_not_installed()

……
class FreeTypeFont(object):
    ……

    def __init__(self, font=None, size=10, index=0, encoding="", file=None):
        ……if isPath(font):
            self.font = core.getfont(font, size, index, encoding)  # 出错的140行
        else:
            ……

根据头部代码可以找到core是来自PIL/_imagingft,找到Pillow-master/PIL/_imagingft.so,这是由C写的so文件.再去追这段C代码,找到Pillow-master/_imagingft.c的getfont函数:

static PyObject*
getfont(PyObject* self_, PyObject* args, PyObject* kw)
{
    /* create a font object from a file name and a size (in pixels) */

    FontObject* self;
    int error = 0;

    char* filename = NULL;
    int size;
    int index = 0;
    unsigned char* encoding;
    unsigned char* font_bytes;
    int font_bytes_size = 0;
    static char* kwlist[] = {
        "filename", "size", "index", "encoding", "font_bytes", NULL
    };
    printf("1:%d\n", error);

    if (!library) {
        PyErr_SetString(
            PyExc_IOError,
            "failed to initialize FreeType library"
            );
        return NULL;
    }
    printf("2:%d\n", error);

    if (!PyArg_ParseTupleAndKeywords(args, kw, "eti|iss#", kwlist,
                                     Py_FileSystemDefaultEncoding, &filename,
                                     &size, &index, &encoding, &font_bytes,
                                     &font_bytes_size)) {
        return NULL;
    }

    printf("3:%d\n", error);
    self = PyObject_New(FontObject, &Font_Type);
    if (!self) {
        if (filename)
            PyMem_Free(filename);
        return NULL;
    }

    printf("4:%d\n", error);
    if (filename && font_bytes_size <= 0) {
        self->font_bytes = NULL;
        error = FT_New_Face(library, filename, index, &self->face);
        printf("4.1:%d\n, filename=%s, index=%d\n", error, filename, index);
    } else {
        /* need to have allocated storage for font_bytes for the life of the object.*/
        /* Don't free this before FT_Done_Face */
        self->font_bytes = PyMem_Malloc(font_bytes_size);
        if (!self->font_bytes) {
            error = 65; // Out of Memory in Freetype. 
        }
        if (!error) {
            memcpy(self->font_bytes, font_bytes, (size_t)font_bytes_size);
            error = FT_New_Memory_Face(library, (FT_Byte*)self->font_bytes, 
                                       font_bytes_size, index, &self->face);
        }
        printf("4.2:%d\n", error);
    }

    printf("5:%d\n", error);
    if (!error)
        error = FT_Set_Pixel_Sizes(self->face, 0, size);   # 定位到问题出在这里

    printf("6: error = %d, size = %d\n", error, size);
    if (!error && encoding && strlen((char*) encoding) == 4) {
        FT_Encoding encoding_tag = FT_MAKE_TAG(
            encoding[0], encoding[1], encoding[2], encoding[3]
            );
        error = FT_Select_Charmap(self->face, encoding_tag);
        printf("6.1:%d\n", error);
    }
    if (filename)
      PyMem_Free(filename);

    printf("7:%d\n", error);
    if (error) {
        if (self->font_bytes) {
            PyMem_Free(self->font_bytes);
        }  
        PyObject_Del(self);
        return geterror(error);
    }

    printf("8:%d\n", error);
    return (PyObject*) self;
}

 我在代码里面插入了一些调试信息,定位到问题发生在函数调用 FT_Set_Pixel_Sizes 里面,这是一个freetype函数的调用。难道是freetype不支持emoji字体?

继续追查freetype,我发现freetype是从2.5开始号称支持了color font,我用的是2.6,应该是没问题的。

我从https://gist.github.com/jokertarot/7583938找到一段支持color font的代码,仔细查看了使用freetype的前三步:

1、调用Init初始化库

2、构造FreeTypeFace对象,生成typeface

3、调用SetXXXFont,注意在这一步普通字体和Color字体是不一样的,普通字体调用了SetNormalFont,内部调用FT_Set_Pixel_Sizes;而Color字体调用SetColorFont,内部调用FT_Select_Size

再回过头来看Pillow-master/_imagingft.c的getfont函数,他是没有区分NormalFont和ColorFont的,一律按照NormalFont来处理了,所以出错。

这应该就是核心问题了:freetype支持color font,但在具体处理的时候需要使用不同的接口,在Pillow这一层没有考虑到这一点,因此是Pillow无法支持color font。要想解决问题,必须在Pillow-master/_imagingft.c的getfont里作出修改。

freetype那段支持color font的代码我还没试过,回头再专门研究一下。


原文地址:https://www.cnblogs.com/palance/p/4809872.html