From 9151da162c4c85d8f03052af7e003d8fbe9e4b3a Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 29 Mar 2020 10:31:10 +0200 Subject: [PATCH] add support for CBDT and embedded bitmaps in truetype fonts --- .github/workflows/test-windows.yml | 4 ++ Tests/test_imagefont_bitmap.py | 1 + docs/reference/ImageDraw.rst | 4 +- src/_imagingft.c | 102 +++++++++++++++++------------ winbuild/build_prepare.py | 20 +++++- 5 files changed, 86 insertions(+), 45 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index a6e85b688..7b3bc20aa 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -105,6 +105,10 @@ jobs: - name: Build dependencies / WebP if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libwebp.cmd" + # for FreeType CBDT font support + - name: Build dependencies / libpng + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_libpng.cmd" - name: Build dependencies / FreeType if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_freetype.cmd" diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py index 0ba682885..07a1f46cc 100644 --- a/Tests/test_imagefont_bitmap.py +++ b/Tests/test_imagefont_bitmap.py @@ -26,6 +26,7 @@ def test_similar(): im_outline = im_bitmap.copy() draw_bitmap = ImageDraw.Draw(im_bitmap) draw_outline = ImageDraw.Draw(im_outline) + draw_outline.fontmode = "1" # disable anti-aliasing to match bitmap font # Metrics are different on the bitmap and TTF fonts, # more so on some platforms and versions of FreeType than others. diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index d1b91d00a..789641223 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -352,7 +352,7 @@ Methods .. versionadded:: 6.2.0 - :param embedded_color: Whether to use embedded color info in COLR and CPAL tables. + :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). .. versionadded:: 8.0.0 @@ -413,7 +413,7 @@ Methods .. versionadded:: 6.2.0 - :param embedded_color: Whether to use embedded color info in COLR and CPAL tables. + :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). .. versionadded:: 8.0.0 diff --git a/src/_imagingft.c b/src/_imagingft.c index 850371488..d91cbccb6 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -564,7 +564,7 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje return 0; } - load_flags = FT_LOAD_NO_BITMAP; + load_flags = FT_LOAD_DEFAULT; if (mask) { load_flags |= FT_LOAD_TARGET_MONO; } @@ -663,10 +663,7 @@ font_getsize(FontObject* self, PyObject* args) return NULL; } - /* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 - * Yifu Yu, 2014-10-15 - */ - load_flags = FT_LOAD_NO_BITMAP; + load_flags = FT_LOAD_DEFAULT; if (mask) { load_flags |= FT_LOAD_TARGET_MONO; } @@ -903,9 +900,7 @@ font_render(FontObject* self, PyObject* args) } im = (Imaging) id; - - /* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */ - load_flags = FT_LOAD_NO_BITMAP; + load_flags = FT_LOAD_DEFAULT; if (mask) { load_flags |= FT_LOAD_TARGET_MONO; } @@ -1000,28 +995,33 @@ font_render(FontObject* self, PyObject* args) /* clip glyph bitmap height to target image bounds */ if (yy >= 0 && yy < im->ysize) { // blend this glyph into the buffer + unsigned char* target; if (color) { /* target[RGB] returns the color, target[A] returns the mask */ /* target bands get split again in ImageDraw.text */ - unsigned char *target = im->image[yy] + xx * 4; + target = im->image[yy] + xx * 4; + } else { + target = im->image8[yy] + xx; + } #ifdef FT_LOAD_COLOR - if (bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { - // paste color glyph - int k; - for (k = x0; k < x1; k++) { - if (target[k * 4 + 3] < source[k * 4 + 3]) { - /* unpremultiply BGRa to RGBA */ - target[k * 4 + 0] = CLIP8((255 * (int)source[k * 4 + 2]) / source[k * 4 + 3]); - target[k * 4 + 1] = CLIP8((255 * (int)source[k * 4 + 1]) / source[k * 4 + 3]); - target[k * 4 + 2] = CLIP8((255 * (int)source[k * 4 + 0]) / source[k * 4 + 3]); - target[k * 4 + 3] = source[k * 4 + 3]; - } + if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + // paste color glyph + int k; + for (k = x0; k < x1; k++) { + if (target[k * 4 + 3] < source[k * 4 + 3]) { + /* unpremultiply BGRa to RGBA */ + target[k * 4 + 0] = CLIP8((255 * (int)source[k * 4 + 2]) / source[k * 4 + 3]); + target[k * 4 + 1] = CLIP8((255 * (int)source[k * 4 + 1]) / source[k * 4 + 3]); + target[k * 4 + 2] = CLIP8((255 * (int)source[k * 4 + 0]) / source[k * 4 + 3]); + target[k * 4 + 3] = source[k * 4 + 3]; } - } else + } + } else #endif - { // pixel_mode should be FT_PIXEL_MODE_GRAY - // fill with ink - int k; + // handle 8bpp separately for performance + if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { + int k; + if (color) { for (k = x0; k < x1; k++) { if (target[k * 4 + 3] < source[k]) { target[k * 4 + 0] = (unsigned char) (foreground_ink); @@ -1030,30 +1030,50 @@ font_render(FontObject* self, PyObject* args) target[k * 4 + 3] = source[k]; } } - } - } else { - unsigned char *target = im->image8[yy] + xx; - if (mask) { - // use monochrome mask (on palette images, etc) - int j, k, m = 128; - for (j = k = 0; j < x1; j++) { - if (j >= x0 && (source[k] & m)) { - target[j] = 255; - } - if (!(m >>= 1)) { - m = 128; - k++; - } - } } else { - // use antialiased rendering - int k; for (k = x0; k < x1; k++) { if (target[k] < source[k]) { target[k] = source[k]; } } } + } else { + int k, v, m, a, b; + switch (bitmap.pixel_mode) { + case FT_PIXEL_MODE_MONO: + a = 3; + b = 7; + m = 0x80; + break; + case FT_PIXEL_MODE_GRAY2: + a = 2; + b = 3; + m = 0xC0; + break; + case FT_PIXEL_MODE_GRAY4: + a = 1; + b = 1; + m = 0xF0; + break; + default: + PyErr_SetString(PyExc_IOError, "unsupported bitmap pixel mode"); + return NULL; + } + for (k = x0; k < x1; k++) { + v = CLIP8(255 * ((source[k >> a] << (k & b)) & m) / m); + if (color) { + if (target[k * 4 + 3] < v) { + target[k * 4 + 0] = (unsigned char) foreground_ink; + target[k * 4 + 1] = (unsigned char) (foreground_ink >> 8); + target[k * 4 + 2] = (unsigned char) (foreground_ink >> 16); + target[k * 4 + 3] = v; + } + } else { + if (target[k] < v) { + target[k] = v; + } + } + } } } source += bitmap.pitch; diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index d66a78b07..0239131eb 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -169,6 +169,20 @@ deps = { ], "libs": [r"output\release-static\{architecture}\lib\*.lib"], }, + "libpng": { + "url": SF_MIRROR + "/project/libpng/libpng16/1.6.37/lpng1637.zip", + "filename": "lpng1637.zip", + "dir": "lpng1637", + "build": [ + # lint: do not inline + cmd_cmake(("-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF")), + cmd_nmake(target="clean"), + cmd_nmake(), + cmd_copy("libpng16_static.lib", "libpng16.lib"), + ], + "headers": [r"png*.h"], + "libs": [r"libpng16.lib"], + }, "freetype": { "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.10.2.tar.gz", # noqa: E501 "filename": "freetype-2.10.2.tar.gz", @@ -181,8 +195,10 @@ deps = { '': '\n $(WindowsSDKVersion)', # noqa: E501 }, r"builds\windows\vc2010\freetype.user.props": { - "": "FT_CONFIG_OPTION_USE_HARFBUZZ", # noqa: E501 - "": r"{dir_harfbuzz}\src", # noqa: E501 + "": "FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ", # noqa: E501 + "": r"{dir_harfbuzz}\src;{inc_dir}", # noqa: E501 + "": "{lib_dir}", # noqa: E501 + "": "zlib.lib;libpng16.lib", # noqa: E501 }, r"src/autofit/afshaper.c": { # link against harfbuzz.lib once it becomes available