Do not raise error if bitmap buffer is empty

This commit is contained in:
Andrew Murray 2024-08-23 18:24:04 +10:00
parent d6cfebd016
commit f9f7ba4ce9
2 changed files with 138 additions and 139 deletions

View File

@ -1,10 +1,8 @@
from __future__ import annotations from __future__ import annotations
import pytest
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from .helper import skip_unless_feature from .helper import skip_unless_feature_version
class TestFontCrash: class TestFontCrash:
@ -17,8 +15,7 @@ class TestFontCrash:
draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2) draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2)
draw.text((10, 10), "Test Text", font=font, fill="#000") draw.text((10, 10), "Test Text", font=font, fill="#000")
@skip_unless_feature("freetype2") @skip_unless_feature_version("freetype2", "2.12.0")
def test_segfault(self) -> None: def test_segfault(self) -> None:
with pytest.raises(OSError): font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784")
font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784") self._fuzz_font(font)
self._fuzz_font(font)

View File

@ -1025,130 +1025,99 @@ font_render(FontObject *self, PyObject *args) {
yy = -(py + glyph_slot->bitmap_top); yy = -(py + glyph_slot->bitmap_top);
} }
// Null buffer, is dereferenced in FT_Bitmap_Convert if (bitmap.buffer) {
if (!bitmap.buffer && bitmap.rows) { /* convert non-8bpp bitmaps */
PyErr_SetString(PyExc_OSError, "Bitmap missing for glyph"); switch (bitmap.pixel_mode) {
goto glyph_error; case FT_PIXEL_MODE_MONO:
} convert_scale = 255;
/* convert non-8bpp bitmaps */
switch (bitmap.pixel_mode) {
case FT_PIXEL_MODE_MONO:
convert_scale = 255;
break;
case FT_PIXEL_MODE_GRAY2:
convert_scale = 255 / 3;
break;
case FT_PIXEL_MODE_GRAY4:
convert_scale = 255 / 15;
break;
default:
convert_scale = 1;
}
switch (bitmap.pixel_mode) {
case FT_PIXEL_MODE_MONO:
case FT_PIXEL_MODE_GRAY2:
case FT_PIXEL_MODE_GRAY4:
if (!bitmap_converted_ready) {
FT_Bitmap_Init(&bitmap_converted);
bitmap_converted_ready = 1;
}
error = FT_Bitmap_Convert(library, &bitmap, &bitmap_converted, 1);
if (error) {
geterror(error);
goto glyph_error;
}
bitmap = bitmap_converted;
/* bitmap is now FT_PIXEL_MODE_GRAY, fall through */
case FT_PIXEL_MODE_GRAY:
break;
case FT_PIXEL_MODE_BGRA:
if (color) {
break; break;
} case FT_PIXEL_MODE_GRAY2:
/* we didn't ask for color, fall through to default */ convert_scale = 255 / 3;
default: break;
PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode"); case FT_PIXEL_MODE_GRAY4:
goto glyph_error; convert_scale = 255 / 15;
} break;
default:
/* clip glyph bitmap width to target image bounds */ convert_scale = 1;
x0 = 0; }
x1 = bitmap.width; switch (bitmap.pixel_mode) {
if (xx < 0) { case FT_PIXEL_MODE_MONO:
x0 = -xx; case FT_PIXEL_MODE_GRAY2:
} case FT_PIXEL_MODE_GRAY4:
if (xx + x1 > im->xsize) { if (!bitmap_converted_ready) {
x1 = im->xsize - xx; FT_Bitmap_Init(&bitmap_converted);
} bitmap_converted_ready = 1;
source = (unsigned char *)bitmap.buffer;
for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++, yy++) {
/* clip glyph bitmap height to target image bounds */
if (yy >= 0 && yy < im->ysize) {
/* blend this glyph into the buffer */
int k;
unsigned char *target;
unsigned int tmp;
if (color) {
/* target[RGB] returns the color, target[A] returns the mask */
/* target bands get split again in ImageDraw.text */
target = (unsigned char *)im->image[yy] + xx * 4;
} else {
target = im->image8[yy] + xx;
}
if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
/* paste color glyph */
for (k = x0; k < x1; k++) {
unsigned int src_alpha = source[k * 4 + 3];
/* paste only if source has data */
if (src_alpha > 0) {
/* unpremultiply BGRa */
int src_red =
CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha);
int src_green =
CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha);
int src_blue =
CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha);
/* blend required if target has data */
if (target[k * 4 + 3] > 0) {
/* blend RGBA colors */
target[k * 4 + 0] =
BLEND(src_alpha, target[k * 4 + 0], src_red, tmp);
target[k * 4 + 1] =
BLEND(src_alpha, target[k * 4 + 1], src_green, tmp);
target[k * 4 + 2] =
BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp);
target[k * 4 + 3] = CLIP8(
src_alpha +
MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)
);
} else {
/* paste unpremultiplied RGBA values */
target[k * 4 + 0] = src_red;
target[k * 4 + 1] = src_green;
target[k * 4 + 2] = src_blue;
target[k * 4 + 3] = src_alpha;
}
}
} }
} else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { error = FT_Bitmap_Convert(library, &bitmap, &bitmap_converted, 1);
if (error) {
geterror(error);
goto glyph_error;
}
bitmap = bitmap_converted;
/* bitmap is now FT_PIXEL_MODE_GRAY, fall through */
case FT_PIXEL_MODE_GRAY:
break;
case FT_PIXEL_MODE_BGRA:
if (color) { if (color) {
unsigned char *ink = (unsigned char *)&foreground_ink; break;
}
/* we didn't ask for color, fall through to default */
default:
PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode");
goto glyph_error;
}
/* clip glyph bitmap width to target image bounds */
x0 = 0;
x1 = bitmap.width;
if (xx < 0) {
x0 = -xx;
}
if (xx + x1 > im->xsize) {
x1 = im->xsize - xx;
}
source = (unsigned char *)bitmap.buffer;
for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++, yy++) {
/* clip glyph bitmap height to target image bounds */
if (yy >= 0 && yy < im->ysize) {
/* blend this glyph into the buffer */
int k;
unsigned char *target;
unsigned int tmp;
if (color) {
/* target[RGB] returns the color, target[A] returns the mask */
/* target bands get split again in ImageDraw.text */
target = (unsigned char *)im->image[yy] + xx * 4;
} else {
target = im->image8[yy] + xx;
}
if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
/* paste color glyph */
for (k = x0; k < x1; k++) { for (k = x0; k < x1; k++) {
unsigned int src_alpha = source[k] * convert_scale; unsigned int src_alpha = source[k * 4 + 3];
/* paste only if source has data */
if (src_alpha > 0) { if (src_alpha > 0) {
/* unpremultiply BGRa */
int src_red =
CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha);
int src_green =
CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha);
int src_blue =
CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha);
/* blend required if target has data */
if (target[k * 4 + 3] > 0) { if (target[k * 4 + 3] > 0) {
/* blend RGBA colors */
target[k * 4 + 0] = BLEND( target[k * 4 + 0] = BLEND(
src_alpha, target[k * 4 + 0], ink[0], tmp src_alpha, target[k * 4 + 0], src_red, tmp
); );
target[k * 4 + 1] = BLEND( target[k * 4 + 1] = BLEND(
src_alpha, target[k * 4 + 1], ink[1], tmp src_alpha, target[k * 4 + 1], src_green, tmp
); );
target[k * 4 + 2] = BLEND( target[k * 4 + 2] = BLEND(
src_alpha, target[k * 4 + 2], ink[2], tmp src_alpha, target[k * 4 + 2], src_blue, tmp
); );
target[k * 4 + 3] = CLIP8( target[k * 4 + 3] = CLIP8(
src_alpha + src_alpha +
@ -1157,35 +1126,68 @@ font_render(FontObject *self, PyObject *args) {
) )
); );
} else { } else {
target[k * 4 + 0] = ink[0]; /* paste unpremultiplied RGBA values */
target[k * 4 + 1] = ink[1]; target[k * 4 + 0] = src_red;
target[k * 4 + 2] = ink[2]; target[k * 4 + 1] = src_green;
target[k * 4 + 2] = src_blue;
target[k * 4 + 3] = src_alpha; target[k * 4 + 3] = src_alpha;
} }
} }
} }
} else { } else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
for (k = x0; k < x1; k++) { if (color) {
unsigned int src_alpha = source[k] * convert_scale; unsigned char *ink = (unsigned char *)&foreground_ink;
if (src_alpha > 0) { for (k = x0; k < x1; k++) {
target[k] = unsigned int src_alpha = source[k] * convert_scale;
target[k] > 0 if (src_alpha > 0) {
? CLIP8( if (target[k * 4 + 3] > 0) {
src_alpha + target[k * 4 + 0] = BLEND(
MULDIV255( src_alpha, target[k * 4 + 0], ink[0], tmp
target[k], (255 - src_alpha), tmp );
target[k * 4 + 1] = BLEND(
src_alpha, target[k * 4 + 1], ink[1], tmp
);
target[k * 4 + 2] = BLEND(
src_alpha, target[k * 4 + 2], ink[2], tmp
);
target[k * 4 + 3] = CLIP8(
src_alpha + MULDIV255(
target[k * 4 + 3],
(255 - src_alpha),
tmp
)
);
} else {
target[k * 4 + 0] = ink[0];
target[k * 4 + 1] = ink[1];
target[k * 4 + 2] = ink[2];
target[k * 4 + 3] = src_alpha;
}
}
}
} else {
for (k = x0; k < x1; k++) {
unsigned int src_alpha = source[k] * convert_scale;
if (src_alpha > 0) {
target[k] =
target[k] > 0
? CLIP8(
src_alpha +
MULDIV255(
target[k], (255 - src_alpha), tmp
)
) )
) : src_alpha;
: src_alpha; }
} }
} }
} else {
PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode");
goto glyph_error;
} }
} else {
PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode");
goto glyph_error;
} }
source += bitmap.pitch;
} }
source += bitmap.pitch;
} }
x += glyph_info[i].x_advance; x += glyph_info[i].x_advance;
y += glyph_info[i].y_advance; y += glyph_info[i].y_advance;