From 2f99e8f45fb80ce3d49b55726da0d24bade3bbd1 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Mon, 23 Dec 2024 14:53:58 +0000 Subject: [PATCH] Crop glyphs --- README.rst | 22 +++++++++++++++------- Tools/rc/rclib/font.py | 15 +++++++++++++-- Tools/rc/rclib/freetype.py | 22 +--------------------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/README.rst b/README.rst index b9687c3..84f7a80 100644 --- a/README.rst +++ b/README.rst @@ -179,6 +179,9 @@ Resource script entries look like this: "": { "codepoints": "", // Which character glyphs to include. See below. "chars": "", // 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": "", "italic": "", "bold": "", @@ -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 @@ -228,12 +231,17 @@ 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": - Whether to produce monochrome (1-bit) or grayscale (8-bit alpha) glyphs. - If not specified, defaults to grayscale. - "size": - 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. + +The **alpha** parameter sets a limit on the number of bits per pixel used for the generate glyphs. +The freetype and VLW fonts are generated using 8 bits per pixel for smoother appearance. +This can be reduced to 4, 2 or 1 bit (monochrome) to reduce storage space. + +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. Images diff --git a/Tools/rc/rclib/font.py b/Tools/rc/rclib/font.py index dd69f3a..7a5ff05 100644 --- a/Tools/rc/rclib/font.py +++ b/Tools/rc/rclib/font.py @@ -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""" @@ -67,10 +67,21 @@ 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) + w, h = img.width, img.height + self.xOffset += bbox[0] + self.yOffset += bbox[1] + 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 diff --git a/Tools/rc/rclib/freetype.py b/Tools/rc/rclib/freetype.py index 9046601..5935df0 100644 --- a/Tools/rc/rclib/freetype.py +++ b/Tools/rc/rclib/freetype.py @@ -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)