From aa1f4cb42b91ee43e7884c41966b87fb4e0e7290 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 12 May 2025 12:46:32 +0200 Subject: [PATCH] Fix alpha compositing in ft2font's draw_bitmap. The old formula (`*dst |= *src`) works fine when either dst or src is full transparent or fully opaque, but not for compositing intermediate values. Fix that (while keeping a fast-path for the common case of writing on an empty buffer). Example (note the more uniform gray zone between the two letters): ``` from matplotlib import pyplot as plt, ft2font as f, cbook import numpy as np font = f.FT2Font(str(cbook._get_data_path("fonts/ttf/DejaVuSans.ttf"))) font.set_size(24, 72) im = f.FT2Image(30, 30) ga = font.load_char(ord("A")) gv = font.load_char(ord("V")) font.draw_glyph_to_bitmap(im, 2, 2, ga) font.draw_glyph_to_bitmap(im, 12, 2, gv) (plt.figure(layout="constrained", figsize=(3, 3)) .add_subplot(xticks=[], yticks=[]) .imshow(np.asarray(im), cmap="gray")) plt.show() ``` --- src/ft2font.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index b2c2c0fa9bd1..1f58aa593e09 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -73,11 +73,20 @@ FT2Image::~FT2Image() free(m_buffer); } -void draw_bitmap( - py::array_t im, FT_Bitmap *bitmap, FT_Int x, FT_Int y) +static std::array const alpha_cov_merge_table{[]() { + auto table = std::array{}; + for (auto dst = 0; dst < 0x100; ++dst) { + for (auto src = 0; src < 0x100; ++src) { + table[(dst << 8) + src] = dst + src - (dst * src + 0x7f) / 0xff; + } + } + return table; +} ()}; + +void +draw_bitmap(py::array_t im, FT_Bitmap *bitmap, FT_Int x, FT_Int y) { auto buf = im.mutable_data(0); - FT_Int image_width = (FT_Int)im.shape(1); FT_Int image_height = (FT_Int)im.shape(0); FT_Int char_width = bitmap->width; @@ -96,7 +105,8 @@ void draw_bitmap( unsigned char *dst = buf + (i * image_width + x1); unsigned char *src = bitmap->buffer + (((i - y_offset) * bitmap->pitch) + x_start); for (FT_Int j = x1; j < x2; ++j, ++dst, ++src) - *dst |= *src; + // Provide a fast-path for the common case of black background. + *dst = *dst ? alpha_cov_merge_table[(*dst << 8) + *src] : *src; } } else if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) { for (FT_Int i = y1; i < y2; ++i) {