Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[background image] [designspace] Convert JPEG to PNG with Pillow as part of putBackgroundImage() #1810

Merged
merged 4 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies = [
"ufomerge>=1.8.0",
"unicodedata2>=15.1.0",
"skia-pathops>=0.8.0.post1",
"pillow>=11.0.0",
]
dynamic = ["version"]
requires-python = ">=3.10"
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ aiohttp==3.11.2
cattrs==24.1.2
fonttools[ufo,unicode]==4.55.0
glyphsLib==6.9.5
pillow==11.0.0
pyyaml==6.0.2
ufomerge==1.8.2
unicodedata2==15.1.0
Expand Down
20 changes: 19 additions & 1 deletion src/fontra/backends/designspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -1188,7 +1188,7 @@ async def getBackgroundImage(self, imageIdentifier: str) -> ImageData | None:

async def putBackgroundImage(self, imageIdentifier: str, data: ImageData) -> None:
if data.type != ImageType.PNG:
raise NotImplementedError("convert image to PNG")
data = convertImageData(data, ImageType.PNG)

imageInfo = self._imageMapping.reverse.get(imageIdentifier)
if imageInfo is None:
Expand Down Expand Up @@ -2240,3 +2240,21 @@ def mergeKernGroups(
mergedGroups[groupName] = gA + [n for n in gB if n not in gASet]

return mergedGroups


def convertImageData(data, type):
import io

from PIL import Image

image = Image.open(io.BytesIO(data.data))
if image.mode == "RGBA" and type == ImageType.JPEG:
# from https://stackoverflow.com/questions/9166400/convert-rgba-png-to-rgb-with-pil
image.load() # required for image.split()
imageJPEG = Image.new("RGB", image.size, (255, 255, 255))
imageJPEG.paste(image, mask=image.split()[3]) # 3 is the alpha channel
image = imageJPEG

outFile = io.BytesIO()
image.save(outFile, type)
return ImageData(type=type, data=outFile.getvalue())
31 changes: 30 additions & 1 deletion test-py/test_backends_designspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@

from fontra.backends import getFileSystemBackend, newFileSystemBackend
from fontra.backends.copy import copyFont
from fontra.backends.designspace import DesignspaceBackend, UFOBackend
from fontra.backends.designspace import DesignspaceBackend, UFOBackend, convertImageData
from fontra.backends.null import NullBackend
from fontra.core.classes import (
Anchor,
Axes,
BackgroundImage,
CrossAxisMapping,
FontAxis,
FontInfo,
FontSource,
GlyphAxis,
GlyphSource,
Guideline,
ImageType,
Layer,
LineMetric,
OpenTypeFeatures,
Expand Down Expand Up @@ -431,6 +433,33 @@ async def test_putGlyph_with_backgroundImage_new_font(testFont, tmpdir):
assert 0, "expected backgroundImage"


async def test_putBackgroundImage_JPEG(writableTestFont):
glyph = await writableTestFont.getGlyph("C")
for layerName, layer in glyph.layers.items():
bgImage = layer.glyph.backgroundImage
if bgImage is not None:
break

imageDataPNG = await writableTestFont.getBackgroundImage(bgImage.identifier)
imageDataJPEG = convertImageData(imageDataPNG, ImageType.JPEG)
assert imageDataJPEG.type == ImageType.JPEG

glyphName = "D"
imageIdentifier = str(uuid.uuid4())
bgImage = BackgroundImage(identifier=imageIdentifier)

glyphD = await writableTestFont.getGlyph(glyphName)
firstLayerName = list(glyphD.layers.keys())[0]
layer = glyphD.layers[firstLayerName]
layer.glyph.backgroundImage = bgImage

await writableTestFont.putGlyph(glyphName, glyphD, [ord("D")])
await writableTestFont.putBackgroundImage(imageIdentifier, imageDataJPEG)

imageRoundtripped = await writableTestFont.getBackgroundImage(imageIdentifier)
assert imageRoundtripped.type == ImageType.PNG


async def test_putAxes(writableTestFont):
axes = await writableTestFont.getAxes()
axes.axes.append(
Expand Down