Skip to content

Commit

Permalink
Crop glyphs
Browse files Browse the repository at this point in the history
  • Loading branch information
mikee47 committed Dec 24, 2024
1 parent fe15ca5 commit 324c6c1
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 30 deletions.
28 changes: 21 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ Resource script entries look like this:
"<name>": {
"codepoints": "<filter>", // Which character glyphs to include. See below.
"chars": "<text string>", // List of required character codepoints
"alpha", // Optional: maximum number of bits per pixel (1, 2, 4 or 8). Default is 8.
"mono", // Optional: Set to *true* to use 1 bit per pixel. Default is *false*.
"size", // Optional parameter for freetype fonts
"normal": "<filename>",
"italic": "<filename>",
"bold": "<filename>",
Expand All @@ -188,7 +191,7 @@ Resource script entries look like this:
Styles are optional but a font must have at least one typeface.

By default, all ASCII characters from 0x20 (space) to 0x7e (~).
By default, all ASCII characters from 0x20 (space) to 0x7e (~) are generated.
The ``codepoints`` parameter is a comma-separated list of ranges:

a-z,A-Z,0-9,0x4000-0x4050
Expand All @@ -197,6 +200,14 @@ This overrides the default and includes only characters and digits, plus unicode
The ``chars`` parameter is a simple list of characters, e.g. "Include these chars".
Both lists are combined, de-duplicated and sorted in ascending order.

The **alpha** parameter sets a limit on the number of bits per pixel used for the generated glyphs.
This cannot be used to convert monochrome fonts to grayscale, only to reduce grayscale resolution.

The **mono** parameter is equivalent to **alpha: 1**, and is ignored if **alpha** is specified.

Generated glyphs are cropped to remove any blank space around the image, then packed to minimise memory usage.
This means that the bitmap data is contiguous, without breaks, from top left to bottom right.

The following font classes are currently supported:

GFX
Expand All @@ -215,6 +226,10 @@ The following font classes are currently supported:
These are from the `TFT_eSPI <https://github.com/Bodmer/TFT_eSPI>`__ library.
See ``resource/fonts/VLW``.

Glyphs are defined using 8 bits per pixel, so have a default **alpha** of 8.
Changing the alpha to 4 or 2 reduces the glyph size (by 50% or 75%) in many cases without
affecting perceived quality. Conversion to monochrome (alpha 1) looks terrible.

Note that TTF/OTF scalable vector fonts are supported directly by this library
so is the preferred format for new fonts.
freetype
Expand All @@ -228,12 +243,11 @@ The following font classes are currently supported:

The ``freetype`` library supports other types so if required these are easily added.

These fonts have some additional parameters:
"mono": <True/False>
Whether to produce monochrome (1-bit) or grayscale (8-bit alpha) glyphs.
If not specified, defaults to grayscale.
"size": <Point size of font>
e.g. 16, 14.5
An optional **size** parameter indicates the point size for the font to generate.
If omitted, the first typeface defined for that font is used.

High quality glyphs of any size can be produced, by default with **alpha** of 8.
TrueType and OpenType fonts can produce good monochrome glyphs, good for larger sizes.


Images
Expand Down
18 changes: 16 additions & 2 deletions Tools/rc/rclib/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import sys
import struct
from .base import Resource, findFile, StructSize, fstrSize
from PIL import Image
from PIL import Image, ImageChops

class FontStyle(enum.Enum):
"""Style is a set of these values, using strings here but bitfields in library"""
Expand Down Expand Up @@ -67,10 +67,24 @@ def set_bitmap(self, img: Image):
""" Convert bitmap to internal (GFX) format and set width, height.
We remove space round the glyph, update glyph offsets and pack the resulting bits.
"""

# Find smallest bounding box and crop
w, h = img.width, img.height
bg = Image.new('L', (w, h), 0)
diff = ImageChops.difference(img, bg)
diff = ImageChops.add(diff, diff, 2.0, -100)
bbox = diff.getbbox()

if bbox:
img = img.crop(bbox)
self.xOffset += bbox[0]
self.yOffset += bbox[1]
else:
img = img.crop((0,0,0,0))
w, h = img.width, img.height

self.width = w
self.height = h
stride = w # All modes 1 byte per pixel
alpha = 1 if img.mode == '1' else self.typeface.font.alpha
self.alpha = alpha
pixels_per_byte = 8 // alpha
Expand Down
22 changes: 1 addition & 21 deletions Tools/rc/rclib/freetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,13 @@
import array
import freetype
from .font import Glyph, Typeface
from PIL import Image, ImageChops
from io import BytesIO
from PIL import Image


def pointsToPixels(points26):
return round(points26 / 64)


def print_bitmap_diff(bitmap: freetype.Bitmap):
if bitmap.width + bitmap.rows == 0:
return
mode = '1' if bitmap.pixel_mode == freetype.FT_PIXEL_MODE_MONO else 'L'
img = Image.frombuffer(mode, (bitmap.width, bitmap.rows), bytearray(bitmap.buffer))
bg = Image.new(mode, (bitmap.width, bitmap.rows), 0)
diff = ImageChops.difference(img, bg)
diff = ImageChops.add(diff, diff, 2.0, -100)
bbox = diff.getbbox()
if bbox is None:
return
w, h = bbox[2] - bbox[0], bbox[3] - bbox[1]
size_orig = bitmap.width * bitmap.rows
size_new = w * h
pix_diff = size_orig - size_new
if pix_diff:
print(f'glyph = ({bitmap.width}, {bitmap.rows}), bbox = ({w}, {h}), pix. diff {pix_diff} ({size_orig} -> {size_new})')


def parse_typeface(typeface: Typeface):
face = freetype.Face(typeface.source)

Expand Down

0 comments on commit 324c6c1

Please sign in to comment.