freetypePen: handle rotate/skew transform
The pen is designed to determine the bitmap size when omitted, which helps users to see an image somehow even when they have no idea how the arguments should be passed. And I realised that I didn't give enough thought to rotate/skew transforms in former PRs. This commit fixes the calculation of the bbox after transformation. Also tries to clear up how the autosizing options work in the docstring. Some minor fixes will follow.
This commit is contained in:
parent
69fc06a1af
commit
a27789c477
@ -114,7 +114,7 @@ class FreeTypePen(BasePen):
|
||||
"""Converts the current contours to ``FT_Outline``.
|
||||
|
||||
Args:
|
||||
transform: A optional 6-tuple containing an affine transformation,
|
||||
transform: An optional 6-tuple containing an affine transformation,
|
||||
or a ``Transform`` object from the ``fontTools.misc.transform``
|
||||
module.
|
||||
evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
|
||||
@ -156,7 +156,7 @@ class FreeTypePen(BasePen):
|
||||
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: An optional 6-tuple containing an affine transformation,
|
||||
or a ``Transform`` object from the ``fontTools.misc.transform``
|
||||
module. The bitmap size is not affected by this matrix.
|
||||
contain: If ``True``, the image size will be automatically expanded
|
||||
@ -169,6 +169,16 @@ class FreeTypePen(BasePen):
|
||||
object of the resulted bitmap and ``size`` is a 2-tuple of its
|
||||
dimension.
|
||||
|
||||
:Notes:
|
||||
The image size should always be given explicitly if you need to get
|
||||
a proper glyph image. When ``width`` and ``height`` are omitted, it
|
||||
forcifully fits to the bounding box and the side bearings get
|
||||
cropped. If you pass ``0`` to both ``width`` and ``height`` and set
|
||||
``contain`` to ``True``, it expands to the bounding box while
|
||||
maintaining the origin of the contours, meaning that LSB will be
|
||||
maintained but RSB won’t. The difference between the two becomes
|
||||
more obvious when rotate or skew transformation is applied.
|
||||
|
||||
:Example:
|
||||
.. code-block::
|
||||
|
||||
@ -183,19 +193,25 @@ class FreeTypePen(BasePen):
|
||||
if not hasattr(transform, 'transformPoint'):
|
||||
transform = Transform(*transform)
|
||||
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 = transform.transformPoints((bbox[0:2], bbox[2:4]))
|
||||
bbox = (*bbox[0], *bbox[1])
|
||||
bbox_size = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||
dx, dy = transform.dx, transform.dy
|
||||
bbox = self.bbox
|
||||
p1, p2, p3, p4 = transform.transformPoint((bbox[0], bbox[1])), transform.transformPoint((bbox[2], bbox[1])), transform.transformPoint((bbox[0], bbox[3])), transform.transformPoint((bbox[2], bbox[3]))
|
||||
px, py = (p1[0], p2[0], p3[0], p4[0]), (p1[1], p2[1], p3[1], p4[1])
|
||||
if contain_x:
|
||||
dx = min(-dx, bbox[0]) * -1.0
|
||||
width = max(width, bbox_size[0])
|
||||
if width is None:
|
||||
dx = dx - min(*px)
|
||||
width = max(*px) - min(*px)
|
||||
else:
|
||||
dx = dx - min(min(*px), 0.0)
|
||||
width = max(width, max(*px) - min(min(*px), 0.0))
|
||||
if contain_y:
|
||||
dy = min(-dy, bbox[1]) * -1.0
|
||||
height = max(height, bbox_size[1])
|
||||
if height is None:
|
||||
dy = dy - min(*py)
|
||||
height = max(*py) - min(*py)
|
||||
else:
|
||||
dy = dy - min(min(*py), 0.0)
|
||||
height = max(height, max(*py) - min(min(*py), 0.0))
|
||||
transform = Transform(*transform[:4], dx, dy)
|
||||
width, height = math.ceil(width), math.ceil(height)
|
||||
buf = ctypes.create_string_buffer(width * height)
|
||||
@ -223,7 +239,7 @@ class FreeTypePen(BasePen):
|
||||
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: An optional 6-tuple containing an affine transformation,
|
||||
or a ``Transform`` object from the ``fontTools.misc.transform``
|
||||
module. The bitmap size is not affected by this matrix.
|
||||
contain: If ``True``, the image size will be automatically expanded
|
||||
@ -235,6 +251,16 @@ class FreeTypePen(BasePen):
|
||||
A ``numpy.ndarray`` object with a shape of ``(height, width)``.
|
||||
Each element takes a value in the range of ``[0.0, 1.0]``.
|
||||
|
||||
:Notes:
|
||||
The image size should always be given explicitly if you need to get
|
||||
a proper glyph image. When ``width`` and ``height`` are omitted, it
|
||||
forcifully fits to the bounding box and the side bearings get
|
||||
cropped. If you pass ``0`` to both ``width`` and ``height`` and set
|
||||
``contain`` to ``True``, it expands to the bounding box while
|
||||
maintaining the origin of the contours, meaning that LSB will be
|
||||
maintained but RSB won’t. The difference between the two becomes
|
||||
more obvious when rotate or skew transformation is applied.
|
||||
|
||||
:Example:
|
||||
.. code-block::
|
||||
|
||||
@ -257,7 +283,7 @@ class FreeTypePen(BasePen):
|
||||
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: An optional 6-tuple containing an affine transformation,
|
||||
or a ``Transform`` object from the ``fontTools.misc.transform``
|
||||
module. The bitmap size is not affected by this matrix.
|
||||
contain: If ``True``, the image size will be automatically expanded
|
||||
@ -265,6 +291,16 @@ class FreeTypePen(BasePen):
|
||||
rendering glyphs with negative sidebearings without clipping.
|
||||
evenOdd: Pass ``True`` for even-odd fill instead of non-zero.
|
||||
|
||||
:Notes:
|
||||
The image size should always be given explicitly if you need to get
|
||||
a proper glyph image. When ``width`` and ``height`` are omitted, it
|
||||
forcifully fits to the bounding box and the side bearings get
|
||||
cropped. If you pass ``0`` to both ``width`` and ``height`` and set
|
||||
``contain`` to ``True``, it expands to the bounding box while
|
||||
maintaining the origin of the contours, meaning that LSB will be
|
||||
maintained but RSB won’t. The difference between the two becomes
|
||||
more obvious when rotate or skew transformation is applied.
|
||||
|
||||
:Example:
|
||||
.. code-block::
|
||||
|
||||
@ -286,7 +322,7 @@ class FreeTypePen(BasePen):
|
||||
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: An optional 6-tuple containing an affine transformation,
|
||||
or a ``Transform`` object from the ``fontTools.misc.transform``
|
||||
module. The bitmap size is not affected by this matrix.
|
||||
contain: If ``True``, the image size will be automatically expanded
|
||||
@ -298,6 +334,16 @@ class FreeTypePen(BasePen):
|
||||
A ``PIL.image`` object. The image is filled in black with alpha
|
||||
channel obtained from the rendered bitmap.
|
||||
|
||||
:Notes:
|
||||
The image size should always be given explicitly if you need to get
|
||||
a proper glyph image. When ``width`` and ``height`` are omitted, it
|
||||
forcifully fits to the bounding box and the side bearings get
|
||||
cropped. If you pass ``0`` to both ``width`` and ``height`` and set
|
||||
``contain`` to ``True``, it expands to the bounding box while
|
||||
maintaining the origin of the contours, meaning that LSB will be
|
||||
maintained but RSB won’t. The difference between the two becomes
|
||||
more obvious when rotate or skew transformation is applied.
|
||||
|
||||
:Example:
|
||||
.. code-block::
|
||||
|
||||
|
BIN
Tests/pens/data/test_rotate.pgm
Normal file
BIN
Tests/pens/data/test_rotate.pgm
Normal file
Binary file not shown.
BIN
Tests/pens/data/test_skew.pgm
Normal file
BIN
Tests/pens/data/test_skew.pgm
Normal file
Binary file not shown.
@ -1,5 +1,6 @@
|
||||
import unittest
|
||||
import os
|
||||
import math
|
||||
|
||||
try:
|
||||
from fontTools.pens.freetypePen import FreeTypePen
|
||||
@ -11,11 +12,11 @@ from fontTools.misc.transform import Scale, Offset
|
||||
|
||||
DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'data')
|
||||
|
||||
def box(pen):
|
||||
pen.moveTo((0, 0))
|
||||
pen.lineTo((0, 500))
|
||||
pen.lineTo((500, 500))
|
||||
pen.lineTo((500, 0))
|
||||
def box(pen, offset=(0, 0)):
|
||||
pen.moveTo((0 + offset[0], 0 + offset[1]))
|
||||
pen.lineTo((0 + offset[0], 500 + offset[1]))
|
||||
pen.lineTo((500 + offset[0], 500 + offset[1]))
|
||||
pen.lineTo((500 + offset[0], 0 + offset[1]))
|
||||
pen.closePath()
|
||||
|
||||
def draw_cubic(pen):
|
||||
@ -136,24 +137,78 @@ class FreeTypePenTest(unittest.TestCase):
|
||||
self.assertEqual(size1, size2)
|
||||
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
|
||||
|
||||
def test_none_width(self):
|
||||
def test_rotate(self):
|
||||
pen = FreeTypePen(None)
|
||||
box(pen)
|
||||
t = Scale(0.05, 0.05).rotate(math.pi / 4.0).translate(1234, 5678)
|
||||
width, height = None, None
|
||||
buf1, size1 = pen.buffer(width=width, height=height, transform=t)
|
||||
buf2, size2 = load_pgm(os.path.join(DATA_DIR, 'test_rotate.pgm'))
|
||||
self.assertEqual(len(buf1), len(buf2))
|
||||
self.assertEqual(size1, size2)
|
||||
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
|
||||
|
||||
def test_skew(self):
|
||||
pen = FreeTypePen(None)
|
||||
box(pen)
|
||||
t = Scale(0.05, 0.05).skew(math.pi / 4.0).translate(1234, 5678)
|
||||
width, height = None, None
|
||||
buf1, size1 = pen.buffer(width=width, height=height, transform=t)
|
||||
buf2, size2 = load_pgm(os.path.join(DATA_DIR, 'test_skew.pgm'))
|
||||
self.assertEqual(len(buf1), len(buf2))
|
||||
self.assertEqual(size1, size2)
|
||||
self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD)
|
||||
|
||||
def test_none_size(self):
|
||||
pen = FreeTypePen(None)
|
||||
star(pen)
|
||||
width, height = None, 1000
|
||||
width, height = None, None
|
||||
buf1, size = pen.buffer(width=width, height=height, transform=Offset(0, 200))
|
||||
buf2, _ = pen.buffer(width=1000, height=height, transform=Offset(0, 200))
|
||||
buf2, _ = pen.buffer(width=1000, height=1000, transform=Offset(0, 200))
|
||||
self.assertEqual(size, (1000, 1000))
|
||||
self.assertEqual(buf1, buf2)
|
||||
|
||||
def test_none_height(self):
|
||||
pen = FreeTypePen(None)
|
||||
box(pen, offset=(250, 250))
|
||||
width, height = None, None
|
||||
buf1, size = pen.buffer(width=width, height=height)
|
||||
buf2, _ = pen.buffer(width=500, height=500, transform=Offset(-250, -250))
|
||||
self.assertEqual(size, (500, 500))
|
||||
self.assertEqual(buf1, buf2)
|
||||
|
||||
pen = FreeTypePen(None)
|
||||
box(pen, offset=(-1234, -5678))
|
||||
width, height = None, None
|
||||
buf1, size = pen.buffer(width=width, height=height)
|
||||
buf2, _ = pen.buffer(width=500, height=500, transform=Offset(1234, 5678))
|
||||
self.assertEqual(size, (500, 500))
|
||||
self.assertEqual(buf1, buf2)
|
||||
|
||||
def test_zero_size(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))
|
||||
width, height = 0, 0
|
||||
buf1, size = pen.buffer(width=width, height=height, transform=Offset(0, 200), contain=True)
|
||||
buf2, _ = pen.buffer(width=1000, height=1000, transform=Offset(0, 200), contain=True)
|
||||
self.assertEqual(size, (1000, 1000))
|
||||
self.assertEqual(buf1, buf2)
|
||||
|
||||
pen = FreeTypePen(None)
|
||||
box(pen, offset=(250, 250))
|
||||
width, height = 0, 0
|
||||
buf1, size = pen.buffer(width=width, height=height, contain=True)
|
||||
buf2, _ = pen.buffer(width=500, height=500, transform=Offset(0, 0), contain=True)
|
||||
self.assertEqual(size, (750, 750))
|
||||
self.assertEqual(buf1, buf2)
|
||||
|
||||
pen = FreeTypePen(None)
|
||||
box(pen, offset=(-1234, -5678))
|
||||
width, height = 0, 0
|
||||
buf1, size = pen.buffer(width=width, height=height, contain=True)
|
||||
buf2, _ = pen.buffer(width=500, height=500, transform=Offset(1234, 5678), contain=True)
|
||||
self.assertEqual(size, (500, 500))
|
||||
self.assertEqual(buf1, buf2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(unittest.main())
|
||||
|
Loading…
x
Reference in New Issue
Block a user