Fit to contents when image size omitted

Eliminates the assumption of any specific metrics from the pen. It still
gives some image without giving any parameters, thus it should be a good
starting point for new users.
This commit is contained in:
Takaaki Fuji 2022-01-11 23:00:38 +09:00
parent e3bbf39a28
commit 072b4c8db0
2 changed files with 53 additions and 22 deletions

View File

@ -148,12 +148,14 @@ class FreeTypePen(BasePen):
(ctypes.c_int)(flags) (ctypes.c_int)(flags)
) )
def buffer(self, width=1000, height=1000, transform=None, contain=False, evenOdd=False): def buffer(self, width=None, height=None, transform=None, contain=False, evenOdd=False):
"""Renders the current contours within a bitmap buffer. """Renders the current contours within a bitmap buffer.
Args: Args:
width: Image width of the bitmap in pixels. width: Image width of the bitmap in pixels. If omitted, it
height: Image height of the bitmap in pixels. automatically fits to the bounding box of the contours.
height: Image height of the bitmap in pixels. If omitted, it
automatically fits to the bounding box of the contours.
transform: A optional 6-tuple containing an affine transformation, transform: A optional 6-tuple containing an affine transformation,
or a ``Transform`` object from the ``fontTools.misc.transform`` or a ``Transform`` object from the ``fontTools.misc.transform``
module. The bitmap size is not affected by this matrix. module. The bitmap size is not affected by this matrix.
@ -166,7 +168,7 @@ class FreeTypePen(BasePen):
A tuple of ``(buffer, size)``, where ``buffer`` is a ``bytes`` A tuple of ``(buffer, size)``, where ``buffer`` is a ``bytes``
object of the resulted bitmap and ``size` is a 2-tuple of its object of the resulted bitmap and ``size` is a 2-tuple of its
dimension. dimension.
:Example: :Example:
>> pen = FreeTypePen(None) >> pen = FreeTypePen(None)
>> glyph.draw(pen) >> glyph.draw(pen)
@ -177,15 +179,20 @@ class FreeTypePen(BasePen):
transform = transform or Transform() transform = transform or Transform()
if not hasattr(transform, 'transformPoint'): if not hasattr(transform, 'transformPoint'):
transform = Transform(*transform) transform = Transform(*transform)
if contain: contain_x, contain_y = contain or width is None, contain or height is None
width, height = width or 0, height or 0
if contain_x or contain_y:
bbox = self.bbox bbox = self.bbox
bbox = transform.transformPoints((bbox[0:2], bbox[2:4])) bbox = transform.transformPoints((bbox[0:2], bbox[2:4]))
bbox = (*bbox[0], *bbox[1]) bbox = (*bbox[0], *bbox[1])
bbox_size = bbox[2] - bbox[0], bbox[3] - bbox[1] bbox_size = bbox[2] - bbox[0], bbox[3] - bbox[1]
dx = min(-transform.dx, bbox[0]) * -1.0 dx, dy = transform.dx, transform.dy
dy = min(-transform.dy, bbox[1]) * -1.0 if contain_x:
width = max(width, bbox_size[0]) dx = min(-dx, bbox[0]) * -1.0
height = max(height, bbox_size[1]) width = max(width, bbox_size[0])
if contain_y:
dy = min(-dy, bbox[1]) * -1.0
height = max(height, bbox_size[1])
transform = Transform(*transform[:4], dx, dy) transform = Transform(*transform[:4], dx, dy)
width, height = math.ceil(width), math.ceil(height) width, height = math.ceil(width), math.ceil(height)
buf = ctypes.create_string_buffer(width * height) buf = ctypes.create_string_buffer(width * height)
@ -205,12 +212,14 @@ class FreeTypePen(BasePen):
raise FT_Exception(err) raise FT_Exception(err)
return buf.raw, (width, height) return buf.raw, (width, height)
def array(self, width=1000, height=1000, transform=None, contain=False, evenOdd=False): def array(self, width=None, height=None, transform=None, contain=False, evenOdd=False):
"""Returns the rendered contours as a numpy array. Requires `numpy`. """Returns the rendered contours as a numpy array. Requires `numpy`.
Args: Args:
width: Image width of the bitmap in pixels. width: Image width of the bitmap in pixels. If omitted, it
height: Image height of the bitmap in pixels. automatically fits to the bounding box of the contours.
height: Image height of the bitmap in pixels. If omitted, it
automatically fits to the bounding box of the contours.
transform: A optional 6-tuple containing an affine transformation, transform: A optional 6-tuple containing an affine transformation,
or a ``Transform`` object from the ``fontTools.misc.transform`` or a ``Transform`` object from the ``fontTools.misc.transform``
module. The bitmap size is not affected by this matrix. module. The bitmap size is not affected by this matrix.
@ -222,7 +231,7 @@ class FreeTypePen(BasePen):
Returns: Returns:
A ``numpy.ndarray`` object with a shape of ``(height, width)``. A ``numpy.ndarray`` object with a shape of ``(height, width)``.
Each element takes a value in the range of ``[0.0, 1.0]``. Each element takes a value in the range of ``[0.0, 1.0]``.
:Example: :Example:
>> pen = FreeTypePen(None) >> pen = FreeTypePen(None)
>> glyph.draw(pen) >> glyph.draw(pen)
@ -234,13 +243,15 @@ class FreeTypePen(BasePen):
buf, size = self.buffer(width=width, height=height, transform=transform, contain=contain, evenOdd=evenOdd) buf, size = self.buffer(width=width, height=height, transform=transform, contain=contain, evenOdd=evenOdd)
return np.frombuffer(buf, 'B').reshape((size[1], size[0])) / 255.0 return np.frombuffer(buf, 'B').reshape((size[1], size[0])) / 255.0
def show(self, width=1000, height=1000, transform=None, contain=False, evenOdd=False): def show(self, width=None, height=None, transform=None, contain=False, evenOdd=False):
"""Plots the rendered contours with `pyplot`. Requires `numpy` and """Plots the rendered contours with `pyplot`. Requires `numpy` and
`matplotlib`. `matplotlib`.
Args: Args:
width: Image width of the bitmap in pixels. width: Image width of the bitmap in pixels. If omitted, it
height: Image height of the bitmap in pixels. automatically fits to the bounding box of the contours.
height: Image height of the bitmap in pixels. If omitted, it
automatically fits to the bounding box of the contours.
transform: A optional 6-tuple containing an affine transformation, transform: A optional 6-tuple containing an affine transformation,
or a ``Transform`` object from the ``fontTools.misc.transform`` or a ``Transform`` object from the ``fontTools.misc.transform``
module. The bitmap size is not affected by this matrix. module. The bitmap size is not affected by this matrix.
@ -248,7 +259,7 @@ class FreeTypePen(BasePen):
so that it fits to the bounding box of the paths. Useful for so that it fits to the bounding box of the paths. Useful for
rendering glyphs with negative sidebearings without clipping. rendering glyphs with negative sidebearings without clipping.
evenOdd: Pass ``True`` for even-odd fill instead of non-zero. evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
:Example: :Example:
>> pen = FreeTypePen(None) >> pen = FreeTypePen(None)
>> glyph.draw(pen) >> glyph.draw(pen)
@ -259,13 +270,15 @@ class FreeTypePen(BasePen):
plt.imshow(a, cmap='gray_r', vmin=0, vmax=1) plt.imshow(a, cmap='gray_r', vmin=0, vmax=1)
plt.show() plt.show()
def image(self, width=1000, height=1000, transform=None, contain=False, evenOdd=False): def image(self, width=None, height=None, transform=None, contain=False, evenOdd=False):
"""Returns the rendered contours as a PIL image. Requires `Pillow`. """Returns the rendered contours as a PIL image. Requires `Pillow`.
Can be used to display a glyph image in Jupyter Notebook. Can be used to display a glyph image in Jupyter Notebook.
Args: Args:
width: Image width of the bitmap in pixels. width: Image width of the bitmap in pixels. If omitted, it
height: Image height of the bitmap in pixels. automatically fits to the bounding box of the contours.
height: Image height of the bitmap in pixels. If omitted, it
automatically fits to the bounding box of the contours.
transform: A optional 6-tuple containing an affine transformation, transform: A optional 6-tuple containing an affine transformation,
or a ``Transform`` object from the ``fontTools.misc.transform`` or a ``Transform`` object from the ``fontTools.misc.transform``
module. The bitmap size is not affected by this matrix. module. The bitmap size is not affected by this matrix.
@ -277,7 +290,7 @@ class FreeTypePen(BasePen):
Returns: Returns:
A ``PIL.image`` object. The image is filled in black with alpha A ``PIL.image`` object. The image is filled in black with alpha
channel obtained from the rendered bitmap. channel obtained from the rendered bitmap.
:Example: :Example:
>> pen = FreeTypePen(None) >> pen = FreeTypePen(None)
>> glyph.draw(pen) >> glyph.draw(pen)

