Merge branch 'main' into progress

This commit is contained in:
Andrew Murray 2023-12-27 12:50:28 +11:00 committed by GitHub
commit 4a0a01195d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 249 additions and 170 deletions

6
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,6 @@
# Flake8
8de95676e0fd89f2326b3953488ab66ff29cd2d0
# Format with Black
53a7e3500437a9fd5826bc04758f7116bd7e52dc
# Format the C code with ClangFormat
46b7e86bab79450ec0a2866c6c0c679afb659d17

View File

@ -75,9 +75,9 @@ jobs:
CIBW_TEST_SKIP: "*-macosx_arm64" CIBW_TEST_SKIP: "*-macosx_arm64"
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: dist name: dist-${{ matrix.os }}-${{ matrix.archs }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
path: ./wheelhouse/*.whl path: ./wheelhouse/*.whl
windows: windows:
@ -116,10 +116,7 @@ jobs:
& python.exe -m pip install -r .ci/requirements-cibw.txt & python.exe -m pip install -r .ci/requirements-cibw.txt
# Cannot cross-compile FriBiDi (only used for tests) & python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.arch }}
$FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}")
if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" }
& python.exe winbuild\build_prepare.py -v @FLAGS
shell: pwsh shell: pwsh
- name: Build wheels - name: Build wheels
@ -157,24 +154,16 @@ jobs:
shell: cmd shell: cmd
- name: Upload wheels - name: Upload wheels
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: dist name: dist-windows-${{ matrix.arch }}
path: ./wheelhouse/*.whl path: ./wheelhouse/*.whl
- name: Prepare to upload FriBiDi
if: "matrix.arch != 'ARM64'"
run: |
mkdir fribidi\${{ matrix.arch }}
copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }}
shell: cmd
- name: Upload fribidi.dll - name: Upload fribidi.dll
if: "matrix.arch != 'ARM64'" uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with: with:
name: fribidi name: fribidi-windows-${{ matrix.arch }}
path: fribidi\* path: winbuild\build\bin\fribidi*
sdist: sdist:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -190,17 +179,26 @@ jobs:
- run: make sdist - run: make sdist
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: dist name: dist-sdist
path: dist/*.tar.gz path: dist/*.tar.gz
success: pypi-publish:
permissions: if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
contents: none
needs: [build, windows, sdist] needs: [build, windows, sdist]
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Wheels Successful name: Upload release to PyPI
environment:
name: release-pypi
url: https://pypi.org/p/Pillow
permissions:
id-token: write
steps: steps:
- name: Success - uses: actions/download-artifact@v4
run: echo Wheels Successful with:
pattern: dist-*
path: dist
merge-multiple: true
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

View File

@ -5,6 +5,9 @@ Changelog (Pillow)
10.2.0 (unreleased) 10.2.0 (unreleased)
------------------- -------------------
- Fix incorrect color blending for overlapping glyphs #7497
[ZachNagengast, nulano, radarhere]
- Attempt memory mapping when tile args is a string #7565 - Attempt memory mapping when tile args is a string #7565
[radarhere] [radarhere]

Binary file not shown.

Binary file not shown.

View File

@ -2,7 +2,6 @@
NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
NotoSans-Regular.ttf, from https://www.google.com/get/noto/ NotoSans-Regular.ttf, from https://www.google.com/get/noto/
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/ NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
NotoColorEmoji.ttf, from https://github.com/googlefonts/noto-emoji
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
@ -25,3 +24,5 @@ FreeMono.ttf is licensed under GPLv3.
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base 10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
"Public domain font. Share and enjoy." "Public domain font. Share and enjoy."
CBDTTestFont.ttf and EBDTTestFont.ttf from https://github.com/nulano/font-tests are public domain.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
Tests/images/cbdt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

BIN
Tests/images/cbdt_mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1,5 +1,7 @@
#!/usr/bin/python3 #!/usr/bin/python3
from __future__ import annotations
# Copyright 2020 Google LLC # Copyright 2020 Google LLC
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@ -13,7 +15,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import annotations
import atheris import atheris

View File

@ -1,5 +1,7 @@
#!/usr/bin/python3 #!/usr/bin/python3
from __future__ import annotations
# Copyright 2020 Google LLC # Copyright 2020 Google LLC
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@ -13,7 +15,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import annotations
import atheris import atheris

View File

@ -859,6 +859,19 @@ def test_bitmap_font_stroke(layout_engine):
assert_image_similar_tofile(im, target, 0.03) assert_image_similar_tofile(im, target, 0.03)
@pytest.mark.parametrize("embedded_color", (False, True))
def test_bitmap_blend(layout_engine, embedded_color):
font = ImageFont.truetype(
"Tests/fonts/EBDTTestFont.ttf", size=64, layout_engine=layout_engine
)
im = Image.new("RGBA", (128, 96), "white")
d = ImageDraw.Draw(im)
d.text((16, 16), "AA", font=font, fill="#8E2F52", embedded_color=embedded_color)
assert_image_equal_tofile(im, "Tests/images/bitmap_font_blend.png")
def test_standard_embedded_color(layout_engine): def test_standard_embedded_color(layout_engine):
txt = "Hello World!" txt = "Hello World!"
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
@ -897,15 +910,15 @@ def test_float_coord(layout_engine, fontmode):
def test_cbdt(layout_engine): def test_cbdt(layout_engine):
try: try:
font = ImageFont.truetype( font = ImageFont.truetype(
"Tests/fonts/NotoColorEmoji.ttf", size=109, layout_engine=layout_engine "Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
) )
im = Image.new("RGB", (150, 150), "white") im = Image.new("RGB", (128, 96), "white")
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
d.text((10, 10), "\U0001f469", font=font, embedded_color=True) d.text((16, 16), "AB", font=font, embedded_color=True)
assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) assert_image_equal_tofile(im, "Tests/images/cbdt.png")
except OSError as e: # pragma: no cover except OSError as e: # pragma: no cover
assert str(e) in ("unimplemented feature", "unknown file format") assert str(e) in ("unimplemented feature", "unknown file format")
pytest.skip("freetype compiled without libpng or CBDT support") pytest.skip("freetype compiled without libpng or CBDT support")
@ -914,17 +927,15 @@ def test_cbdt(layout_engine):
def test_cbdt_mask(layout_engine): def test_cbdt_mask(layout_engine):
try: try:
font = ImageFont.truetype( font = ImageFont.truetype(
"Tests/fonts/NotoColorEmoji.ttf", size=109, layout_engine=layout_engine "Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
) )
im = Image.new("RGB", (150, 150), "white") im = Image.new("RGB", (128, 96), "white")
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
d.text((10, 10), "\U0001f469", "black", font=font) d.text((16, 16), "AB", "green", font=font)
assert_image_similar_tofile( assert_image_equal_tofile(im, "Tests/images/cbdt_mask.png")
im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2
)
except OSError as e: # pragma: no cover except OSError as e: # pragma: no cover
assert str(e) in ("unimplemented feature", "unknown file format") assert str(e) in ("unimplemented feature", "unknown file format")
pytest.skip("freetype compiled without libpng or CBDT support") pytest.skip("freetype compiled without libpng or CBDT support")

View File

@ -233,7 +233,7 @@ htmlhelp_basename = "PillowPILForkdoc"
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
latex_elements = { latex_elements: dict[str, str] = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper', # 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').

View File

@ -5,7 +5,7 @@ from PIL import Image, ImageDraw, ImageFont
font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 16) font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 16)
def test(anchor): def test(anchor: str) -> Image.Image:
im = Image.new("RGBA", (200, 100), "white") im = Image.new("RGBA", (200, 100), "white")
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
d.line(((100, 0), (100, 100)), "gray") d.line(((100, 0), (100, 100)), "gray")

View File

@ -15,7 +15,7 @@ except AttributeError:
pass pass
def testimage(): def testimage() -> None:
""" """
PIL lets you create in-memory images with various pixel types: PIL lets you create in-memory images with various pixel types:

View File

@ -24,7 +24,7 @@ class ContainerIO:
file (for example a TAR file). file (for example a TAR file).
""" """
def __init__(self, file, offset, length): def __init__(self, file, offset, length) -> None:
""" """
Create file object. Create file object.

View File

@ -1181,7 +1181,7 @@ class Image:
return im return im
def copy(self): def copy(self) -> Image:
""" """
Copies this image. Use this method if you wish to paste things Copies this image. Use this method if you wish to paste things
into an image, but still retain the original. into an image, but still retain the original.
@ -2467,7 +2467,7 @@ class Image:
} }
) )
def seek(self, frame): def seek(self, frame) -> Image:
""" """
Seeks to the given frame in this sequence file. If you seek Seeks to the given frame in this sequence file. If you seek
beyond the end of the sequence, the method raises an beyond the end of the sequence, the method raises an
@ -2554,7 +2554,7 @@ class Image:
return self._new(self.im.getband(channel)) return self._new(self.im.getband(channel))
def tell(self): def tell(self) -> int:
""" """
Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`. Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`.

View File

@ -33,6 +33,7 @@ from __future__ import annotations
import math import math
import numbers import numbers
import struct
from . import Image, ImageColor from . import Image, ImageColor
@ -543,7 +544,8 @@ class ImageDraw:
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
# extract mask and set text alpha # extract mask and set text alpha
color, mask = mask, mask.getband(3) color, mask = mask, mask.getband(3)
color.fillband(3, (ink >> 24) & 0xFF) ink_alpha = struct.pack("i", ink)[3]
color.fillband(3, ink_alpha)
x, y = coord x, y = coord
self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask) self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask)
else: else:

View File

@ -15,33 +15,37 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from functools import lru_cache
# mode descriptor cache
_modes = None
class ModeDescriptor: class ModeDescriptor:
"""Wrapper for mode strings.""" """Wrapper for mode strings."""
def __init__(self, mode, bands, basemode, basetype, typestr): def __init__(
self,
mode: str,
bands: tuple[str, ...],
basemode: str,
basetype: str,
typestr: str,
) -> None:
self.mode = mode self.mode = mode
self.bands = bands self.bands = bands
self.basemode = basemode self.basemode = basemode
self.basetype = basetype self.basetype = basetype
self.typestr = typestr self.typestr = typestr
def __str__(self): def __str__(self) -> str:
return self.mode return self.mode
def getmode(mode): @lru_cache
def getmode(mode: str) -> ModeDescriptor:
"""Gets a mode descriptor for the given mode.""" """Gets a mode descriptor for the given mode."""
global _modes
if not _modes:
# initialize mode cache # initialize mode cache
modes = {}
endian = "<" if sys.byteorder == "little" else ">" endian = "<" if sys.byteorder == "little" else ">"
for m, (basemode, basetype, bands, typestr) in {
modes = {
# core modes # core modes
# Bits need to be extended to bytes # Bits need to be extended to bytes
"1": ("L", "L", ("1",), "|b1"), "1": ("L", "L", ("1",), "|b1"),
@ -65,10 +69,12 @@ def getmode(mode):
"LA": ("L", "L", ("L", "A"), "|u1"), "LA": ("L", "L", ("L", "A"), "|u1"),
"La": ("L", "L", ("L", "a"), "|u1"), "La": ("L", "L", ("L", "a"), "|u1"),
"PA": ("RGB", "L", ("P", "A"), "|u1"), "PA": ("RGB", "L", ("P", "A"), "|u1"),
}.items(): }
modes[m] = ModeDescriptor(m, bands, basemode, basetype, typestr) if mode in modes:
# mapping modes base_mode, base_type, bands, type_str = modes[mode]
for i16mode, typestr in { return ModeDescriptor(mode, bands, base_mode, base_type, type_str)
mapping_modes = {
# I;16 == I;16L, and I;32 == I;32L # I;16 == I;16L, and I;32 == I;32L
"I;16": "<u2", "I;16": "<u2",
"I;16S": "<i2", "I;16S": "<i2",
@ -84,8 +90,7 @@ def getmode(mode):
"I;32S": "<i4", "I;32S": "<i4",
"I;32BS": ">i4", "I;32BS": ">i4",
"I;32LS": "<i4", "I;32LS": "<i4",
}.items(): }
modes[i16mode] = ModeDescriptor(i16mode, ("I",), "L", "L", typestr)
# set global mode cache atomically type_str = mapping_modes[mode]
_modes = modes return ModeDescriptor(mode, ("I",), "L", "L", type_str)
return _modes[mode]

View File

@ -16,6 +16,10 @@
## ##
from __future__ import annotations from __future__ import annotations
from typing import Callable
from . import Image
class Iterator: class Iterator:
""" """
@ -29,14 +33,14 @@ class Iterator:
:param im: An image object. :param im: An image object.
""" """
def __init__(self, im): def __init__(self, im: Image.Image):
if not hasattr(im, "seek"): if not hasattr(im, "seek"):
msg = "im must have seek method" msg = "im must have seek method"
raise AttributeError(msg) raise AttributeError(msg)
self.im = im self.im = im
self.position = getattr(self.im, "_min_frame", 0) self.position = getattr(self.im, "_min_frame", 0)
def __getitem__(self, ix): def __getitem__(self, ix: int) -> Image.Image:
try: try:
self.im.seek(ix) self.im.seek(ix)
return self.im return self.im
@ -44,10 +48,10 @@ class Iterator:
msg = "end of sequence" msg = "end of sequence"
raise IndexError(msg) from e raise IndexError(msg) from e
def __iter__(self): def __iter__(self) -> Iterator:
return self return self
def __next__(self): def __next__(self) -> Image.Image:
try: try:
self.im.seek(self.position) self.im.seek(self.position)
self.position += 1 self.position += 1
@ -57,7 +61,10 @@ class Iterator:
raise StopIteration(msg) from e raise StopIteration(msg) from e
def all_frames(im, func=None): def all_frames(
im: Image.Image | list[Image.Image],
func: Callable[[Image.Image], Image.Image] | None = None,
) -> list[Image.Image]:
""" """
Applies a given function to all frames in an image or a list of images. Applies a given function to all frames in an image or a list of images.
The frames are returned as a list of separate images. The frames are returned as a list of separate images.

View File

@ -16,6 +16,7 @@
from __future__ import annotations from __future__ import annotations
import io import io
from types import TracebackType
from . import ContainerIO from . import ContainerIO
@ -23,7 +24,7 @@ from . import ContainerIO
class TarIO(ContainerIO.ContainerIO): class TarIO(ContainerIO.ContainerIO):
"""A file object that provides read access to a given member of a TAR file.""" """A file object that provides read access to a given member of a TAR file."""
def __init__(self, tarfile, file): def __init__(self, tarfile: str, file: str) -> None:
""" """
Create file object. Create file object.
@ -57,11 +58,16 @@ class TarIO(ContainerIO.ContainerIO):
super().__init__(self.fh, self.fh.tell(), size) super().__init__(self.fh, self.fh.tell(), size)
# Context manager support # Context manager support
def __enter__(self): def __enter__(self) -> TarIO:
return self return self
def __exit__(self, *args): def __exit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
self.close() self.close()
def close(self): def close(self) -> None:
self.fh.close() self.fh.close()

View File

@ -1049,8 +1049,8 @@ font_render(FontObject *self, PyObject *args) {
if (yy >= 0 && yy < im->ysize) { if (yy >= 0 && yy < im->ysize) {
/* blend this glyph into the buffer */ /* blend this glyph into the buffer */
int k; int k;
unsigned char v;
unsigned char *target; unsigned char *target;
unsigned int tmp;
if (color) { if (color) {
/* target[RGB] returns the color, target[A] returns the mask */ /* target[RGB] returns the color, target[A] returns the mask */
/* target bands get split again in ImageDraw.text */ /* target bands get split again in ImageDraw.text */
@ -1061,34 +1061,55 @@ font_render(FontObject *self, PyObject *args) {
if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
/* paste color glyph */ /* paste color glyph */
for (k = x0; k < x1; k++) { for (k = x0; k < x1; k++) {
if (target[k * 4 + 3] < source[k * 4 + 3]) { unsigned int src_alpha = source[k * 4 + 3];
/* unpremultiply BGRa to RGBA */
target[k * 4 + 0] = CLIP8( /* paste only if source has data */
(255 * (int)source[k * 4 + 2]) / source[k * 4 + 3]); if (src_alpha > 0) {
target[k * 4 + 1] = CLIP8( /* unpremultiply BGRa */
(255 * (int)source[k * 4 + 1]) / source[k * 4 + 3]); int src_red = CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha);
target[k * 4 + 2] = CLIP8( int src_green = CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha);
(255 * (int)source[k * 4 + 0]) / source[k * 4 + 3]); int src_blue = CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha);
target[k * 4 + 3] = source[k * 4 + 3];
/* blend required if target has data */
if (target[k * 4 + 3] > 0) {
/* blend RGBA colors */
target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], src_red, tmp);
target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], src_green, tmp);
target[k * 4 + 2] = BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp);
target[k * 4 + 3] = CLIP8(src_alpha + MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp));
} else {
/* paste unpremultiplied RGBA values */
target[k * 4 + 0] = src_red;
target[k * 4 + 1] = src_green;
target[k * 4 + 2] = src_blue;
target[k * 4 + 3] = src_alpha;
}
} }
} }
} else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { } else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
if (color) { if (color) {
unsigned char *ink = (unsigned char *)&foreground_ink; unsigned char *ink = (unsigned char *)&foreground_ink;
for (k = x0; k < x1; k++) { for (k = x0; k < x1; k++) {
v = source[k] * convert_scale; unsigned int src_alpha = source[k] * convert_scale;
if (target[k * 4 + 3] < v) { if (src_alpha > 0) {
if (target[k * 4 + 3] > 0) {
target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], ink[0], tmp);
target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], ink[1], tmp);
target[k * 4 + 2] = BLEND(src_alpha, target[k * 4 + 2], ink[2], tmp);
target[k * 4 + 3] = CLIP8(src_alpha + MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp));
} else {
target[k * 4 + 0] = ink[0]; target[k * 4 + 0] = ink[0];
target[k * 4 + 1] = ink[1]; target[k * 4 + 1] = ink[1];
target[k * 4 + 2] = ink[2]; target[k * 4 + 2] = ink[2];
target[k * 4 + 3] = v; target[k * 4 + 3] = src_alpha;
}
} }
} }
} else { } else {
for (k = x0; k < x1; k++) { for (k = x0; k < x1; k++) {
v = source[k] * convert_scale; unsigned int src_alpha = source[k] * convert_scale;
if (target[k] < v) { if (src_alpha > 0) {
target[k] = v; target[k] = target[k] > 0 ? CLIP8(src_alpha + MULDIV255(target[k], (255 - src_alpha), tmp)) : src_alpha;
} }
} }
} }

View File

@ -56,7 +56,9 @@ def cmd_nmake(
) )
def cmds_cmake(target: str | tuple[str, ...] | list[str], *params) -> list[str]: def cmds_cmake(
target: str | tuple[str, ...] | list[str], *params, build_dir: str = "."
) -> list[str]:
if not isinstance(target, str): if not isinstance(target, str):
target = " ".join(target) target = " ".join(target)
@ -73,10 +75,11 @@ def cmds_cmake(target: str | tuple[str, ...] | list[str], *params) -> list[str]:
"-DCMAKE_CXX_FLAGS=-nologo", "-DCMAKE_CXX_FLAGS=-nologo",
*params, *params,
'-G "{cmake_generator}"', '-G "{cmake_generator}"',
".", f'-B "{build_dir}"',
"-S .",
] ]
), ),
f"{{cmake}} --build . --clean-first --parallel --target {target}", f'{{cmake}} --build "{build_dir}" --clean-first --parallel --target {target}',
] ]
@ -367,7 +370,14 @@ DEPS = {
"build": [ "build": [
cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.13-COPYING"), cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.13-COPYING"),
cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
*cmds_cmake("fribidi"), # generated tab.i files cannot be cross-compiled
" ^&^& ".join(
[
"if {architecture}==ARM64 cmd /c call {vcvarsall} x86",
*cmds_cmake("fribidi-gen", "-DARCH=x86", build_dir="build_x86"),
]
),
*cmds_cmake("fribidi", "-DARCH={architecture}"),
], ],
"bins": [r"*.dll"], "bins": [r"*.dll"],
}, },
@ -381,10 +391,9 @@ def find_msvs(architecture: str) -> dict[str, str] | None:
print("Program Files not found") print("Program Files not found")
return None return None
requires = ["-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"]
if architecture == "ARM64": if architecture == "ARM64":
tools = "Microsoft.VisualStudio.Component.VC.Tools.ARM64" requires += ["-requires", "Microsoft.VisualStudio.Component.VC.Tools.ARM64"]
else:
tools = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
try: try:
vspath = ( vspath = (
@ -395,8 +404,7 @@ def find_msvs(architecture: str) -> dict[str, str] | None:
), ),
"-latest", "-latest",
"-prerelease", "-prerelease",
"-requires", *requires,
tools,
"-property", "-property",
"installationPath", "installationPath",
"-products", "-products",
@ -707,11 +715,6 @@ if __name__ == "__main__":
disabled += ["libimagequant"] disabled += ["libimagequant"]
if args.no_fribidi: if args.no_fribidi:
disabled += ["fribidi"] disabled += ["fribidi"]
elif args.architecture == "ARM64" and platform.machine() != "ARM64":
import warnings
warnings.warn("Cross-compiling FriBiDi is currently not supported, disabling")
disabled += ["fribidi"]
prefs = { prefs = {
"architecture": args.architecture, "architecture": args.architecture,

View File

@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.12)
project(fribidi) project(fribidi)
add_definitions(-D_CRT_SECURE_NO_WARNINGS) add_definitions(-D_CRT_SECURE_NO_WARNINGS)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(lib) include_directories(lib)
function(extract_regex_1 var text regex) function(extract_regex_1 var text regex)
@ -27,12 +27,20 @@ function(fribidi_conf)
set(PACKAGE_BUGREPORT "https://github.com/fribidi/fribidi/issues/new") set(PACKAGE_BUGREPORT "https://github.com/fribidi/fribidi/issues/new")
set(SIZEOF_INT 4) set(SIZEOF_INT 4)
set(FRIBIDI_MSVC_BUILD_PLACEHOLDER "#define FRIBIDI_BUILT_WITH_MSVC") set(FRIBIDI_MSVC_BUILD_PLACEHOLDER "#define FRIBIDI_BUILT_WITH_MSVC")
message("detected ${PACKAGE_NAME} version ${FRIBIDI_VERSION}") message("Detected ${PACKAGE_NAME} version ${FRIBIDI_VERSION}")
configure_file(lib/fribidi-config.h.in lib/fribidi-config.h @ONLY) configure_file(lib/fribidi-config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/lib/fribidi-config.h @ONLY)
endfunction() endfunction()
fribidi_conf() fribidi_conf()
option(ARCH "Target architecture")
if(${ARCH} STREQUAL ARM64)
set(GEN FALSE)
else()
set(GEN TRUE)
endif()
message("Generate tab.i files: " ${GEN})
function(prepend var prefix) function(prepend var prefix)
set(out "") set(out "")
foreach(f ${ARGN}) foreach(f ${ARGN})
@ -56,7 +64,8 @@ macro(fribidi_definitions _TGT)
endmacro() endmacro()
function(fribidi_gen _NAME _OUTNAME _PARAM) function(fribidi_gen _NAME _OUTNAME _PARAM)
set(_OUT lib/${_OUTNAME}) set(_OUT ${CMAKE_CURRENT_SOURCE_DIR}/lib/${_OUTNAME})
if(GEN)
prepend(_DEP "${CMAKE_CURRENT_SOURCE_DIR}/gen.tab/" ${ARGN}) prepend(_DEP "${CMAKE_CURRENT_SOURCE_DIR}/gen.tab/" ${ARGN})
add_executable(gen-${_NAME} add_executable(gen-${_NAME}
gen.tab/gen-${_NAME}.c gen.tab/gen-${_NAME}.c
@ -68,6 +77,7 @@ function(fribidi_gen _NAME _OUTNAME _PARAM)
COMMAND gen-${_NAME} ${_PARAM} ${_DEP} > ${_OUT} COMMAND gen-${_NAME} ${_PARAM} ${_DEP} > ${_OUT}
DEPENDS ${_DEP} DEPENDS ${_DEP}
OUTPUT ${_OUT}) OUTPUT ${_OUT})
endif(GEN)
list(APPEND FRIBIDI_SOURCES_GENERATED "${_OUT}") list(APPEND FRIBIDI_SOURCES_GENERATED "${_OUT}")
set(FRIBIDI_SOURCES_GENERATED ${FRIBIDI_SOURCES_GENERATED} PARENT_SCOPE) set(FRIBIDI_SOURCES_GENERATED ${FRIBIDI_SOURCES_GENERATED} PARENT_SCOPE)
endfunction() endfunction()
@ -78,8 +88,10 @@ fribidi_gen(unicode-version fribidi-unicode-version.h ""
macro(fribidi_tab _NAME) macro(fribidi_tab _NAME)
fribidi_gen(${_NAME}-tab ${_NAME}.tab.i 2 ${ARGN}) fribidi_gen(${_NAME}-tab ${_NAME}.tab.i 2 ${ARGN})
if(GEN)
target_sources(gen-${_NAME}-tab target_sources(gen-${_NAME}-tab
PRIVATE lib/fribidi-unicode-version.h) PRIVATE lib/fribidi-unicode-version.h)
endif(GEN)
endmacro() endmacro()
fribidi_tab(bidi-type unidata/UnicodeData.txt) fribidi_tab(bidi-type unidata/UnicodeData.txt)
@ -89,6 +101,8 @@ fribidi_tab(mirroring unidata/BidiMirroring.txt)
fribidi_tab(brackets unidata/BidiBrackets.txt unidata/UnicodeData.txt) fribidi_tab(brackets unidata/BidiBrackets.txt unidata/UnicodeData.txt)
fribidi_tab(brackets-type unidata/BidiBrackets.txt) fribidi_tab(brackets-type unidata/BidiBrackets.txt)
add_custom_target(fribidi-gen DEPENDS ${FRIBIDI_SOURCES_GENERATED})
file(GLOB FRIBIDI_SOURCES lib/*.c) file(GLOB FRIBIDI_SOURCES lib/*.c)
file(GLOB FRIBIDI_HEADERS lib/*.h) file(GLOB FRIBIDI_HEADERS lib/*.h)