View File

@ -6,7 +6,7 @@ try:
except ImportError: except ImportError:
FREETYPE_PY_AVAILABLE = False FREETYPE_PY_AVAILABLE = False
from fontTools.misc.transform import Scale from fontTools.misc.transform import Scale, Offset
def draw_cubic(pen): def draw_cubic(pen):
pen.moveTo((50, 0)) pen.moveTo((50, 0))
@ -127,6 +127,24 @@ class FreeTypePenTest(unittest.TestCase):
self.assertEqual(len(buf1), len(buf2)) self.assertEqual(len(buf1), len(buf2))
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
def test_none_width(self):
pen = FreeTypePen(None)
star(pen)
width, height = None, 1000
buf1, size = pen.buffer(width=width, height=height, transform=Offset(0, 200))
buf2, _ = pen.buffer(width=1000, height=height, transform=Offset(0, 200))
self.assertEqual(size, (1000, 1000))
self.assertEqual(buf1, buf2)
def test_none_height(self):
pen = FreeTypePen(None)
star(pen)
width, height = 1000, None
buf1, size = pen.buffer(width=width, height=height)
buf2, _ = pen.buffer(width=width, height=1000, transform=Offset(0, 200))
self.assertEqual(size, (1000, 1000))
self.assertEqual(buf1, buf2)
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
sys.exit(unittest.main()) sys.exit(unittest.main())